From 8377235fffb5fc975dc1ef5039647737b49f1c92 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Tue, 23 Oct 2018 22:26:50 +0200 Subject: [PATCH 01/58] Debug info removed, close #143 --- .../OperationalStatusToStringConverter.cs | 3 --- .../Localization/Strings.Designer.cs | 27 ++++++++++++------- .../Resources/Localization/Strings.resx | 9 ++++--- .../Views/IPScannerSettingsView.xaml | 2 +- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Source/NETworkManager/Converters/OperationalStatusToStringConverter.cs b/Source/NETworkManager/Converters/OperationalStatusToStringConverter.cs index dedf349217..10dbbe50d5 100644 --- a/Source/NETworkManager/Converters/OperationalStatusToStringConverter.cs +++ b/Source/NETworkManager/Converters/OperationalStatusToStringConverter.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Globalization; using System.Net.NetworkInformation; using System.Windows.Data; @@ -10,8 +9,6 @@ public sealed class OperationalStatusToStringConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - Debug.WriteLine("Converter runs..."); - if (!(value is OperationalStatus operationalStatus)) return "-/-"; diff --git a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs index 4adea0370d..b10eaf3ef6 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs @@ -3716,15 +3716,6 @@ public static string RAW { } } - /// - /// Looks up a localized string similar to Read the MAC address from the ARP cache. - /// - public static string ReadTheMACAddressFromTheArpCache { - get { - return ResourceManager.GetString("ReadTheMACAddressFromTheArpCache", resourceCulture); - } - } - /// /// Looks up a localized string similar to Received / Lost. /// @@ -4346,6 +4337,15 @@ public static string ResolveHostname { } } + /// + /// Looks up a localized string similar to Resolve MAC address and vendor. + /// + public static string ResolveMACAddressAndVendor { + get { + return ResourceManager.GetString("ResolveMACAddressAndVendor", resourceCulture); + } + } + /// /// Looks up a localized string similar to Resolve PTR. /// @@ -4940,6 +4940,15 @@ public static string Steel { } } + /// + /// Looks up a localized string similar to . + /// + public static string String1 { + get { + return ResourceManager.GetString("String1", resourceCulture); + } + } + /// /// Looks up a localized string similar to Subnet. /// diff --git a/Source/NETworkManager/Resources/Localization/Strings.resx b/Source/NETworkManager/Resources/Localization/Strings.resx index 69404c8485..cae8869085 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.resx +++ b/Source/NETworkManager/Resources/Localization/Strings.resx @@ -561,9 +561,6 @@ Primary DNS server - - Read the MAC address from the ARP cache - Received / Lost @@ -2030,4 +2027,10 @@ If you click "Move & Restart", the remaining files will be copied and the ap Connecting... + + Resolve MAC address and vendor + + + + \ No newline at end of file diff --git a/Source/NETworkManager/Views/IPScannerSettingsView.xaml b/Source/NETworkManager/Views/IPScannerSettingsView.xaml index 9f057ea468..7b4fd2482b 100644 --- a/Source/NETworkManager/Views/IPScannerSettingsView.xaml +++ b/Source/NETworkManager/Views/IPScannerSettingsView.xaml @@ -55,7 +55,7 @@ - + From 8d5bffcf8095da8107a05034ec771b72b8d5e381 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Tue, 23 Oct 2018 22:54:12 +0200 Subject: [PATCH 02/58] Close #152 --- Source/NETworkManager/Controls/PuTTYControl.xaml.cs | 4 ++-- Source/NETworkManager/Controls/TightVNCControl.xaml.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/NETworkManager/Controls/PuTTYControl.xaml.cs b/Source/NETworkManager/Controls/PuTTYControl.xaml.cs index ff7058e85e..3e0df3e9f5 100644 --- a/Source/NETworkManager/Controls/PuTTYControl.xaml.cs +++ b/Source/NETworkManager/Controls/PuTTYControl.xaml.cs @@ -168,10 +168,10 @@ private async void Connect() style &= ~(NativeMethods.WS_CAPTION | NativeMethods.WS_POPUP | NativeMethods.WS_THICKFRAME); NativeMethods.SetWindowLongPtr(_appWin, NativeMethods.GWL_STYLE, new IntPtr(style)); + IsConnected = true; + // Resize embedded application & refresh ResizeEmbeddedPutty(); - - IsConnected = true; } } else diff --git a/Source/NETworkManager/Controls/TightVNCControl.xaml.cs b/Source/NETworkManager/Controls/TightVNCControl.xaml.cs index 2d17328d82..1d12d41821 100644 --- a/Source/NETworkManager/Controls/TightVNCControl.xaml.cs +++ b/Source/NETworkManager/Controls/TightVNCControl.xaml.cs @@ -167,11 +167,11 @@ private async void Connect() long style = (int)NativeMethods.GetWindowLong(_appWin, NativeMethods.GWL_STYLE); style &= ~(NativeMethods.WS_CAPTION | NativeMethods.WS_POPUP | NativeMethods.WS_THICKFRAME); NativeMethods.SetWindowLongPtr(_appWin, NativeMethods.GWL_STYLE, new IntPtr(style)); + + IsConnected = true; // Resize embedded application & refresh ResizeEmbeddedTightVNC(); - - IsConnected = true; } } else From bba03e0ae7278ae346b7a644c65cdf35afbf922c Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Tue, 23 Oct 2018 22:58:39 +0200 Subject: [PATCH 03/58] Update to 1.8.1.0 --- Source/NETworkManager/Controls/PuTTYControl.xaml.cs | 8 ++++---- Source/NETworkManager/Properties/AssemblyInfo.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/NETworkManager/Controls/PuTTYControl.xaml.cs b/Source/NETworkManager/Controls/PuTTYControl.xaml.cs index 3e0df3e9f5..f388603226 100644 --- a/Source/NETworkManager/Controls/PuTTYControl.xaml.cs +++ b/Source/NETworkManager/Controls/PuTTYControl.xaml.cs @@ -171,7 +171,7 @@ private async void Connect() IsConnected = true; // Resize embedded application & refresh - ResizeEmbeddedPutty(); + ResizeEmbeddedPuTTY(); } } else @@ -204,7 +204,7 @@ private void PuTTYProcess_Exited(object sender, EventArgs e) IsConnected = false; } - private void ResizeEmbeddedPutty() + private void ResizeEmbeddedPuTTY() { if (IsConnected) NativeMethods.SetWindowPos(_puttyProcess.MainWindowHandle, IntPtr.Zero, 0, 0, PuTTYHost.ClientSize.Width, PuTTYHost.ClientSize.Height, NativeMethods.SWP_NOZORDER | NativeMethods.SWP_NOACTIVATE); @@ -234,14 +234,14 @@ public void CloseTab() private void PuTTYGrid_SizeChanged(object sender, SizeChangedEventArgs e) { if (_puttyProcess != null) - ResizeEmbeddedPutty(); + ResizeEmbeddedPuTTY(); } private void ResizeTimer_Tick(object sender, EventArgs e) { _resizeTimer.Stop(); - ResizeEmbeddedPutty(); + ResizeEmbeddedPuTTY(); } #endregion } diff --git a/Source/NETworkManager/Properties/AssemblyInfo.cs b/Source/NETworkManager/Properties/AssemblyInfo.cs index d10f08c60a..fcd38ffe63 100644 --- a/Source/NETworkManager/Properties/AssemblyInfo.cs +++ b/Source/NETworkManager/Properties/AssemblyInfo.cs @@ -49,6 +49,6 @@ // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern // übernehmen, indem Sie "*" eingeben: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.8.0.0")] -[assembly: AssemblyFileVersion("1.8.0.0")] +[assembly: AssemblyVersion("1.8.1.0")] +[assembly: AssemblyFileVersion("1.8.1.0")] From bb801865640eb77d3aac73647705133a7be3cd83 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Tue, 23 Oct 2018 23:07:14 +0200 Subject: [PATCH 04/58] Debug info removed --- Source/NETworkManager/App.xaml.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/NETworkManager/App.xaml.cs b/Source/NETworkManager/App.xaml.cs index 2c7880b604..03f0494496 100644 --- a/Source/NETworkManager/App.xaml.cs +++ b/Source/NETworkManager/App.xaml.cs @@ -52,7 +52,6 @@ private void Application_Startup(object sender, StartupEventArgs e) // Update integrated settings %LocalAppData%\NETworkManager\NETworkManager_GUID (custom settings path) if (Settings.Default.UpgradeRequired) { - Debug.Write("Upgrade settings..."); Settings.Default.Upgrade(); Settings.Default.UpgradeRequired = false; } From a5a9a4d0ae30cc7b921707c9163fa4c7c8f00c29 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Thu, 25 Oct 2018 21:43:10 +0200 Subject: [PATCH 05/58] Chocolatey update to 1.8.1.0 --- Choco/networkmanager.nuspec | 2 +- Choco/tools/chocolateyinstall.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Choco/networkmanager.nuspec b/Choco/networkmanager.nuspec index a243346f50..d5f3b282d6 100644 --- a/Choco/networkmanager.nuspec +++ b/Choco/networkmanager.nuspec @@ -3,7 +3,7 @@ NETworkManager - 1.8.0.0 + 1.8.1.0 BornToBeRoot NETworkManager BornToBeRoot diff --git a/Choco/tools/chocolateyinstall.ps1 b/Choco/tools/chocolateyinstall.ps1 index d488cac3e1..a66ea0034a 100644 --- a/Choco/tools/chocolateyinstall.ps1 +++ b/Choco/tools/chocolateyinstall.ps1 @@ -1,7 +1,7 @@  $ErrorActionPreference = 'Stop'; $toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" -$url = 'https://github.com/BornToBeRoot/NETworkManager/releases/download/v1.8.0.0/NETworkManager_v1.8.0.0_Setup.msi' +$url = 'https://github.com/BornToBeRoot/NETworkManager/releases/download/v1.8.1.0/NETworkManager_v1.8.1.0_Setup.msi' $packageArgs = @{ packageName = $env:ChocolateyPackageName @@ -11,7 +11,7 @@ $packageArgs = @{ softwareName = 'NETworkManager*' - checksum = 'F3470CA8626A0E2E8A7342D7EC6E1280B7955281B49F162B63215D1FB038AD67' + checksum = 'CB2F9B4B1A269965AC26D3C5FE7D76D4D2B7CAED59549CF8599F83E2032CCF37' checksumType = 'sha256' silentArgs = "/qn /norestart /l*v `"$($env:TEMP)\$($packageName).$($env:chocolateyPackageVersion).MsiInstall.log`"" From a9e38683765958c899f908cf6e8e4b2cd781ba68 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Mon, 29 Oct 2018 00:40:30 +0100 Subject: [PATCH 06/58] Export dialog added --- Source/NETworkManager/NETworkManager.csproj | 8 ++ .../Localization/Strings.Designer.cs | 54 +++++++++++++ .../Resources/Localization/Strings.resx | 18 +++++ .../ViewModels/ExportViewModel.cs | 81 +++++++++++++++++++ .../ViewModels/IPScannerViewModel.cs | 50 +++++++++++- Source/NETworkManager/Views/ExportDialog.xaml | 49 +++++++++++ .../NETworkManager/Views/ExportDialog.xaml.cs | 10 +++ .../NETworkManager/Views/IPScannerView.xaml | 30 ++++++- .../Views/IPScannerView.xaml.cs | 3 +- 9 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 Source/NETworkManager/ViewModels/ExportViewModel.cs create mode 100644 Source/NETworkManager/Views/ExportDialog.xaml create mode 100644 Source/NETworkManager/Views/ExportDialog.xaml.cs diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj index 1270729bf9..e6acda1794 100644 --- a/Source/NETworkManager/NETworkManager.csproj +++ b/Source/NETworkManager/NETworkManager.csproj @@ -313,6 +313,7 @@ + @@ -372,6 +373,9 @@ RemoteDesktopControl.xaml + + ExportDialog.xaml + DNSServerDialog.xaml @@ -660,6 +664,10 @@ Designer PreserveNewest + + MSBuild:Compile + Designer + MSBuild:Compile Designer diff --git a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs index b10eaf3ef6..ce1a3cee8a 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs @@ -987,6 +987,15 @@ public static string Crimson { } } + /// + /// Looks up a localized string similar to CSV. + /// + public static string CSV { + get { + return ResourceManager.GetString("CSV", resourceCulture); + } + } + /// /// Looks up a localized string similar to Custom. /// @@ -2229,6 +2238,42 @@ public static string Export { } } + /// + /// Looks up a localized string similar to Export all. + /// + public static string ExportAll { + get { + return ResourceManager.GetString("ExportAll", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export.... + /// + public static string ExportDots { + get { + return ResourceManager.GetString("ExportDots", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export location.... + /// + public static string ExportLocationDots { + get { + return ResourceManager.GetString("ExportLocationDots", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export selected. + /// + public static string ExportSelected { + get { + return ResourceManager.GetString("ExportSelected", resourceCulture); + } + } + /// /// Looks up a localized string similar to Field cannot be empty!. /// @@ -5786,6 +5831,15 @@ public static string WrongPasswordMessage { } } + /// + /// Looks up a localized string similar to XML. + /// + public static string XML { + get { + return ResourceManager.GetString("XML", resourceCulture); + } + } + /// /// Looks up a localized string similar to Yellow. /// diff --git a/Source/NETworkManager/Resources/Localization/Strings.resx b/Source/NETworkManager/Resources/Localization/Strings.resx index cae8869085..43afefcf40 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.resx +++ b/Source/NETworkManager/Resources/Localization/Strings.resx @@ -2033,4 +2033,22 @@ If you click "Move & Restart", the remaining files will be copied and the ap + + Export all + + + Export... + + + Export selected + + + CSV + + + Export location... + + + XML + \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/ExportViewModel.cs b/Source/NETworkManager/ViewModels/ExportViewModel.cs new file mode 100644 index 0000000000..a3c6a5686f --- /dev/null +++ b/Source/NETworkManager/ViewModels/ExportViewModel.cs @@ -0,0 +1,81 @@ +using NETworkManager.Utilities; +using System; +using System.Windows.Forms; +using System.Windows.Input; + +namespace NETworkManager.ViewModels +{ + public class ExportViewModel : ViewModelBase + { + public ICommand ExportCommand { get; } + + public ICommand CancelCommand { get; } + + private bool _useCSV; + public bool UseCSV + { + get => _useCSV; + set + { + if(value == _useCSV) + return; + + _useCSV = value; + OnPropertyChanged(); + } + } + + private bool _useXML; + public bool UseXML + { + get => _useXML; + set + { + if(value== _useXML) + return; + + _useXML = value; + OnPropertyChanged(); + } + } + + private string _exportFilePath; + public string ExportFilePath + { + get => _exportFilePath; + set + { + if(value == _exportFilePath) + return; + + _exportFilePath = value; + OnPropertyChanged(); + } + } + + public ExportViewModel(Action deleteCommand, Action cancelHandler) + { + ExportCommand = new RelayCommand(p => deleteCommand(this)); + CancelCommand = new RelayCommand(p => cancelHandler(this)); + + // Default + UseCSV = true; + } + + public ICommand BrowseFileCommand + { + get { return new RelayCommand(p => BrowseFileAction()); } + } + + private void BrowseFileAction() + { + var saveFileDialog = new SaveFileDialog(); + + if (saveFileDialog.ShowDialog() == DialogResult.OK) + { + ExportFilePath = saveFileDialog.FileName; + } + + } + } +} diff --git a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs index 20a1fa04e1..109d2a3847 100644 --- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs @@ -14,13 +14,16 @@ using System.Linq; using NETworkManager.Utilities; using Dragablz; +using MahApps.Metro.Controls.Dialogs; using NETworkManager.Controls; +using NETworkManager.Views; namespace NETworkManager.ViewModels { public class IPScannerViewModel : ViewModelBase { #region Variables + private readonly IDialogCoordinator _dialogCoordinator; private CancellationTokenSource _cancellationTokenSource; @@ -257,10 +260,12 @@ public bool ExpandStatistics #endregion #region Constructor, load settings, shutdown - public IPScannerViewModel(int tabId, string ipRange) + public IPScannerViewModel(IDialogCoordinator instance, int tabId, string ipRange) { _isLoading = true; + _dialogCoordinator = instance; + _tabId = tabId; IPRange = ipRange; @@ -320,7 +325,7 @@ private void RedirectToApplicationAction(object name) if (!(name is string appName)) return; - if(!Enum.TryParse(appName, out ApplicationViewManager.Name app)) + if (!Enum.TryParse(appName, out ApplicationViewManager.Name app)) return; var host = !string.IsNullOrEmpty(SelectedIPScanResult.Hostname) ? SelectedIPScanResult.Hostname : SelectedIPScanResult @@ -348,7 +353,7 @@ private void PerformDNSLookupHostnameAction() { EventSystem.RedirectToApplication(ApplicationViewManager.Name.DNSLookup, SelectedIPScanResult.Hostname); } - + public ICommand CopySelectedIPAddressCommand { get { return new RelayCommand(p => CopySelectedIPAddressAction()); } @@ -428,6 +433,45 @@ private void CopySelectedStatusAction() { CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString("IPStatus_" + SelectedIPScanResult.PingInfo.Status)); } + + public ICommand ExportAllCommand + { + get { return new RelayCommand(p => ExportAllAction()); } + } + + private async void ExportAllAction() + { + var customDialog = new CustomDialog + { + Title = Resources.Localization.Strings.Export + }; + + var exportViewModel = new ExportViewModel(instance => + { + _dialogCoordinator.HideMetroDialogAsync(this, customDialog); + + }, instance => + { + _dialogCoordinator.HideMetroDialogAsync(this, customDialog); + }); + + customDialog.Content = new ExportDialog + { + DataContext = exportViewModel + }; + + await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); + } + + public ICommand ExportSelectedCommand + { + get { return new RelayCommand(p => ExportSelectedAction()); } + } + + private void ExportSelectedAction() + { + + } #endregion #region Methods diff --git a/Source/NETworkManager/Views/ExportDialog.xaml b/Source/NETworkManager/Views/ExportDialog.xaml new file mode 100644 index 0000000000..ff9fa3dddc --- /dev/null +++ b/Source/NETworkManager/Views/ExportDialog.xaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From cae24b395b8344f2d9c7bc9a14aaa7ebc59f79f3 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sat, 3 Nov 2018 11:14:36 +0100 Subject: [PATCH 09/58] Fix localization, export as csv --- Source/NETworkManager.sln.DotSettings | 2 + .../Converters/AccentToStringConverter.cs | 3 +- .../Converters/AppThemeToStringConverter.cs | 3 +- .../AutoRefreshTimeToStringConverter.cs | 3 +- .../Converters/IPStatusToStringConverter.cs | 9 +-- .../Converters/PortStatusToStringConverter.cs | 3 +- .../Converters/TcpStateToStringConverter.cs | 3 +- .../Models/Export/ExportInfo.cs | 11 +++ .../Models/Export/ExportManager.cs | 52 ++++++++++++ .../Models/Settings/LocalizationManager.cs | 12 +++ Source/NETworkManager/NETworkManager.csproj | 2 + .../ViewModels/ConnectionsViewModel.cs | 4 +- .../ViewModels/ExportViewModel.cs | 32 +++++--- .../ViewModels/IPScannerViewModel.cs | 80 ++++++++++++------- .../ViewModels/PingViewModel.cs | 2 +- .../ViewModels/PortScannerViewModel.cs | 2 +- Source/NETworkManager/Views/ExportDialog.xaml | 2 +- 17 files changed, 165 insertions(+), 60 deletions(-) create mode 100644 Source/NETworkManager/Models/Export/ExportInfo.cs create mode 100644 Source/NETworkManager/Models/Export/ExportManager.cs diff --git a/Source/NETworkManager.sln.DotSettings b/Source/NETworkManager.sln.DotSettings index 66eb939ec1..6ba082da2c 100644 --- a/Source/NETworkManager.sln.DotSettings +++ b/Source/NETworkManager.sln.DotSettings @@ -2,6 +2,7 @@ AES ARP CNAME + CSV DES DNS HTTP @@ -23,6 +24,7 @@ SNMPV SSH TCP + TTL PuTTY VNC XML diff --git a/Source/NETworkManager/Converters/AccentToStringConverter.cs b/Source/NETworkManager/Converters/AccentToStringConverter.cs index 0fa77dcaae..9eb9ee7647 100644 --- a/Source/NETworkManager/Converters/AccentToStringConverter.cs +++ b/Source/NETworkManager/Converters/AccentToStringConverter.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; using System.Windows.Data; +using NETworkManager.Models.Settings; namespace NETworkManager.Converters { @@ -13,7 +14,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn if (!(value is Accent accent)) return "No valid accent passed!"; - var name = Resources.Localization.Strings.ResourceManager.GetString(accent.Name); + var name = Resources.Localization.Strings.ResourceManager.GetString(accent.Name, LocalizationManager.Culture); if (string.IsNullOrEmpty(name)) name = accent.Name; diff --git a/Source/NETworkManager/Converters/AppThemeToStringConverter.cs b/Source/NETworkManager/Converters/AppThemeToStringConverter.cs index 08dea08016..55b87442a7 100644 --- a/Source/NETworkManager/Converters/AppThemeToStringConverter.cs +++ b/Source/NETworkManager/Converters/AppThemeToStringConverter.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; using System.Windows.Data; +using NETworkManager.Models.Settings; namespace NETworkManager.Converters { @@ -13,7 +14,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn if (!(value is AppTheme theme)) return "No valid theme passed!"; - var name = Resources.Localization.Strings.ResourceManager.GetString(theme.Name); + var name = Resources.Localization.Strings.ResourceManager.GetString(theme.Name, LocalizationManager.Culture); if (string.IsNullOrEmpty(name)) name = theme.Name; diff --git a/Source/NETworkManager/Converters/AutoRefreshTimeToStringConverter.cs b/Source/NETworkManager/Converters/AutoRefreshTimeToStringConverter.cs index 57f19772fa..220978fe66 100644 --- a/Source/NETworkManager/Converters/AutoRefreshTimeToStringConverter.cs +++ b/Source/NETworkManager/Converters/AutoRefreshTimeToStringConverter.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Windows.Data; +using NETworkManager.Models.Settings; using static NETworkManager.Utilities.AutoRefreshTime; namespace NETworkManager.Converters @@ -12,7 +13,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn if (!(value is TimeUnit timeUnit)) return "No valid time unit passed!"; - var timeUnitTranslated = Resources.Localization.Strings.ResourceManager.GetString("TimeUnit_" + timeUnit); + var timeUnitTranslated = Resources.Localization.Strings.ResourceManager.GetString("TimeUnit_" + timeUnit, LocalizationManager.Culture); return string.IsNullOrEmpty(timeUnitTranslated) ? timeUnit.ToString() : timeUnitTranslated; } diff --git a/Source/NETworkManager/Converters/IPStatusToStringConverter.cs b/Source/NETworkManager/Converters/IPStatusToStringConverter.cs index fc91246e4c..b4b8f0a3e5 100644 --- a/Source/NETworkManager/Converters/IPStatusToStringConverter.cs +++ b/Source/NETworkManager/Converters/IPStatusToStringConverter.cs @@ -1,7 +1,7 @@ using System; using System.Globalization; -using System.Net.NetworkInformation; using System.Windows.Data; +using NETworkManager.Models.Settings; namespace NETworkManager.Converters { @@ -9,12 +9,7 @@ public sealed class IPStatusToStringConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - if(!(value is IPStatus ipStatus)) - return "-/-"; - - var status = Resources.Localization.Strings.ResourceManager.GetString("IPStatus_" + ipStatus); - - return string.IsNullOrEmpty(status) ? ipStatus.ToString() : status; + return LocalizationManager.TranslateIPStatus(value); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/Source/NETworkManager/Converters/PortStatusToStringConverter.cs b/Source/NETworkManager/Converters/PortStatusToStringConverter.cs index 221c8678e3..7c2f4b76b1 100644 --- a/Source/NETworkManager/Converters/PortStatusToStringConverter.cs +++ b/Source/NETworkManager/Converters/PortStatusToStringConverter.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Windows.Data; +using NETworkManager.Models.Settings; using static NETworkManager.Models.Network.PortInfo; namespace NETworkManager.Converters @@ -12,7 +13,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn if (!(value is PortStatus portStatus)) return "-/-"; - var status = Resources.Localization.Strings.ResourceManager.GetString("PortState_" + portStatus); + var status = Resources.Localization.Strings.ResourceManager.GetString("PortState_" + portStatus, LocalizationManager.Culture); return string.IsNullOrEmpty(status) ? portStatus.ToString() : status; } diff --git a/Source/NETworkManager/Converters/TcpStateToStringConverter.cs b/Source/NETworkManager/Converters/TcpStateToStringConverter.cs index eb91bc7e39..c4049d3643 100644 --- a/Source/NETworkManager/Converters/TcpStateToStringConverter.cs +++ b/Source/NETworkManager/Converters/TcpStateToStringConverter.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Net.NetworkInformation; using System.Windows.Data; +using NETworkManager.Models.Settings; namespace NETworkManager.Converters { @@ -12,7 +13,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn if (!(value is TcpState tcpState)) return "-/-"; - var status = Resources.Localization.Strings.ResourceManager.GetString("TcpState_" + tcpState); + var status = Resources.Localization.Strings.ResourceManager.GetString("TcpState_" + tcpState, LocalizationManager.Culture); return string.IsNullOrEmpty(status) ? tcpState.ToString() : status; } diff --git a/Source/NETworkManager/Models/Export/ExportInfo.cs b/Source/NETworkManager/Models/Export/ExportInfo.cs new file mode 100644 index 0000000000..956721296c --- /dev/null +++ b/Source/NETworkManager/Models/Export/ExportInfo.cs @@ -0,0 +1,11 @@ +using System.Collections.ObjectModel; + +namespace NETworkManager.Models.Export +{ + public class ExportInfo + { + public ApplicationViewManager.Name ApplicationName { get; set; } + public string FilePath { get; set; } + public string Data { get; set; } + } +} diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs new file mode 100644 index 0000000000..7437ef8aaa --- /dev/null +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -0,0 +1,52 @@ +using System.Collections.ObjectModel; +using System.Text; +using NETworkManager.Models.Network; +using NETworkManager.Models.Settings; +using NETworkManager.Resources.Localization; + +namespace NETworkManager.Models.Export +{ + public static class ExportManager + { + #region Methods + public static void Export(ExportInfo exportInfo) + { + System.IO.File.WriteAllText(exportInfo.FilePath, exportInfo.Data); + } + + // IPScannerHostInfo + public static string CreateData(ObservableCollection data, ExportFileType fileType) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.AppendLine($"{Strings.IPAddress},{Strings.Hostname},{Strings.MACAddress},{Strings.Vendor},{Strings.Bytes},{Strings.Time},{Strings.TTL},{Strings.Status}"); + + foreach (var info in data) + { + stringBuilder.AppendLine($"{info.PingInfo.IPAddress},{info.Hostname},{info.MACAddress},{info.Vendor},{info.PingInfo.Bytes},{info.PingInfo.Time},{LocalizationManager.TranslateIPStatus(info.PingInfo.Status)}"); + } + + return stringBuilder.ToString(); + } + + public static string GetFileExtensionAsString(ExportFileType fileExtension) + { + switch (fileExtension) + { + case ExportFileType.CSV: + return "CSV"; + case ExportFileType.XML: + return "XML"; + default: + return string.Empty; + } + } + #endregion + + public enum ExportFileType + { + CSV, + XML + } + } +} diff --git a/Source/NETworkManager/Models/Settings/LocalizationManager.cs b/Source/NETworkManager/Models/Settings/LocalizationManager.cs index 8e5cb45079..5c82b8951c 100644 --- a/Source/NETworkManager/Models/Settings/LocalizationManager.cs +++ b/Source/NETworkManager/Models/Settings/LocalizationManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Net.NetworkInformation; namespace NETworkManager.Models.Settings { @@ -50,5 +51,16 @@ public static void Change(LocalizationInfo info) // Set the culture code Culture = new CultureInfo(info.Code); } + + + public static string TranslateIPStatus(object value) + { + if (!(value is IPStatus ipStatus)) + return "-/-"; + + var status = Resources.Localization.Strings.ResourceManager.GetString("IPStatus_" + ipStatus, Culture); + + return string.IsNullOrEmpty(status) ? ipStatus.ToString() : status; + } } } diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj index b4f534eee3..8f4d83fe32 100644 --- a/Source/NETworkManager/NETworkManager.csproj +++ b/Source/NETworkManager/NETworkManager.csproj @@ -245,6 +245,8 @@ + + diff --git a/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs b/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs index 4e6b541d18..fbb29727cf 100644 --- a/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs +++ b/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs @@ -173,7 +173,7 @@ public ConnectionsViewModel() var filter = Search.Replace(" ", "").Replace("-", "").Replace(":", ""); // Search by local/remote IP Address, local/remote Port, Protocol and State - return o is ConnectionInfo info && (info.LocalIPAddress.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.LocalPort.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.RemoteIPAddress.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.RemotePort.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.Protocol.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || Resources.Localization.Strings.ResourceManager.GetString("TcpState_" + info.State.ToString()).IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1); + return o is ConnectionInfo info && (info.LocalIPAddress.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.LocalPort.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.RemoteIPAddress.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.RemotePort.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.Protocol.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || Resources.Localization.Strings.ResourceManager.GetString("TcpState_" + info.State.ToString(), LocalizationManager.Culture).IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1); }; AutoRefreshTimes = CollectionViewSource.GetDefaultView(AutoRefreshTime.Defaults); @@ -267,7 +267,7 @@ public ICommand CopySelectedStateCommand private void CopySelectedStateAction() { - CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString("TcpState_" + SelectedConnectionInfo.State)); + CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString("TcpState_" + SelectedConnectionInfo.State, LocalizationManager.Culture)); } #endregion diff --git a/Source/NETworkManager/ViewModels/ExportViewModel.cs b/Source/NETworkManager/ViewModels/ExportViewModel.cs index ad16a26d0e..d9299534b6 100644 --- a/Source/NETworkManager/ViewModels/ExportViewModel.cs +++ b/Source/NETworkManager/ViewModels/ExportViewModel.cs @@ -2,6 +2,7 @@ using System; using System.Windows.Forms; using System.Windows.Input; +using NETworkManager.Models.Export; using NETworkManager.Resources.Localization; namespace NETworkManager.ViewModels @@ -12,15 +13,20 @@ public class ExportViewModel : ViewModelBase public ICommand CancelCommand { get; } + public ExportManager.ExportFileType FileType; + private bool _useCSV; public bool UseCSV { get => _useCSV; set { - if(value == _useCSV) + if (value == _useCSV) return; + if (value) + FileType = ExportManager.ExportFileType.CSV; + _useCSV = value; OnPropertyChanged(); } @@ -32,24 +38,27 @@ public bool UseXML get => _useXML; set { - if(value== _useXML) + if (value == _useXML) return; + if (value) + FileType = ExportManager.ExportFileType.XML; + _useXML = value; OnPropertyChanged(); } } - private string _exportFilePath; - public string ExportFilePath + private string _filePath; + public string FilePath { - get => _exportFilePath; + get => _filePath; set { - if(value == _exportFilePath) + if (value == _filePath) return; - _exportFilePath = value; + _filePath = value; OnPropertyChanged(); } } @@ -72,17 +81,14 @@ private void BrowseFileAction() { var saveFileDialog = new SaveFileDialog(); - if (UseCSV) - saveFileDialog.Filter = $@"CSV-{Strings.File} | *.csv"; + var fileExtension = ExportManager.GetFileExtensionAsString(FileType); - if(UseXML) - saveFileDialog.Filter = $@"XML-{Strings.File} | *.xml"; + saveFileDialog.Filter = $@"{fileExtension}-{Strings.File} | *.{fileExtension.ToLower()}"; if (saveFileDialog.ShowDialog() == DialogResult.OK) { - ExportFilePath = saveFileDialog.FileName; + FilePath = saveFileDialog.FileName; } - } } } diff --git a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs index 109d2a3847..2b280cac78 100644 --- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs @@ -16,6 +16,7 @@ using Dragablz; using MahApps.Metro.Controls.Dialogs; using NETworkManager.Controls; +using NETworkManager.Models.Export; using NETworkManager.Views; namespace NETworkManager.ViewModels @@ -23,6 +24,7 @@ namespace NETworkManager.ViewModels public class IPScannerViewModel : ViewModelBase { #region Variables + private readonly IDialogCoordinator _dialogCoordinator; private CancellationTokenSource _cancellationTokenSource; @@ -36,6 +38,7 @@ public class IPScannerViewModel : ViewModelBase private readonly bool _isLoading; private string _ipRange; + public string IPRange { get => _ipRange; @@ -256,7 +259,6 @@ public bool ExpandStatistics } public bool ShowStatistics => SettingsManager.Current.IPScanner_ShowStatistics; - #endregion #region Constructor, load settings, shutdown @@ -274,7 +276,9 @@ public IPScannerViewModel(IDialogCoordinator instance, int tabId, string ipRange // Result view IPScanResultView = CollectionViewSource.GetDefaultView(IPScanResult); - IPScanResultView.SortDescriptions.Add(new SortDescription(nameof(IPScannerHostInfo.PingInfo) + "." + nameof(PingInfo.IPAddressInt32), ListSortDirection.Ascending)); + IPScanResultView.SortDescriptions.Add(new SortDescription( + nameof(IPScannerHostInfo.PingInfo) + "." + nameof(PingInfo.IPAddressInt32), + ListSortDirection.Ascending)); LoadSettings(); @@ -286,13 +290,13 @@ public IPScannerViewModel(IDialogCoordinator instance, int tabId, string ipRange public void OnLoaded() { - if (_firstLoad) - { - if (!string.IsNullOrEmpty(IPRange)) - StartScan(); + if (!_firstLoad) + return; - _firstLoad = false; - } + if (!string.IsNullOrEmpty(IPRange)) + StartScan(); + + _firstLoad = false; } private void LoadSettings() @@ -302,6 +306,7 @@ private void LoadSettings() #endregion #region ICommands & Actions + public ICommand ScanCommand { get { return new RelayCommand(p => ScanAction()); } @@ -328,8 +333,10 @@ private void RedirectToApplicationAction(object name) if (!Enum.TryParse(appName, out ApplicationViewManager.Name app)) return; - var host = !string.IsNullOrEmpty(SelectedIPScanResult.Hostname) ? SelectedIPScanResult.Hostname : SelectedIPScanResult - .PingInfo.IPAddress.ToString(); + var host = !string.IsNullOrEmpty(SelectedIPScanResult.Hostname) + ? SelectedIPScanResult.Hostname + : SelectedIPScanResult + .PingInfo.IPAddress.ToString(); EventSystem.RedirectToApplication(app, host); } @@ -341,7 +348,8 @@ public ICommand PerformDNSLookupIPAddressCommand private void PerformDNSLookupIPAddressAction() { - EventSystem.RedirectToApplication(ApplicationViewManager.Name.DNSLookup, SelectedIPScanResult.PingInfo.IPAddress.ToString()); + EventSystem.RedirectToApplication(ApplicationViewManager.Name.DNSLookup, + SelectedIPScanResult.PingInfo.IPAddress.ToString()); } public ICommand PerformDNSLookupHostnameCommand @@ -431,7 +439,7 @@ public ICommand CopySelectedStatusCommand private void CopySelectedStatusAction() { - CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString("IPStatus_" + SelectedIPScanResult.PingInfo.Status)); + CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString("IPStatus_" + SelectedIPScanResult.PingInfo.Status, LocalizationManager.Culture)); } public ICommand ExportAllCommand @@ -450,10 +458,16 @@ private async void ExportAllAction() { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); - }, instance => - { - _dialogCoordinator.HideMetroDialogAsync(this, customDialog); - }); + var info = new ExportInfo + { + ApplicationName = ApplicationViewManager.Name.IPScanner, + FilePath = instance.FilePath, + Data = ExportManager.CreateData(IPScanResult, instance.FileType) + }; + + ExportManager.Export(info); + + }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }); customDialog.Content = new ExportDialog { @@ -470,8 +484,8 @@ public ICommand ExportSelectedCommand private void ExportSelectedAction() { - } + #endregion #region Methods @@ -512,16 +526,17 @@ private async void StartScan() try { - ipRanges = await IPScanRangeHelper.ResolveHostnamesInIPRangeAsync(ipHostOrRanges, _cancellationTokenSource.Token); + ipRanges = await IPScanRangeHelper.ResolveHostnamesInIPRangeAsync(ipHostOrRanges, + _cancellationTokenSource.Token); } catch (OperationCanceledException) { - IpScanner_UserHasCanceled(this, EventArgs.Empty); + IPScanner_UserHasCanceled(this, EventArgs.Empty); return; } catch (AggregateException exceptions) // DNS error (could not resolve hostname...) { - IpScanner_DnsResolveFailed(exceptions); + IPScanner_DnsResolveFailed(exceptions); return; } @@ -530,11 +545,13 @@ private async void StartScan() try { // Create a list of all ip addresses - ipAddresses = await IPScanRangeHelper.ConvertIPRangeToIPAddressesAsync(ipRanges.ToArray(), _cancellationTokenSource.Token); + ipAddresses = + await IPScanRangeHelper.ConvertIPRangeToIPAddressesAsync(ipRanges.ToArray(), + _cancellationTokenSource.Token); } catch (OperationCanceledException) { - IpScanner_UserHasCanceled(this, EventArgs.Empty); + IPScanner_UserHasCanceled(this, EventArgs.Empty); return; } @@ -568,9 +585,9 @@ private async void StartScan() var ipScanner = new IPScanner(); ipScanner.HostFound += IpScanner_HostFound; - ipScanner.ScanComplete += IpScanner_ScanComplete; + ipScanner.ScanComplete += IPScanner_ScanComplete; ipScanner.ProgressChanged += IpScanner_ProgressChanged; - ipScanner.UserHasCanceled += IpScanner_UserHasCanceled; + ipScanner.UserHasCanceled += IPScanner_UserHasCanceled; ipScanner.ScanAsync(ipAddresses, ipScannerOptions, _cancellationTokenSource.Token); } @@ -599,7 +616,7 @@ private void ScanFinished() private void AddIPRangeToHistory(string ipRange) { // Create the new list - List list = ListHelper.Modify(SettingsManager.Current.IPScanner_IPRangeHistory.ToList(), ipRange, SettingsManager.Current.General_HistoryListEntries); + var list = ListHelper.Modify(SettingsManager.Current.IPScanner_IPRangeHistory.ToList(), ipRange, SettingsManager.Current.General_HistoryListEntries); // Clear the old items SettingsManager.Current.IPScanner_IPRangeHistory.Clear(); @@ -615,9 +632,11 @@ public void OnClose() if (IsScanRunning) StopScan(); } + #endregion #region Events + private void IpScanner_HostFound(object sender, IPScannerHostFoundArgs e) { var ipScannerHostInfo = IPScannerHostInfo.Parse(e); @@ -631,7 +650,7 @@ private void IpScanner_HostFound(object sender, IPScannerHostFoundArgs e) HostsFound++; } - private void IpScanner_ScanComplete(object sender, EventArgs e) + private void IPScanner_ScanComplete(object sender, EventArgs e) { ScanFinished(); } @@ -641,15 +660,16 @@ private void IpScanner_ProgressChanged(object sender, ProgressChangedArgs e) IPAddressesScanned = e.Value; } - private void IpScanner_DnsResolveFailed(AggregateException e) + private void IPScanner_DnsResolveFailed(AggregateException e) { - StatusMessage = $"{Resources.Localization.Strings.TheFollowingHostnamesCouldNotBeResolved} {string.Join(", ", e.Flatten().InnerExceptions.Select(x => x.Message))}"; + StatusMessage = + $"{Resources.Localization.Strings.TheFollowingHostnamesCouldNotBeResolved} {string.Join(", ", e.Flatten().InnerExceptions.Select(x => x.Message))}"; DisplayStatusMessage = true; ScanFinished(); } - private void IpScanner_UserHasCanceled(object sender, EventArgs e) + private void IPScanner_UserHasCanceled(object sender, EventArgs e) { StatusMessage = Resources.Localization.Strings.CanceledByUserMessage; DisplayStatusMessage = true; @@ -679,4 +699,4 @@ private void SettingsManager_PropertyChanged(object sender, PropertyChangedEvent } #endregion } -} +} \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/PingViewModel.cs b/Source/NETworkManager/ViewModels/PingViewModel.cs index bb8e4ee40d..29ea9a61ac 100644 --- a/Source/NETworkManager/ViewModels/PingViewModel.cs +++ b/Source/NETworkManager/ViewModels/PingViewModel.cs @@ -400,7 +400,7 @@ public ICommand CopySelectedStatusCommand private void CopySelectedStatusAction() { - CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString("IPStatus_" + SelectedPingResult.Status)); + CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString("IPStatus_" + SelectedPingResult.Status, LocalizationManager.Culture)); } #endregion diff --git a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs index 1e9af15f4c..e486cb9f9e 100644 --- a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs @@ -369,7 +369,7 @@ public ICommand CopySelectedStatusCommand private void CopySelectedStatusAction() { - CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString(SelectedScanResult.Status.ToString())); + CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString(SelectedScanResult.Status.ToString(), LocalizationManager.Culture)); } public ICommand CopySelectedProtocolCommand diff --git a/Source/NETworkManager/Views/ExportDialog.xaml b/Source/NETworkManager/Views/ExportDialog.xaml index f18f6086a4..22f1c0607b 100644 --- a/Source/NETworkManager/Views/ExportDialog.xaml +++ b/Source/NETworkManager/Views/ExportDialog.xaml @@ -33,7 +33,7 @@ - + From 7cb817fedf8df931a665d6eca90095d9dd14d216 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sat, 3 Nov 2018 11:50:31 +0100 Subject: [PATCH 10/58] Update --- .../Models/Export/ExportInfo.cs | 11 ------- .../Models/Export/ExportManager.cs | 30 ++++++++++++++----- Source/NETworkManager/NETworkManager.csproj | 1 - .../Localization/Strings.Designer.cs | 27 +++++++++++++++++ .../Resources/Localization/Strings.resx | 9 ++++++ .../ViewModels/ExportViewModel.cs | 29 ++++++++++++++++++ .../ViewModels/IPScannerViewModel.cs | 25 +++------------- Source/NETworkManager/Views/ExportDialog.xaml | 13 ++++++-- .../NETworkManager/Views/IPScannerView.xaml | 20 +------------ 9 files changed, 102 insertions(+), 63 deletions(-) delete mode 100644 Source/NETworkManager/Models/Export/ExportInfo.cs diff --git a/Source/NETworkManager/Models/Export/ExportInfo.cs b/Source/NETworkManager/Models/Export/ExportInfo.cs deleted file mode 100644 index 956721296c..0000000000 --- a/Source/NETworkManager/Models/Export/ExportInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.ObjectModel; - -namespace NETworkManager.Models.Export -{ - public class ExportInfo - { - public ApplicationViewManager.Name ApplicationName { get; set; } - public string FilePath { get; set; } - public string Data { get; set; } - } -} diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index 7437ef8aaa..507fd1c110 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -1,4 +1,5 @@ -using System.Collections.ObjectModel; +using System; +using System.Collections.ObjectModel; using System.Text; using NETworkManager.Models.Network; using NETworkManager.Models.Settings; @@ -9,22 +10,35 @@ namespace NETworkManager.Models.Export public static class ExportManager { #region Methods - public static void Export(ExportInfo exportInfo) + public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) { - System.IO.File.WriteAllText(exportInfo.FilePath, exportInfo.Data); + var text = string.Empty; + switch (fileType) + { + case ExportFileType.CSV: + text = CreateCSVData(collection); + + break; + case ExportFileType.XML: + + + break; + default: + throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); + } + + System.IO.File.WriteAllText(filePath, text); } // IPScannerHostInfo - public static string CreateData(ObservableCollection data, ExportFileType fileType) + public static string CreateCSVData(ObservableCollection collection) { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"{Strings.IPAddress},{Strings.Hostname},{Strings.MACAddress},{Strings.Vendor},{Strings.Bytes},{Strings.Time},{Strings.TTL},{Strings.Status}"); - foreach (var info in data) - { - stringBuilder.AppendLine($"{info.PingInfo.IPAddress},{info.Hostname},{info.MACAddress},{info.Vendor},{info.PingInfo.Bytes},{info.PingInfo.Time},{LocalizationManager.TranslateIPStatus(info.PingInfo.Status)}"); - } + foreach (var info in collection) + stringBuilder.AppendLine($"{info.PingInfo.IPAddress},{info.Hostname},{info.MACAddress},{info.Vendor},{info.PingInfo.Bytes},{info.PingInfo.Time},{info.PingInfo.TTL},{LocalizationManager.TranslateIPStatus(info.PingInfo.Status)}"); return stringBuilder.ToString(); } diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj index 8f4d83fe32..6f3791f11e 100644 --- a/Source/NETworkManager/NETworkManager.csproj +++ b/Source/NETworkManager/NETworkManager.csproj @@ -245,7 +245,6 @@ - diff --git a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs index 6284c39ffc..6b4ff1fea0 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs @@ -276,6 +276,15 @@ public static string Administrator { } } + /// + /// Looks up a localized string similar to All. + /// + public static string All { + get { + return ResourceManager.GetString("All", resourceCulture); + } + } + /// /// Looks up a localized string similar to Always show icon in tray. /// @@ -2373,6 +2382,15 @@ public static string ForkMeOnGitHub { } } + /// + /// Looks up a localized string similar to Format. + /// + public static string Format { + get { + return ResourceManager.GetString("Format", resourceCulture); + } + } + /// /// Looks up a localized string similar to Found. /// @@ -4634,6 +4652,15 @@ public static string SelectAScreenResolution { } } + /// + /// Looks up a localized string similar to Selected. + /// + public static string Selected { + get { + return ResourceManager.GetString("Selected", resourceCulture); + } + } + /// /// Looks up a localized string similar to The selected settings are overwritten.. /// diff --git a/Source/NETworkManager/Resources/Localization/Strings.resx b/Source/NETworkManager/Resources/Localization/Strings.resx index 8fb56e2d32..7652bbb56c 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.resx +++ b/Source/NETworkManager/Resources/Localization/Strings.resx @@ -2057,4 +2057,13 @@ If you click "Move & Restart", the remaining files will be copied and the ap Enter valid file path! + + All + + + Format + + + Selected + \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/ExportViewModel.cs b/Source/NETworkManager/ViewModels/ExportViewModel.cs index d9299534b6..b1734032ac 100644 --- a/Source/NETworkManager/ViewModels/ExportViewModel.cs +++ b/Source/NETworkManager/ViewModels/ExportViewModel.cs @@ -13,6 +13,34 @@ public class ExportViewModel : ViewModelBase public ICommand CancelCommand { get; } + public bool _exportAll; + public bool ExportAll + { + get => _exportAll; + set + { + if(value==_exportAll) + return; + + _exportAll = value; + OnPropertyChanged(); + } + } + + public bool _exportSelected; + public bool ExportSelected + { + get => _exportSelected; + set + { + if (value == _exportSelected) + return; + + _exportSelected = value; + OnPropertyChanged(); + } + } + public ExportManager.ExportFileType FileType; private bool _useCSV; @@ -69,6 +97,7 @@ public ExportViewModel(Action deleteCommand, Action cancelHandler(this)); // Default + ExportAll = true; UseCSV = true; } diff --git a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs index 2b280cac78..d5184a3130 100644 --- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs @@ -442,12 +442,12 @@ private void CopySelectedStatusAction() CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString("IPStatus_" + SelectedIPScanResult.PingInfo.Status, LocalizationManager.Culture)); } - public ICommand ExportAllCommand + public ICommand ExportCommand { - get { return new RelayCommand(p => ExportAllAction()); } + get { return new RelayCommand(p => ExportAction()); } } - private async void ExportAllAction() + private async void ExportAction() { var customDialog = new CustomDialog { @@ -458,14 +458,7 @@ private async void ExportAllAction() { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); - var info = new ExportInfo - { - ApplicationName = ApplicationViewManager.Name.IPScanner, - FilePath = instance.FilePath, - Data = ExportManager.CreateData(IPScanResult, instance.FileType) - }; - - ExportManager.Export(info); + ExportManager.Export(instance.FilePath, instance.FileType, IPScanResult); }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }); @@ -476,16 +469,6 @@ private async void ExportAllAction() await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); } - - public ICommand ExportSelectedCommand - { - get { return new RelayCommand(p => ExportSelectedAction()); } - } - - private void ExportSelectedAction() - { - } - #endregion #region Methods diff --git a/Source/NETworkManager/Views/ExportDialog.xaml b/Source/NETworkManager/Views/ExportDialog.xaml index 22f1c0607b..6e0d4777e7 100644 --- a/Source/NETworkManager/Views/ExportDialog.xaml +++ b/Source/NETworkManager/Views/ExportDialog.xaml @@ -24,14 +24,21 @@ + + - + + + + + + - - + + diff --git a/Source/NETworkManager/Views/IPScannerView.xaml b/Source/NETworkManager/Views/IPScannerView.xaml index 905a7f0c0d..d80b30eb4a 100644 --- a/Source/NETworkManager/Views/IPScannerView.xaml +++ b/Source/NETworkManager/Views/IPScannerView.xaml @@ -343,7 +343,7 @@ - + @@ -351,24 +351,6 @@ - - - - - - - - - - - - - - - - - - From 8638f3bb9e629f823d24ef68244bff31cea30fc6 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sat, 3 Nov 2018 19:32:12 +0100 Subject: [PATCH 11/58] Multi select export added --- .../ViewModels/CredentialsViewModel.cs | 2 +- .../ViewModels/IPScannerViewModel.cs | 36 +++++++++++++------ .../NETworkManager/Views/IPScannerView.xaml | 30 +++++++++++----- 3 files changed, 49 insertions(+), 19 deletions(-) diff --git a/Source/NETworkManager/ViewModels/CredentialsViewModel.cs b/Source/NETworkManager/ViewModels/CredentialsViewModel.cs index 36e28c1c4c..8a7a8698a9 100644 --- a/Source/NETworkManager/ViewModels/CredentialsViewModel.cs +++ b/Source/NETworkManager/ViewModels/CredentialsViewModel.cs @@ -358,7 +358,7 @@ public async void Add() }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); - }, false); + }); customDialog.Content = new CredentialDialog { diff --git a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs index d5184a3130..cd4b605eeb 100644 --- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs @@ -4,6 +4,7 @@ using System.Windows.Input; using System.Windows; using System; +using System.Collections; using System.Threading; using System.Collections.Generic; using NETworkManager.Models.Network; @@ -12,6 +13,7 @@ using System.ComponentModel; using System.Windows.Data; using System.Linq; +using System.Management.Instrumentation; using NETworkManager.Utilities; using Dragablz; using MahApps.Metro.Controls.Dialogs; @@ -82,16 +84,16 @@ public bool CancelScan } } - private ObservableCollection _ipScanResult = new ObservableCollection(); - public ObservableCollection IPScanResult + private ObservableCollection _ipScanResults = new ObservableCollection(); + public ObservableCollection IPScanResults { - get => _ipScanResult; + get => _ipScanResults; set { - if (value != null && value == _ipScanResult) + if (value != null && value == _ipScanResults) return; - _ipScanResult = value; + _ipScanResults = value; } } @@ -111,6 +113,20 @@ public IPScannerHostInfo SelectedIPScanResult } } + private IList _selectedIPScanResults = new ArrayList(); + public IList SelectedIPScanResults + { + get => _selectedIPScanResults; + set + { + if (Equals(value, _selectedIPScanResults)) + return; + + _selectedIPScanResults = value; + OnPropertyChanged(); + } + } + public bool ResolveHostname => SettingsManager.Current.IPScanner_ResolveHostname; public bool ResolveMACAddress => SettingsManager.Current.IPScanner_ResolveMACAddress; @@ -275,7 +291,7 @@ public IPScannerViewModel(IDialogCoordinator instance, int tabId, string ipRange IPRangeHistoryView = CollectionViewSource.GetDefaultView(SettingsManager.Current.IPScanner_IPRangeHistory); // Result view - IPScanResultView = CollectionViewSource.GetDefaultView(IPScanResult); + IPScanResultView = CollectionViewSource.GetDefaultView(IPScanResults); IPScanResultView.SortDescriptions.Add(new SortDescription( nameof(IPScannerHostInfo.PingInfo) + "." + nameof(PingInfo.IPAddressInt32), ListSortDirection.Ascending)); @@ -458,7 +474,7 @@ private async void ExportAction() { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); - ExportManager.Export(instance.FilePath, instance.FileType, IPScanResult); + ExportManager.Export(instance.FilePath, instance.FileType, instance.ExportAll ? IPScanResults : new ObservableCollection(SelectedIPScanResults.Cast().ToArray())); }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }); @@ -486,7 +502,7 @@ private async void StartScan() _dispatcherTimer.Start(); EndTime = null; - IPScanResult.Clear(); + IPScanResults.Clear(); HostsFound = 0; // Change the tab title (not nice, but it works) @@ -626,8 +642,8 @@ private void IpScanner_HostFound(object sender, IPScannerHostFoundArgs e) Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate { - lock (IPScanResult) - IPScanResult.Add(ipScannerHostInfo); + lock (IPScanResults) + IPScanResults.Add(ipScannerHostInfo); })); HostsFound++; diff --git a/Source/NETworkManager/Views/IPScannerView.xaml b/Source/NETworkManager/Views/IPScannerView.xaml index d80b30eb4a..e0b5e61a41 100644 --- a/Source/NETworkManager/Views/IPScannerView.xaml +++ b/Source/NETworkManager/Views/IPScannerView.xaml @@ -13,6 +13,7 @@ xmlns:network="clr-namespace:NETworkManager.Models.Network" xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro" + xmlns:controls="clr-namespace:NETworkManager.Controls" dialogs:DialogParticipation.Register="{Binding}" Loaded="UserControl_Loaded" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:IPScannerViewModel}"> @@ -163,8 +164,8 @@ - - + + - - + + + @@ -382,8 +396,8 @@ - - + + From 1bcec02b9f6c4615fe2a2618d4f9f9a84a884cfe Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sat, 3 Nov 2018 21:52:22 +0100 Subject: [PATCH 12/58] Update --- .../Converters/PingTimeToStringConverter.cs | 11 +---- .../Models/Export/ExportManager.cs | 44 +++++++++++++++---- Source/NETworkManager/Models/Network/Ping.cs | 13 +++++- .../Models/Settings/SettingsInfo.cs | 37 +++++++++++++++- .../ViewModels/ExportViewModel.cs | 34 ++++++++++++-- .../ViewModels/IPScannerViewModel.cs | 13 +++--- 6 files changed, 121 insertions(+), 31 deletions(-) diff --git a/Source/NETworkManager/Converters/PingTimeToStringConverter.cs b/Source/NETworkManager/Converters/PingTimeToStringConverter.cs index 04ab7323fe..1d91a48295 100644 --- a/Source/NETworkManager/Converters/PingTimeToStringConverter.cs +++ b/Source/NETworkManager/Converters/PingTimeToStringConverter.cs @@ -9,16 +9,7 @@ public sealed class PingTimeToStringConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - var status = (IPStatus)values[0]; - - - if (status != IPStatus.Success && status != IPStatus.TtlExpired) - return "-/-"; - - long.TryParse(values[1].ToString(), out var time); - - return time == 0 ? "<1 ms" : $"{time} ms"; - + return Models.Network.Ping.TimeToString((IPStatus)values[0], (long)values[1]); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index 507fd1c110..2ec3d9d86f 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -1,6 +1,9 @@ using System; using System.Collections.ObjectModel; +using System.Linq; using System.Text; +using System.Windows.Markup; +using System.Xml.Linq; using NETworkManager.Models.Network; using NETworkManager.Models.Settings; using NETworkManager.Resources.Localization; @@ -12,35 +15,60 @@ public static class ExportManager #region Methods public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) { - var text = string.Empty; switch (fileType) { case ExportFileType.CSV: - text = CreateCSVData(collection); + CreateCSV(collection, filePath); break; case ExportFileType.XML: - + CreateXMLData(collection, filePath); break; default: throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); } - System.IO.File.WriteAllText(filePath, text); } // IPScannerHostInfo - public static string CreateCSVData(ObservableCollection collection) + private static void CreateCSV(ObservableCollection collection, string filePath) { var stringBuilder = new StringBuilder(); - stringBuilder.AppendLine($"{Strings.IPAddress},{Strings.Hostname},{Strings.MACAddress},{Strings.Vendor},{Strings.Bytes},{Strings.Time},{Strings.TTL},{Strings.Status}"); + stringBuilder.AppendLine($"{nameof(PingInfo.IPAddress)},{nameof(IPScannerHostInfo.Hostname)},{nameof(IPScannerHostInfo.MACAddress)},{nameof(IPScannerHostInfo.Vendor)},{nameof(PingInfo.Bytes)},{nameof(PingInfo.Time)},{nameof(PingInfo.TTL)},{nameof(PingInfo.Status)}"); foreach (var info in collection) - stringBuilder.AppendLine($"{info.PingInfo.IPAddress},{info.Hostname},{info.MACAddress},{info.Vendor},{info.PingInfo.Bytes},{info.PingInfo.Time},{info.PingInfo.TTL},{LocalizationManager.TranslateIPStatus(info.PingInfo.Status)}"); + stringBuilder.AppendLine($"{info.PingInfo.IPAddress},{info.Hostname},{info.MACAddress},{info.Vendor},{info.PingInfo.Bytes},{Ping.TimeToString(info.PingInfo.Status, info.PingInfo.Time, true)},{info.PingInfo.TTL},{info.PingInfo.Status}"); + + System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); + } + + public static void CreateXMLData(ObservableCollection collection, string filePath) + { + var document = new XDocument( + new XDeclaration("1.0", "utf-8", "yes"), - return stringBuilder.ToString(); + new XElement(ApplicationViewManager.Name.IPScanner.ToString(), + new XElement(nameof(IPScannerHostInfo) + "s", + + from info in collection + select + new XElement(nameof(IPScannerHostInfo), new XAttribute(nameof(info.PingInfo.IPAddress), info.PingInfo.IPAddress), + new XElement(nameof(IPScannerHostInfo.Hostname), info.Hostname), + new XElement(nameof(IPScannerHostInfo.MACAddress), info.MACAddress), + new XElement(nameof(IPScannerHostInfo.Vendor), info.Vendor), + new XElement(nameof(PingInfo.Bytes), info.PingInfo.Bytes), + new XElement(nameof(PingInfo.Time), Ping.TimeToString(info.PingInfo.Status, info.PingInfo.Time, true)), + new XElement(nameof(PingInfo.TTL), info.PingInfo.TTL), + new XElement(nameof(PingInfo.Status), info.PingInfo.Status))))); + + document.Save(filePath); + } + + public static string CreateXAttributeString(string s) + { + return s.Replace(" ", "").Replace("-", ""); } public static string GetFileExtensionAsString(ExportFileType fileExtension) diff --git a/Source/NETworkManager/Models/Network/Ping.cs b/Source/NETworkManager/Models/Network/Ping.cs index 2fe46dc041..008586d9cb 100644 --- a/Source/NETworkManager/Models/Network/Ping.cs +++ b/Source/NETworkManager/Models/Network/Ping.cs @@ -85,7 +85,7 @@ public void SendAsync(IPAddress ipAddress, PingOptions pingOptions, Cancellation if (pingReply != null && pingReply.Address == null) OnPingReceived(new PingReceivedArgs(timestamp, ipAddress, hostname, pingReply.Status)); else if (pingReply != null) - OnPingReceived(new PingReceivedArgs(timestamp, pingReply.Address, hostname,pingReply.Status)); + OnPingReceived(new PingReceivedArgs(timestamp, pingReply.Address, hostname, pingReply.Status)); } else { @@ -128,6 +128,17 @@ public void SendAsync(IPAddress ipAddress, PingOptions pingOptions, Cancellation OnPingCompleted(); }, cancellationToken); } + + // Param: disableSpecialChar --> ExportManager --> "<" this char cannot be displayed in xml + public static string TimeToString(IPStatus status, long time, bool disableSpecialChar = false) + { + if (status != IPStatus.Success && status != IPStatus.TtlExpired) + return "-/-"; + + long.TryParse(time.ToString(), out var t); + + return disableSpecialChar ? $"{t} ms" : t == 0 ? "<1 ms" : $"{t} ms"; + } } } diff --git a/Source/NETworkManager/Models/Settings/SettingsInfo.cs b/Source/NETworkManager/Models/Settings/SettingsInfo.cs index 61cdac2086..7a61530334 100644 --- a/Source/NETworkManager/Models/Settings/SettingsInfo.cs +++ b/Source/NETworkManager/Models/Settings/SettingsInfo.cs @@ -8,6 +8,7 @@ using System.Xml.Serialization; using Heijden.DNS; using Lextm.SharpSnmpLib.Messaging; +using NETworkManager.Models.Export; using NETworkManager.Models.Network; using NETworkManager.Utilities; using static NETworkManager.Models.Network.SNMP; @@ -675,6 +676,40 @@ public bool IPScanner_ShowStatistics SettingsChanged = true; } } + + private string _ipScanner_ExportFilePath; + public string IPScanner_ExportFilePath + { + get => _ipScanner_ExportFilePath; + set + { + if (value == _ipScanner_ExportFilePath) + return; + + _ipScanner_ExportFilePath = value; + + OnPropertyChanged(); + + SettingsChanged = true; + } + } + + private ExportManager.ExportFileType _ipScanner_ExportFileType = ExportManager.ExportFileType.CSV; + public ExportManager.ExportFileType IPScanner_ExportFileType + { + get => _ipScanner_ExportFileType; + set + { + if(value == _ipScanner_ExportFileType) + return; + + _ipScanner_ExportFileType = value; + + OnPropertyChanged(); + + SettingsChanged = true; + } + } #endregion #region Port Scanner @@ -2678,7 +2713,7 @@ public SettingsInfo() PuTTY_ProfileHistory.CollectionChanged += CollectionChanged; // TightVNC - TightVNC_HostHistory.CollectionChanged += CollectionChanged; + TightVNC_HostHistory.CollectionChanged += CollectionChanged; // SNMP SNMP_HostHistory.CollectionChanged += CollectionChanged; diff --git a/Source/NETworkManager/ViewModels/ExportViewModel.cs b/Source/NETworkManager/ViewModels/ExportViewModel.cs index b1734032ac..cad24a5ec6 100644 --- a/Source/NETworkManager/ViewModels/ExportViewModel.cs +++ b/Source/NETworkManager/ViewModels/ExportViewModel.cs @@ -41,7 +41,7 @@ public bool ExportSelected } } - public ExportManager.ExportFileType FileType; + public ExportManager.ExportFileType FileType { get; set; } private bool _useCSV; public bool UseCSV @@ -55,6 +55,8 @@ public bool UseCSV if (value) FileType = ExportManager.ExportFileType.CSV; + ChangeFilePathExtension(FileType); + _useCSV = value; OnPropertyChanged(); } @@ -72,6 +74,8 @@ public bool UseXML if (value) FileType = ExportManager.ExportFileType.XML; + ChangeFilePathExtension(FileType); + _useXML = value; OnPropertyChanged(); } @@ -91,14 +95,25 @@ public string FilePath } } - public ExportViewModel(Action deleteCommand, Action cancelHandler) + public ExportViewModel(Action deleteCommand, Action cancelHandler, ExportManager.ExportFileType fileType = ExportManager.ExportFileType.CSV, string filePath = "") { ExportCommand = new RelayCommand(p => deleteCommand(this)); CancelCommand = new RelayCommand(p => cancelHandler(this)); // Default ExportAll = true; - UseCSV = true; + + switch (fileType) + { + case ExportManager.ExportFileType.CSV: + UseCSV = true; + break; + case ExportManager.ExportFileType.XML: + UseXML = true; + break; + } + + FilePath = filePath; } public ICommand BrowseFileCommand @@ -119,5 +134,18 @@ private void BrowseFileAction() FilePath = saveFileDialog.FileName; } } + + private void ChangeFilePathExtension(ExportManager.ExportFileType fileType) + { + if (fileType == ExportManager.ExportFileType.CSV) + { + + } + + if (fileType == ExportManager.ExportFileType.XML) + { + + } + } } } diff --git a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs index cd4b605eeb..80a9069b17 100644 --- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs @@ -13,7 +13,6 @@ using System.ComponentModel; using System.Windows.Data; using System.Linq; -using System.Management.Instrumentation; using NETworkManager.Utilities; using Dragablz; using MahApps.Metro.Controls.Dialogs; @@ -349,10 +348,7 @@ private void RedirectToApplicationAction(object name) if (!Enum.TryParse(appName, out ApplicationViewManager.Name app)) return; - var host = !string.IsNullOrEmpty(SelectedIPScanResult.Hostname) - ? SelectedIPScanResult.Hostname - : SelectedIPScanResult - .PingInfo.IPAddress.ToString(); + var host = !string.IsNullOrEmpty(SelectedIPScanResult.Hostname)? SelectedIPScanResult.Hostname: SelectedIPScanResult.PingInfo.IPAddress.ToString(); EventSystem.RedirectToApplication(app, host); } @@ -476,7 +472,9 @@ private async void ExportAction() ExportManager.Export(instance.FilePath, instance.FileType, instance.ExportAll ? IPScanResults : new ObservableCollection(SelectedIPScanResults.Cast().ToArray())); - }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }); + SettingsManager.Current.IPScanner_ExportFileType = instance.FileType; + SettingsManager.Current.IPScanner_ExportFilePath = instance.FilePath; + }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }, SettingsManager.Current.IPScanner_ExportFileType, SettingsManager.Current.IPScanner_ExportFilePath); customDialog.Content = new ExportDialog { @@ -661,8 +659,7 @@ private void IpScanner_ProgressChanged(object sender, ProgressChangedArgs e) private void IPScanner_DnsResolveFailed(AggregateException e) { - StatusMessage = - $"{Resources.Localization.Strings.TheFollowingHostnamesCouldNotBeResolved} {string.Join(", ", e.Flatten().InnerExceptions.Select(x => x.Message))}"; + StatusMessage =$"{Resources.Localization.Strings.TheFollowingHostnamesCouldNotBeResolved} {string.Join(", ", e.Flatten().InnerExceptions.Select(x => x.Message))}"; DisplayStatusMessage = true; ScanFinished(); From 48026ce13f3c960d825dadee69f08be9cfad5182 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sun, 4 Nov 2018 17:23:25 +0100 Subject: [PATCH 13/58] Change file extension when changing export format --- .../Models/Export/ExportManager.cs | 4 --- .../ViewModels/ExportViewModel.cs | 33 +++++++++++-------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index 2ec3d9d86f..93a6fe84c8 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -2,11 +2,8 @@ using System.Collections.ObjectModel; using System.Linq; using System.Text; -using System.Windows.Markup; using System.Xml.Linq; using NETworkManager.Models.Network; -using NETworkManager.Models.Settings; -using NETworkManager.Resources.Localization; namespace NETworkManager.Models.Export { @@ -28,7 +25,6 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo default: throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); } - } // IPScannerHostInfo diff --git a/Source/NETworkManager/ViewModels/ExportViewModel.cs b/Source/NETworkManager/ViewModels/ExportViewModel.cs index cad24a5ec6..5c39a5c539 100644 --- a/Source/NETworkManager/ViewModels/ExportViewModel.cs +++ b/Source/NETworkManager/ViewModels/ExportViewModel.cs @@ -1,5 +1,6 @@ using NETworkManager.Utilities; using System; +using System.IO; using System.Windows.Forms; using System.Windows.Input; using NETworkManager.Models.Export; @@ -13,13 +14,13 @@ public class ExportViewModel : ViewModelBase public ICommand CancelCommand { get; } - public bool _exportAll; + private bool _exportAll; public bool ExportAll { get => _exportAll; set { - if(value==_exportAll) + if (value == _exportAll) return; _exportAll = value; @@ -27,7 +28,7 @@ public bool ExportAll } } - public bool _exportSelected; + private bool _exportSelected; public bool ExportSelected { get => _exportSelected; @@ -53,9 +54,10 @@ public bool UseCSV return; if (value) + { FileType = ExportManager.ExportFileType.CSV; - - ChangeFilePathExtension(FileType); + ChangeFilePathExtension(FileType); + } _useCSV = value; OnPropertyChanged(); @@ -72,9 +74,10 @@ public bool UseXML return; if (value) + { FileType = ExportManager.ExportFileType.XML; - - ChangeFilePathExtension(FileType); + ChangeFilePathExtension(FileType); + } _useXML = value; OnPropertyChanged(); @@ -137,15 +140,17 @@ private void BrowseFileAction() private void ChangeFilePathExtension(ExportManager.ExportFileType fileType) { - if (fileType == ExportManager.ExportFileType.CSV) - { - - } + if (string.IsNullOrEmpty(FilePath)) + return; - if (fileType == ExportManager.ExportFileType.XML) - { + var extension = Path.GetExtension(FilePath).Replace(".", ""); - } + if (extension.Equals(ExportManager.GetFileExtensionAsString(fileType), StringComparison.CurrentCultureIgnoreCase)) + return; + + FilePath = FilePath.Substring(0, FilePath.Length - extension.Length); + + FilePath += ExportManager.GetFileExtensionAsString(fileType).ToLower(); } } } From 6650c429fb3a6b96138426cbc46377d804900a80 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sun, 4 Nov 2018 23:44:41 +0100 Subject: [PATCH 14/58] PortScanner export added --- .../Models/Export/ExportManager.cs | 73 +++++++++++-- ...annerHostFoundArgs.cs => HostFoundArgs.cs} | 6 +- .../{IPScannerHostInfo.cs => HostInfo.cs} | 10 +- .../Models/Network/IPScanner.cs | 6 +- .../NETworkManager/Models/Network/PortInfo.cs | 11 +- .../Models/Network/PortScannedArgs.cs | 8 +- .../Models/Network/PortScanner.cs | 6 +- .../Models/Settings/SettingsInfo.cs | 34 ++++++ Source/NETworkManager/NETworkManager.csproj | 7 +- .../NETworkManager/Properties/AssemblyInfo.cs | 4 +- .../Localization/Strings.Designer.cs | 9 ++ .../Resources/Localization/Strings.resx | 3 + .../ViewModels/IPScannerViewModel.cs | 35 +++--- .../ViewModels/PortScannerViewModel.cs | 101 ++++++++++++++---- .../NETworkManager/Views/IPScannerView.xaml | 21 ++-- .../NETworkManager/Views/PortScannerView.xaml | 63 +++++++---- .../Views/PortScannerView.xaml.cs | 9 +- Source/NETworkManager/packages.config | 1 + 18 files changed, 305 insertions(+), 102 deletions(-) rename Source/NETworkManager/Models/Network/{IPScannerHostFoundArgs.cs => HostFoundArgs.cs} (68%) rename Source/NETworkManager/Models/Network/{IPScannerHostInfo.cs => HostInfo.cs} (58%) diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index 93a6fe84c8..0246a3318c 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Xml.Linq; +using NETworkManager.Models.Lookup; using NETworkManager.Models.Network; namespace NETworkManager.Models.Export @@ -10,7 +12,7 @@ namespace NETworkManager.Models.Export public static class ExportManager { #region Methods - public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) + public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) { switch (fileType) { @@ -27,12 +29,28 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo } } - // IPScannerHostInfo - private static void CreateCSV(ObservableCollection collection, string filePath) + public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) + { + switch (fileType) + { + case ExportFileType.CSV: + CreateCSV(collection, filePath); + + break; + case ExportFileType.XML: + CreateXMLData(collection, filePath); + + break; + default: + throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); + } + } + + private static void CreateCSV(IEnumerable collection, string filePath) { var stringBuilder = new StringBuilder(); - stringBuilder.AppendLine($"{nameof(PingInfo.IPAddress)},{nameof(IPScannerHostInfo.Hostname)},{nameof(IPScannerHostInfo.MACAddress)},{nameof(IPScannerHostInfo.Vendor)},{nameof(PingInfo.Bytes)},{nameof(PingInfo.Time)},{nameof(PingInfo.TTL)},{nameof(PingInfo.Status)}"); + stringBuilder.AppendLine($"{nameof(PingInfo.IPAddress)},{nameof(HostInfo.Hostname)},{nameof(HostInfo.MACAddress)},{nameof(HostInfo.Vendor)},{nameof(PingInfo.Bytes)},{nameof(PingInfo.Time)},{nameof(PingInfo.TTL)},{nameof(PingInfo.Status)}"); foreach (var info in collection) stringBuilder.AppendLine($"{info.PingInfo.IPAddress},{info.Hostname},{info.MACAddress},{info.Vendor},{info.PingInfo.Bytes},{Ping.TimeToString(info.PingInfo.Status, info.PingInfo.Time, true)},{info.PingInfo.TTL},{info.PingInfo.Status}"); @@ -40,20 +58,33 @@ private static void CreateCSV(ObservableCollection collection System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); } - public static void CreateXMLData(ObservableCollection collection, string filePath) + private static void CreateCSV(IEnumerable collection, string filePath) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.AppendLine($"{nameof(PortInfo.IPAddress)},{nameof(PortInfo.Hostname)},{nameof(PortInfo.Port)},{nameof(PortLookupInfo.Protocol)},{nameof(PortLookupInfo.Service)},{nameof(PortLookupInfo.Description)},{nameof(PortInfo.Status)}"); + + foreach (var info in collection) + stringBuilder.AppendLine($"{info.IPAddress},{info.Hostname},{info.Port},{info.LookupInfo.Protocol},{info.LookupInfo.Service},{info.LookupInfo.Description},{info.Status}"); + + System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); + } + + public static void CreateXMLData(IEnumerable collection, string filePath) { var document = new XDocument( new XDeclaration("1.0", "utf-8", "yes"), new XElement(ApplicationViewManager.Name.IPScanner.ToString(), - new XElement(nameof(IPScannerHostInfo) + "s", + new XElement(nameof(HostInfo) + "s", from info in collection select - new XElement(nameof(IPScannerHostInfo), new XAttribute(nameof(info.PingInfo.IPAddress), info.PingInfo.IPAddress), - new XElement(nameof(IPScannerHostInfo.Hostname), info.Hostname), - new XElement(nameof(IPScannerHostInfo.MACAddress), info.MACAddress), - new XElement(nameof(IPScannerHostInfo.Vendor), info.Vendor), + new XElement(nameof(HostInfo), + new XElement(nameof(PingInfo.IPAddress), info.PingInfo.IPAddress), + new XElement(nameof(HostInfo.Hostname), info.Hostname), + new XElement(nameof(HostInfo.MACAddress), info.MACAddress), + new XElement(nameof(HostInfo.Vendor), info.Vendor), new XElement(nameof(PingInfo.Bytes), info.PingInfo.Bytes), new XElement(nameof(PingInfo.Time), Ping.TimeToString(info.PingInfo.Status, info.PingInfo.Time, true)), new XElement(nameof(PingInfo.TTL), info.PingInfo.TTL), @@ -61,6 +92,28 @@ from info in collection document.Save(filePath); } + + public static void CreateXMLData(IEnumerable collection, string filePath) + { + var document = new XDocument( + new XDeclaration("1.0", "utf-8", "yes"), + + new XElement(ApplicationViewManager.Name.PortScanner.ToString(), + new XElement(nameof(PortInfo) + "s", + + from info in collection + select + new XElement(nameof(PortInfo), + new XElement(nameof(info.IPAddress), info.IPAddress), + new XElement(nameof(PortInfo.Hostname), info.Hostname), + new XElement(nameof(PortInfo.Port), info.Port), + new XElement(nameof(PortLookupInfo.Protocol), info.LookupInfo.Protocol), + new XElement(nameof(PortLookupInfo.Service), info.LookupInfo.Service), + new XElement(nameof(PortLookupInfo.Description), info.LookupInfo.Description), + new XElement(nameof(PortInfo.Status), info.Status))))); + + document.Save(filePath); + } public static string CreateXAttributeString(string s) { diff --git a/Source/NETworkManager/Models/Network/IPScannerHostFoundArgs.cs b/Source/NETworkManager/Models/Network/HostFoundArgs.cs similarity index 68% rename from Source/NETworkManager/Models/Network/IPScannerHostFoundArgs.cs rename to Source/NETworkManager/Models/Network/HostFoundArgs.cs index 5d4d20fc36..acb2277a08 100644 --- a/Source/NETworkManager/Models/Network/IPScannerHostFoundArgs.cs +++ b/Source/NETworkManager/Models/Network/HostFoundArgs.cs @@ -2,19 +2,19 @@ namespace NETworkManager.Models.Network { - public class IPScannerHostFoundArgs : System.EventArgs + public class HostFoundArgs : System.EventArgs { public PingInfo PingInfo { get; set; } public string Hostname { get; set; } public PhysicalAddress MACAddress { get; set; } public string Vendor { get; set; } - public IPScannerHostFoundArgs() + public HostFoundArgs() { } - public IPScannerHostFoundArgs(PingInfo pingInfo, string hostname, PhysicalAddress macAddress, string vendor) + public HostFoundArgs(PingInfo pingInfo, string hostname, PhysicalAddress macAddress, string vendor) { PingInfo = pingInfo; Hostname = hostname; diff --git a/Source/NETworkManager/Models/Network/IPScannerHostInfo.cs b/Source/NETworkManager/Models/Network/HostInfo.cs similarity index 58% rename from Source/NETworkManager/Models/Network/IPScannerHostInfo.cs rename to Source/NETworkManager/Models/Network/HostInfo.cs index 3bc8ec11cd..169d4e88d0 100644 --- a/Source/NETworkManager/Models/Network/IPScannerHostInfo.cs +++ b/Source/NETworkManager/Models/Network/HostInfo.cs @@ -2,19 +2,19 @@ namespace NETworkManager.Models.Network { - public class IPScannerHostInfo + public class HostInfo { public PingInfo PingInfo { get; set; } public string Hostname { get; set; } public PhysicalAddress MACAddress { get; set; } public string Vendor { get; set; } - public IPScannerHostInfo() + public HostInfo() { } - public IPScannerHostInfo(PingInfo pingInfo, string hostname, PhysicalAddress macAddress, string vendor) + public HostInfo(PingInfo pingInfo, string hostname, PhysicalAddress macAddress, string vendor) { PingInfo = pingInfo; Hostname = hostname; @@ -22,9 +22,9 @@ public IPScannerHostInfo(PingInfo pingInfo, string hostname, PhysicalAddress mac Vendor = vendor; } - public static IPScannerHostInfo Parse(IPScannerHostFoundArgs e) + public static HostInfo Parse(HostFoundArgs e) { - return new IPScannerHostInfo(e.PingInfo, e.Hostname, e.MACAddress, e.Vendor); + return new HostInfo(e.PingInfo, e.Hostname, e.MACAddress, e.Vendor); } } } diff --git a/Source/NETworkManager/Models/Network/IPScanner.cs b/Source/NETworkManager/Models/Network/IPScanner.cs index 0f6bafdfe1..f80998fd5f 100644 --- a/Source/NETworkManager/Models/Network/IPScanner.cs +++ b/Source/NETworkManager/Models/Network/IPScanner.cs @@ -14,9 +14,9 @@ public class IPScanner #endregion #region Events - public event EventHandler HostFound; + public event EventHandler HostFound; - protected virtual void OnHostFound(IPScannerHostFoundArgs e) + protected virtual void OnHostFound(HostFoundArgs e) { HostFound?.Invoke(this, e); } @@ -152,7 +152,7 @@ public void ScanAsync(IPAddress[] ipAddresses, IPScannerOptions ipScannerOptions } } - OnHostFound(new IPScannerHostFoundArgs(pingInfo, hostname, macAddress, vendor)); + OnHostFound(new HostFoundArgs(pingInfo, hostname, macAddress, vendor)); } IncreaseProcess(); diff --git a/Source/NETworkManager/Models/Network/PortInfo.cs b/Source/NETworkManager/Models/Network/PortInfo.cs index 988c066152..52bedcaf91 100644 --- a/Source/NETworkManager/Models/Network/PortInfo.cs +++ b/Source/NETworkManager/Models/Network/PortInfo.cs @@ -1,12 +1,12 @@ using NETworkManager.Models.Lookup; -using System; using System.Net; namespace NETworkManager.Models.Network { public class PortInfo { - public Tuple Host { get; set; } + public IPAddress IPAddress { get; set; } + public string Hostname { get; set; } public int Port { get; set; } public PortLookupInfo LookupInfo { get; set; } public PortStatus Status { get; set; } @@ -16,9 +16,10 @@ public PortInfo() } - public PortInfo(Tuple host, int port, PortLookupInfo lookupInfo, PortStatus status) + public PortInfo(IPAddress ipAddress, string hostname, int port, PortLookupInfo lookupInfo, PortStatus status) { - Host = host; + IPAddress = ipAddress; + Hostname = hostname; Port = port; LookupInfo = lookupInfo; Status = status; @@ -26,7 +27,7 @@ public PortInfo(Tuple host, int port, PortLookupInfo lookupIn public static PortInfo Parse(PortScannedArgs e) { - return new PortInfo(e.Host, e.Port, e.LookupInfo, e.Status); + return new PortInfo(e.IPAddress, e.Hostname, e.Port, e.LookupInfo, e.Status); } public enum PortStatus diff --git a/Source/NETworkManager/Models/Network/PortScannedArgs.cs b/Source/NETworkManager/Models/Network/PortScannedArgs.cs index b743d8f4e5..48812dedb2 100644 --- a/Source/NETworkManager/Models/Network/PortScannedArgs.cs +++ b/Source/NETworkManager/Models/Network/PortScannedArgs.cs @@ -6,7 +6,8 @@ namespace NETworkManager.Models.Network { public class PortScannedArgs : EventArgs { - public Tuple Host { get; set; } + public IPAddress IPAddress { get; set; } + public string Hostname { get; set; } public int Port { get; set; } public PortLookupInfo LookupInfo { get; set; } public PortInfo.PortStatus Status { get; set; } @@ -16,9 +17,10 @@ public PortScannedArgs() } - public PortScannedArgs(Tuple host, int port, PortLookupInfo lookupInfo, PortInfo.PortStatus status) + public PortScannedArgs(IPAddress ipAddres, string hostname, int port, PortLookupInfo lookupInfo, PortInfo.PortStatus status) { - Host = host; + IPAddress = ipAddres; + Hostname = hostname; Port = port; LookupInfo = lookupInfo; Status = status; diff --git a/Source/NETworkManager/Models/Network/PortScanner.cs b/Source/NETworkManager/Models/Network/PortScanner.cs index 04dc4b5d10..46578c044b 100644 --- a/Source/NETworkManager/Models/Network/PortScanner.cs +++ b/Source/NETworkManager/Models/Network/PortScanner.cs @@ -81,18 +81,18 @@ public void ScanAsync(List> hostData, int[] ports, Port { tcpClient.EndConnect(tcpClientConnection); - OnPortScanned(new PortScannedArgs(host, port, PortLookup.Lookup(port).FirstOrDefault(x => x.Protocol == PortLookup.Protocol.Tcp), PortInfo.PortStatus.Open)); + OnPortScanned(new PortScannedArgs(host.Item1,host.Item2, port, PortLookup.Lookup(port).FirstOrDefault(x => x.Protocol == PortLookup.Protocol.Tcp), PortInfo.PortStatus.Open)); } catch { if (portScannerOptions.ShowClosed) - OnPortScanned(new PortScannedArgs(host, port, PortLookup.Lookup(port).FirstOrDefault(x => x.Protocol == PortLookup.Protocol.Tcp), PortInfo.PortStatus.Closed)); + OnPortScanned(new PortScannedArgs(host.Item1, host.Item2, port, PortLookup.Lookup(port).FirstOrDefault(x => x.Protocol == PortLookup.Protocol.Tcp), PortInfo.PortStatus.Closed)); } } else { if (portScannerOptions.ShowClosed) - OnPortScanned(new PortScannedArgs(host, port, PortLookup.Lookup(port).FirstOrDefault(x => x.Protocol == PortLookup.Protocol.Tcp), PortInfo.PortStatus.Closed)); + OnPortScanned(new PortScannedArgs(host.Item1, host.Item2, port, PortLookup.Lookup(port).FirstOrDefault(x => x.Protocol == PortLookup.Protocol.Tcp), PortInfo.PortStatus.Closed)); } } diff --git a/Source/NETworkManager/Models/Settings/SettingsInfo.cs b/Source/NETworkManager/Models/Settings/SettingsInfo.cs index 7a61530334..43957909e3 100644 --- a/Source/NETworkManager/Models/Settings/SettingsInfo.cs +++ b/Source/NETworkManager/Models/Settings/SettingsInfo.cs @@ -855,6 +855,40 @@ public bool PortScanner_ShowStatistics SettingsChanged = true; } } + + private string _portScanner_ExportFilePath; + public string PortScanner_ExportFilePath + { + get => _portScanner_ExportFilePath; + set + { + if (value == _portScanner_ExportFilePath) + return; + + _portScanner_ExportFilePath = value; + + OnPropertyChanged(); + + SettingsChanged = true; + } + } + + private ExportManager.ExportFileType _portScanner_ExportFileType = ExportManager.ExportFileType.CSV; + public ExportManager.ExportFileType PortScanner_ExportFileType + { + get => _portScanner_ExportFileType; + set + { + if (value == _portScanner_ExportFileType) + return; + + _portScanner_ExportFileType = value; + + OnPropertyChanged(); + + SettingsChanged = true; + } + } #endregion #region Ping diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj index 6f3791f11e..7827643b42 100644 --- a/Source/NETworkManager/NETworkManager.csproj +++ b/Source/NETworkManager/NETworkManager.csproj @@ -105,6 +105,9 @@ True obj\Debug\MSTSCLib.dll + + ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + ..\packages\Octokit.0.32.0\lib\net45\Octokit.dll @@ -995,8 +998,8 @@ - - + + diff --git a/Source/NETworkManager/Properties/AssemblyInfo.cs b/Source/NETworkManager/Properties/AssemblyInfo.cs index fcd38ffe63..c896d610de 100644 --- a/Source/NETworkManager/Properties/AssemblyInfo.cs +++ b/Source/NETworkManager/Properties/AssemblyInfo.cs @@ -49,6 +49,6 @@ // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern // übernehmen, indem Sie "*" eingeben: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.8.1.0")] -[assembly: AssemblyFileVersion("1.8.1.0")] +[assembly: AssemblyVersion("1.8.2.0")] +[assembly: AssemblyFileVersion("1.8.2.0")] diff --git a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs index 6b4ff1fea0..8fb632fdac 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs @@ -303,6 +303,15 @@ public static string Amber { } } + /// + /// Looks up a localized string similar to An error occurred while exporting the data. See error message for details:. + /// + public static string AnErrorOccurredWhileExportingTheData { + get { + return ResourceManager.GetString("AnErrorOccurredWhileExportingTheData", resourceCulture); + } + } + /// /// Looks up a localized string similar to Answers. /// diff --git a/Source/NETworkManager/Resources/Localization/Strings.resx b/Source/NETworkManager/Resources/Localization/Strings.resx index 7652bbb56c..086dbee267 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.resx +++ b/Source/NETworkManager/Resources/Localization/Strings.resx @@ -2066,4 +2066,7 @@ If you click "Move & Restart", the remaining files will be copied and the ap Selected + + An error occurred while exporting the data. See error message for details: + \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs index 80a9069b17..2295ea5d44 100644 --- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs @@ -25,7 +25,6 @@ namespace NETworkManager.ViewModels public class IPScannerViewModel : ViewModelBase { #region Variables - private readonly IDialogCoordinator _dialogCoordinator; private CancellationTokenSource _cancellationTokenSource; @@ -83,8 +82,8 @@ public bool CancelScan } } - private ObservableCollection _ipScanResults = new ObservableCollection(); - public ObservableCollection IPScanResults + private ObservableCollection _ipScanResults = new ObservableCollection(); + public ObservableCollection IPScanResults { get => _ipScanResults; set @@ -98,8 +97,8 @@ public ObservableCollection IPScanResults public ICollectionView IPScanResultView { get; } - private IPScannerHostInfo _selectedIPScanResult; - public IPScannerHostInfo SelectedIPScanResult + private HostInfo _selectedIPScanResult; + public HostInfo SelectedIPScanResult { get => _selectedIPScanResult; set @@ -292,7 +291,7 @@ public IPScannerViewModel(IDialogCoordinator instance, int tabId, string ipRange // Result view IPScanResultView = CollectionViewSource.GetDefaultView(IPScanResults); IPScanResultView.SortDescriptions.Add(new SortDescription( - nameof(IPScannerHostInfo.PingInfo) + "." + nameof(PingInfo.IPAddressInt32), + nameof(HostInfo.PingInfo) + "." + nameof(PingInfo.IPAddressInt32), ListSortDirection.Ascending)); LoadSettings(); @@ -348,7 +347,7 @@ private void RedirectToApplicationAction(object name) if (!Enum.TryParse(appName, out ApplicationViewManager.Name app)) return; - var host = !string.IsNullOrEmpty(SelectedIPScanResult.Hostname)? SelectedIPScanResult.Hostname: SelectedIPScanResult.PingInfo.IPAddress.ToString(); + var host = !string.IsNullOrEmpty(SelectedIPScanResult.Hostname) ? SelectedIPScanResult.Hostname : SelectedIPScanResult.PingInfo.IPAddress.ToString(); EventSystem.RedirectToApplication(app, host); } @@ -466,12 +465,22 @@ private async void ExportAction() Title = Resources.Localization.Strings.Export }; - var exportViewModel = new ExportViewModel(instance => + var exportViewModel = new ExportViewModel(async instance => { - _dialogCoordinator.HideMetroDialogAsync(this, customDialog); + await _dialogCoordinator.HideMetroDialogAsync(this, customDialog); - ExportManager.Export(instance.FilePath, instance.FileType, instance.ExportAll ? IPScanResults : new ObservableCollection(SelectedIPScanResults.Cast().ToArray())); + try + { + ExportManager.Export(instance.FilePath, instance.FileType, instance.ExportAll ? IPScanResults : new ObservableCollection(SelectedIPScanResults.Cast().ToArray())); + } + catch (Exception ex) + { + var settings = AppearanceManager.MetroDialog; + settings.AffirmativeButtonText = Resources.Localization.Strings.OK; + await _dialogCoordinator.ShowMessageAsync(this, Resources.Localization.Strings.Error, Resources.Localization.Strings.AnErrorOccurredWhileExportingTheData + Environment.NewLine + Environment.NewLine + ex.Message, MessageDialogStyle.Affirmative, settings); + } + SettingsManager.Current.IPScanner_ExportFileType = instance.FileType; SettingsManager.Current.IPScanner_ExportFilePath = instance.FilePath; }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }, SettingsManager.Current.IPScanner_ExportFileType, SettingsManager.Current.IPScanner_ExportFilePath); @@ -634,9 +643,9 @@ public void OnClose() #region Events - private void IpScanner_HostFound(object sender, IPScannerHostFoundArgs e) + private void IpScanner_HostFound(object sender, HostFoundArgs e) { - var ipScannerHostInfo = IPScannerHostInfo.Parse(e); + var ipScannerHostInfo = HostInfo.Parse(e); Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate { @@ -659,7 +668,7 @@ private void IpScanner_ProgressChanged(object sender, ProgressChangedArgs e) private void IPScanner_DnsResolveFailed(AggregateException e) { - StatusMessage =$"{Resources.Localization.Strings.TheFollowingHostnamesCouldNotBeResolved} {string.Join(", ", e.Flatten().InnerExceptions.Select(x => x.Message))}"; + StatusMessage = $"{Resources.Localization.Strings.TheFollowingHostnamesCouldNotBeResolved} {string.Join(", ", e.Flatten().InnerExceptions.Select(x => x.Message))}"; DisplayStatusMessage = true; ScanFinished(); diff --git a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs index e486cb9f9e..cafc2280a2 100644 --- a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs @@ -1,6 +1,7 @@ using System.Windows.Input; using System.Windows; using System; +using System.Collections; using System.Collections.ObjectModel; using NETworkManager.Models.Settings; using System.Collections.Generic; @@ -15,13 +16,18 @@ using System.Linq; using NETworkManager.Utilities; using Dragablz; +using MahApps.Metro.Controls.Dialogs; using NETworkManager.Controls; +using NETworkManager.Models.Export; +using NETworkManager.Views; namespace NETworkManager.ViewModels { public class PortScannerViewModel : ViewModelBase { #region Variables + private readonly IDialogCoordinator _dialogCoordinator; + private CancellationTokenSource _cancellationTokenSource; private readonly int _tabId; @@ -93,31 +99,45 @@ public bool CancelScan } } - private ObservableCollection _portScanResult = new ObservableCollection(); + private ObservableCollection _portScanResults = new ObservableCollection(); public ObservableCollection PortScanResult { - get => _portScanResult; + get => _portScanResults; set { - if (_portScanResult != null && value == _portScanResult) + if (_portScanResults != null && value == _portScanResults) return; - _portScanResult = value; + _portScanResults = value; } } public ICollectionView PortScanResultView { get; } - private PortInfo _selectedScanResult; - public PortInfo SelectedScanResult + private PortInfo _selectedPortScanResult; + public PortInfo SelectedPortScanResult + { + get => _selectedPortScanResult; + set + { + if (value == _selectedPortScanResult) + return; + + _selectedPortScanResult = value; + OnPropertyChanged(); + } + } + + private IList _selectedPortScanResults = new ArrayList(); + public IList SelectedPortScanResults { - get => _selectedScanResult; + get => _selectedPortScanResults; set { - if (value == _selectedScanResult) + if (Equals(value, _selectedPortScanResults)) return; - _selectedScanResult = value; + _selectedPortScanResults = value; OnPropertyChanged(); } } @@ -270,10 +290,12 @@ public string StatusMessage #endregion #region Constructor, load settings, shutdown - public PortScannerViewModel(int tabId, string host, string port) + public PortScannerViewModel(IDialogCoordinator instance, int tabId, string host, string port) { _isLoading = true; + _dialogCoordinator = instance; + _tabId = tabId; Host = host; Port = port; @@ -284,7 +306,7 @@ public PortScannerViewModel(int tabId, string host, string port) // Result view PortScanResultView = CollectionViewSource.GetDefaultView(PortScanResult); - PortScanResultView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(PortInfo.Host))); + PortScanResultView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(PortInfo.IPAddress))); LoadSettings(); @@ -339,7 +361,7 @@ public ICommand CopySelectedIPAddressCommand private void CopySelectedIPAddressAction() { - CommonMethods.SetClipboard(SelectedScanResult.Host.Item1.ToString()); + CommonMethods.SetClipboard(SelectedPortScanResult.IPAddress.ToString()); } public ICommand CopySelectedHostnameCommand @@ -349,7 +371,7 @@ public ICommand CopySelectedHostnameCommand private void CopySelectedHostnameAction() { - CommonMethods.SetClipboard(SelectedScanResult.Host.Item2); + CommonMethods.SetClipboard(SelectedPortScanResult.Hostname); } public ICommand CopySelectedPortCommand @@ -359,7 +381,7 @@ public ICommand CopySelectedPortCommand private void CopySelectedPortAction() { - CommonMethods.SetClipboard(SelectedScanResult.Port.ToString()); + CommonMethods.SetClipboard(SelectedPortScanResult.Port.ToString()); } public ICommand CopySelectedStatusCommand @@ -369,7 +391,7 @@ public ICommand CopySelectedStatusCommand private void CopySelectedStatusAction() { - CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString(SelectedScanResult.Status.ToString(), LocalizationManager.Culture)); + CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString(SelectedPortScanResult.Status.ToString(), LocalizationManager.Culture)); } public ICommand CopySelectedProtocolCommand @@ -379,7 +401,7 @@ public ICommand CopySelectedProtocolCommand private void CopySelectedProtocolAction() { - CommonMethods.SetClipboard(SelectedScanResult.LookupInfo.Protocol.ToString()); + CommonMethods.SetClipboard(SelectedPortScanResult.LookupInfo.Protocol.ToString()); } public ICommand CopySelectedServiceCommand @@ -389,7 +411,7 @@ public ICommand CopySelectedServiceCommand private void CopySelectedServiceAction() { - CommonMethods.SetClipboard(SelectedScanResult.LookupInfo.Service); + CommonMethods.SetClipboard(SelectedPortScanResult.LookupInfo.Service); } public ICommand CopySelectedDescriptionCommand @@ -399,7 +421,50 @@ public ICommand CopySelectedDescriptionCommand private void CopySelectedDescriptionAction() { - CommonMethods.SetClipboard(SelectedScanResult.LookupInfo.Description); + CommonMethods.SetClipboard(SelectedPortScanResult.LookupInfo.Description); + } + + public ICommand ExportCommand + { + get { return new RelayCommand(p => ExportAction()); } + } + + private async void ExportAction() + { + var customDialog = new CustomDialog + { + Title = Resources.Localization.Strings.Export + }; + + var exportViewModel = new ExportViewModel(async instance => + { + await _dialogCoordinator.HideMetroDialogAsync(this, customDialog); + + try + { + ExportManager.Export(instance.FilePath, instance.FileType, + instance.ExportAll + ? PortScanResult + : new ObservableCollection(SelectedPortScanResults.Cast().ToArray())); + } + catch (Exception ex) + { + var settings = AppearanceManager.MetroDialog; + settings.AffirmativeButtonText = Resources.Localization.Strings.OK; + + await _dialogCoordinator.ShowMessageAsync(this, Resources.Localization.Strings.Error, Resources.Localization.Strings.AnErrorOccurredWhileExportingTheData + Environment.NewLine + Environment.NewLine + ex.Message, MessageDialogStyle.Affirmative, settings); + } + + SettingsManager.Current.PortScanner_ExportFileType = instance.FileType; + SettingsManager.Current.PortScanner_ExportFilePath = instance.FilePath; + }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }, SettingsManager.Current.PortScanner_ExportFileType, SettingsManager.Current.PortScanner_ExportFilePath); + + customDialog.Content = new ExportDialog + { + DataContext = exportViewModel + }; + + await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); } #endregion diff --git a/Source/NETworkManager/Views/IPScannerView.xaml b/Source/NETworkManager/Views/IPScannerView.xaml index e0b5e61a41..8106e463f4 100644 --- a/Source/NETworkManager/Views/IPScannerView.xaml +++ b/Source/NETworkManager/Views/IPScannerView.xaml @@ -12,8 +12,8 @@ xmlns:viewModels="clr-namespace:NETworkManager.ViewModels" xmlns:network="clr-namespace:NETworkManager.Models.Network" xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" - xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro" xmlns:controls="clr-namespace:NETworkManager.Controls" + xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro" dialogs:DialogParticipation.Register="{Binding}" Loaded="UserControl_Loaded" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:IPScannerViewModel}"> @@ -375,27 +375,26 @@ - - + - + - + - - + + - - + + - - + + diff --git a/Source/NETworkManager/Views/PortScannerView.xaml b/Source/NETworkManager/Views/PortScannerView.xaml index 2ac491d3e9..72f54cb716 100644 --- a/Source/NETworkManager/Views/PortScannerView.xaml +++ b/Source/NETworkManager/Views/PortScannerView.xaml @@ -7,10 +7,13 @@ xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks" xmlns:validators="clr-namespace:NETworkManager.Validators" xmlns:converters="clr-namespace:NETworkManager.Converters" - xmlns:controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro" + xmlns:mahappsControls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro" xmlns:viewModels="clr-namespace:NETworkManager.ViewModels" xmlns:network="clr-namespace:NETworkManager.Models.Network" xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" + xmlns:controls="clr-namespace:NETworkManager.Controls" + xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro" + dialogs:DialogParticipation.Register="{Binding}" Loaded="UserControl_Loaded" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:PortScannerViewModel}"> @@ -136,9 +139,9 @@ - - - - - + + - - - - + + + + - - + + - - + + @@ -341,5 +365,4 @@ - - + \ No newline at end of file diff --git a/Source/NETworkManager/Views/PortScannerView.xaml.cs b/Source/NETworkManager/Views/PortScannerView.xaml.cs index cc18bf14f7..7036347ba4 100644 --- a/Source/NETworkManager/Views/PortScannerView.xaml.cs +++ b/Source/NETworkManager/Views/PortScannerView.xaml.cs @@ -1,17 +1,18 @@ using NETworkManager.ViewModels; using System.Windows.Controls; +using MahApps.Metro.Controls.Dialogs; namespace NETworkManager.Views { - public partial class PortScannerView - { - private readonly PortScannerViewModel _viewModel; + public partial class PortScannerView + { + private readonly PortScannerViewModel _viewModel; public PortScannerView(int tabId, string host = null, string ports = null) { InitializeComponent(); - _viewModel = new PortScannerViewModel(tabId, host, ports); + _viewModel = new PortScannerViewModel(DialogCoordinator.Instance, tabId, host, ports); DataContext = _viewModel; diff --git a/Source/NETworkManager/packages.config b/Source/NETworkManager/packages.config index 81a288c50e..f4f643930a 100644 --- a/Source/NETworkManager/packages.config +++ b/Source/NETworkManager/packages.config @@ -7,5 +7,6 @@ + \ No newline at end of file From 56e2367190dbfa15a86727b1bc20514ec04ecfd2 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Mon, 5 Nov 2018 00:31:37 +0100 Subject: [PATCH 15/58] JSON added --- Source/NETworkManager.sln.DotSettings | 1 + .../Models/Export/ExportManager.cs | 46 +++++++++++------- .../Models/Lookup/PortLookup.cs | 3 ++ .../NETworkManager/Models/Network/PingInfo.cs | 7 +++ .../NETworkManager/Models/Network/PortInfo.cs | 6 +++ Source/NETworkManager/NETworkManager.csproj | 1 + .../Localization/Strings.Designer.cs | 9 ++++ .../Resources/Localization/Strings.resx | 3 ++ .../Utilities/JSONIPAddressConverter.cs | 48 +++++++++++++++++++ .../ViewModels/ExportViewModel.cs | 26 ++++++++++ Source/NETworkManager/Views/ExportDialog.xaml | 19 ++++++-- 11 files changed, 149 insertions(+), 20 deletions(-) create mode 100644 Source/NETworkManager/Utilities/JSONIPAddressConverter.cs diff --git a/Source/NETworkManager.sln.DotSettings b/Source/NETworkManager.sln.DotSettings index 6ba082da2c..38ce66f746 100644 --- a/Source/NETworkManager.sln.DotSettings +++ b/Source/NETworkManager.sln.DotSettings @@ -8,6 +8,7 @@ HTTP ID IP + JSON LAN MAC MD diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index 0246a3318c..f23b5a6d87 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Xml.Linq; +using Newtonsoft.Json; using NETworkManager.Models.Lookup; using NETworkManager.Models.Network; @@ -11,6 +12,11 @@ namespace NETworkManager.Models.Export { public static class ExportManager { + #region Variables + private static readonly XDeclaration DefaultXDeclaration = new XDeclaration("1.0", "utf-8", "yes"); + + #endregion + #region Methods public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) { @@ -18,11 +24,12 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo { case ExportFileType.CSV: CreateCSV(collection, filePath); - break; case ExportFileType.XML: - CreateXMLData(collection, filePath); - + CreateXML(collection, filePath); + break; + case ExportFileType.JSON: + CreateJSON(collection, filePath); break; default: throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); @@ -35,11 +42,12 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo { case ExportFileType.CSV: CreateCSV(collection, filePath); - break; case ExportFileType.XML: - CreateXMLData(collection, filePath); - + CreateXML(collection, filePath); + break; + case ExportFileType.JSON: + CreateJSON(collection, filePath); break; default: throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); @@ -70,10 +78,9 @@ private static void CreateCSV(IEnumerable collection, string filePath) System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); } - public static void CreateXMLData(IEnumerable collection, string filePath) + public static void CreateXML(IEnumerable collection, string filePath) { - var document = new XDocument( - new XDeclaration("1.0", "utf-8", "yes"), + var document = new XDocument(DefaultXDeclaration, new XElement(ApplicationViewManager.Name.IPScanner.ToString(), new XElement(nameof(HostInfo) + "s", @@ -92,11 +99,10 @@ from info in collection document.Save(filePath); } - - public static void CreateXMLData(IEnumerable collection, string filePath) + + public static void CreateXML(IEnumerable collection, string filePath) { - var document = new XDocument( - new XDeclaration("1.0", "utf-8", "yes"), + var document = new XDocument(DefaultXDeclaration, new XElement(ApplicationViewManager.Name.PortScanner.ToString(), new XElement(nameof(PortInfo) + "s", @@ -115,9 +121,14 @@ from info in collection document.Save(filePath); } - public static string CreateXAttributeString(string s) + public static void CreateJSON(IEnumerable collection, string filePath) + { + System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(collection,Formatting.Indented)); + } + + public static void CreateJSON(IEnumerable collection, string filePath) { - return s.Replace(" ", "").Replace("-", ""); + System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(collection, Formatting.Indented)); } public static string GetFileExtensionAsString(ExportFileType fileExtension) @@ -128,6 +139,8 @@ public static string GetFileExtensionAsString(ExportFileType fileExtension) return "CSV"; case ExportFileType.XML: return "XML"; + case ExportFileType.JSON: + return "JSON"; default: return string.Empty; } @@ -137,7 +150,8 @@ public static string GetFileExtensionAsString(ExportFileType fileExtension) public enum ExportFileType { CSV, - XML + XML, + JSON } } } diff --git a/Source/NETworkManager/Models/Lookup/PortLookup.cs b/Source/NETworkManager/Models/Lookup/PortLookup.cs index d3778356a9..a335dc6f73 100644 --- a/Source/NETworkManager/Models/Lookup/PortLookup.cs +++ b/Source/NETworkManager/Models/Lookup/PortLookup.cs @@ -5,6 +5,8 @@ using NETworkManager.Models.Settings; using System; using System.Xml; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace NETworkManager.Models.Lookup { @@ -75,6 +77,7 @@ public static List LookupByService(List portsByService) } #endregion + [JsonConverter(typeof(StringEnumConverter))] public enum Protocol { Tcp, diff --git a/Source/NETworkManager/Models/Network/PingInfo.cs b/Source/NETworkManager/Models/Network/PingInfo.cs index c852612d98..e698fd7cdb 100644 --- a/Source/NETworkManager/Models/Network/PingInfo.cs +++ b/Source/NETworkManager/Models/Network/PingInfo.cs @@ -2,17 +2,24 @@ using System; using System.Net; using System.Net.NetworkInformation; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace NETworkManager.Models.Network { public class PingInfo { public DateTime Timestamp { get; set; } + + [JsonConverter(typeof(JsonIPAddressConverter))] public IPAddress IPAddress { get; set; } + public string Hostname { get; set; } public int Bytes { get; set; } public long Time { get; set; } public int TTL { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] public IPStatus Status { get; set; } public int IPAddressInt32 => IPAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? IPv4AddressHelper.ConvertToInt32(IPAddress) : 0; diff --git a/Source/NETworkManager/Models/Network/PortInfo.cs b/Source/NETworkManager/Models/Network/PortInfo.cs index 52bedcaf91..a1dbc785da 100644 --- a/Source/NETworkManager/Models/Network/PortInfo.cs +++ b/Source/NETworkManager/Models/Network/PortInfo.cs @@ -1,11 +1,16 @@ using NETworkManager.Models.Lookup; using System.Net; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using NETworkManager.Utilities; namespace NETworkManager.Models.Network { public class PortInfo { + [JsonConverter(typeof(JsonIPAddressConverter))] public IPAddress IPAddress { get; set; } + public string Hostname { get; set; } public int Port { get; set; } public PortLookupInfo LookupInfo { get; set; } @@ -30,6 +35,7 @@ public static PortInfo Parse(PortScannedArgs e) return new PortInfo(e.IPAddress, e.Hostname, e.Port, e.LookupInfo, e.Status); } + [JsonConverter(typeof(StringEnumConverter))] public enum PortStatus { Open, diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj index 7827643b42..e416397eec 100644 --- a/Source/NETworkManager/NETworkManager.csproj +++ b/Source/NETworkManager/NETworkManager.csproj @@ -280,6 +280,7 @@ + diff --git a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs index 8fb632fdac..139ae69e2a 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs @@ -2823,6 +2823,15 @@ public static string IPv6DefaultGateway { } } + /// + /// Looks up a localized string similar to JSON. + /// + public static string JSON { + get { + return ResourceManager.GetString("JSON", resourceCulture); + } + } + /// /// Looks up a localized string similar to Keyboard. /// diff --git a/Source/NETworkManager/Resources/Localization/Strings.resx b/Source/NETworkManager/Resources/Localization/Strings.resx index 086dbee267..178b4418fe 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.resx +++ b/Source/NETworkManager/Resources/Localization/Strings.resx @@ -2069,4 +2069,7 @@ If you click "Move & Restart", the remaining files will be copied and the ap An error occurred while exporting the data. See error message for details: + + JSON + \ No newline at end of file diff --git a/Source/NETworkManager/Utilities/JSONIPAddressConverter.cs b/Source/NETworkManager/Utilities/JSONIPAddressConverter.cs new file mode 100644 index 0000000000..19ef186016 --- /dev/null +++ b/Source/NETworkManager/Utilities/JSONIPAddressConverter.cs @@ -0,0 +1,48 @@ +// Blog: https://pingfu.net/how-to-serialise-ipaddress-ipendpoint +// Source: https://gist.github.com/marcbarry/2e7a64fed2ae539cf415 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace NETworkManager.Utilities +{ + public class JsonIPAddressConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(IPAddress) || objectType == typeof(List); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // convert an ipaddress represented as a string into an IPAddress object and return it to the caller + if (objectType == typeof(IPAddress)) + return IPAddress.Parse(JToken.Load(reader).ToString()); + + // convert a json array of ipaddresses represented as strings into a List object and return it to the caller + if (objectType == typeof(List)) + return JToken.Load(reader).Select(address => IPAddress.Parse((string)address)).ToList(); + + throw new NotImplementedException(); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value.GetType() == typeof(IPAddress)) + { + JToken.FromObject(value.ToString()).WriteTo(writer); + return; + } + + // convert a List object to an array of strings of ipaddresses and write it to the serialiser + if (value.GetType() != typeof(List)) + throw new NotImplementedException(); + + JToken.FromObject((from n in (List)value select n.ToString()).ToList()).WriteTo(writer); + } + } +} diff --git a/Source/NETworkManager/ViewModels/ExportViewModel.cs b/Source/NETworkManager/ViewModels/ExportViewModel.cs index 5c39a5c539..8c36ea2fc6 100644 --- a/Source/NETworkManager/ViewModels/ExportViewModel.cs +++ b/Source/NETworkManager/ViewModels/ExportViewModel.cs @@ -84,6 +84,27 @@ public bool UseXML } } + private bool _useJSON; + public bool UseJSON + { + get => _useJSON; + set + { + if (value == _useJSON) + return; + + if (value) + { + FileType = ExportManager.ExportFileType.JSON; + ChangeFilePathExtension(FileType); + } + + _useJSON = value; + OnPropertyChanged(); + } + } + + private string _filePath; public string FilePath { @@ -114,6 +135,11 @@ public ExportViewModel(Action deleteCommand, Action - + + + + - + - + + + + - + + From 8b5e5f31069e5706923b91ff44468f6ed14f5527 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Mon, 5 Nov 2018 01:01:52 +0100 Subject: [PATCH 16/58] Export json --- .../Models/Export/ExportManager.cs | 42 +++++++++++++++++-- .../Models/Lookup/PortLookup.cs | 1 - .../NETworkManager/Models/Network/PingInfo.cs | 7 ---- .../NETworkManager/Models/Network/PortInfo.cs | 6 --- 4 files changed, 38 insertions(+), 18 deletions(-) diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index f23b5a6d87..51b465d5aa 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -121,14 +121,48 @@ from info in collection document.Save(filePath); } - public static void CreateJSON(IEnumerable collection, string filePath) + // This might be a horror to maintain, but i have no other idea... + public static void CreateJSON(ObservableCollection collection, string filePath) { - System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(collection,Formatting.Indented)); + var jsonData = new object[collection.Count]; + + for (var i = 0; i < collection.Count; i++) + { + jsonData[i] = new + { + IPAddress = collection[i].PingInfo.IPAddress.ToString(), + collection[i].Hostname, + MACAddress = collection[i].MACAddress.ToString(), + collection[i].Vendor, + collection[i].PingInfo.Bytes, + Time = Ping.TimeToString(collection[i].PingInfo.Status, collection[i].PingInfo.Time, true), + collection[i].PingInfo.TTL, + Status = collection[i].PingInfo.Status.ToString() + }; + } + + System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); } - public static void CreateJSON(IEnumerable collection, string filePath) + public static void CreateJSON(ObservableCollection collection, string filePath) { - System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(collection, Formatting.Indented)); + var jsonData = new object[collection.Count]; + + for (var i = 0; i < collection.Count; i++) + { + jsonData[i] = new + { + IPAddress = collection[i].IPAddress.ToString(), + collection[i].Hostname, + collection[i].Port, + Protocol = collection[i].LookupInfo.Protocol.ToString(), + collection[i].LookupInfo.Service, + collection[i].LookupInfo.Description, + Status = collection[i].Status.ToString() + }; + } + + System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); } public static string GetFileExtensionAsString(ExportFileType fileExtension) diff --git a/Source/NETworkManager/Models/Lookup/PortLookup.cs b/Source/NETworkManager/Models/Lookup/PortLookup.cs index a335dc6f73..de11f3f56f 100644 --- a/Source/NETworkManager/Models/Lookup/PortLookup.cs +++ b/Source/NETworkManager/Models/Lookup/PortLookup.cs @@ -77,7 +77,6 @@ public static List LookupByService(List portsByService) } #endregion - [JsonConverter(typeof(StringEnumConverter))] public enum Protocol { Tcp, diff --git a/Source/NETworkManager/Models/Network/PingInfo.cs b/Source/NETworkManager/Models/Network/PingInfo.cs index e698fd7cdb..c852612d98 100644 --- a/Source/NETworkManager/Models/Network/PingInfo.cs +++ b/Source/NETworkManager/Models/Network/PingInfo.cs @@ -2,24 +2,17 @@ using System; using System.Net; using System.Net.NetworkInformation; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; namespace NETworkManager.Models.Network { public class PingInfo { public DateTime Timestamp { get; set; } - - [JsonConverter(typeof(JsonIPAddressConverter))] public IPAddress IPAddress { get; set; } - public string Hostname { get; set; } public int Bytes { get; set; } public long Time { get; set; } public int TTL { get; set; } - - [JsonConverter(typeof(StringEnumConverter))] public IPStatus Status { get; set; } public int IPAddressInt32 => IPAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? IPv4AddressHelper.ConvertToInt32(IPAddress) : 0; diff --git a/Source/NETworkManager/Models/Network/PortInfo.cs b/Source/NETworkManager/Models/Network/PortInfo.cs index a1dbc785da..52bedcaf91 100644 --- a/Source/NETworkManager/Models/Network/PortInfo.cs +++ b/Source/NETworkManager/Models/Network/PortInfo.cs @@ -1,16 +1,11 @@ using NETworkManager.Models.Lookup; using System.Net; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using NETworkManager.Utilities; namespace NETworkManager.Models.Network { public class PortInfo { - [JsonConverter(typeof(JsonIPAddressConverter))] public IPAddress IPAddress { get; set; } - public string Hostname { get; set; } public int Port { get; set; } public PortLookupInfo LookupInfo { get; set; } @@ -35,7 +30,6 @@ public static PortInfo Parse(PortScannedArgs e) return new PortInfo(e.IPAddress, e.Hostname, e.Port, e.LookupInfo, e.Status); } - [JsonConverter(typeof(StringEnumConverter))] public enum PortStatus { Open, From 2b01797a4bc9c1c6db48fbe26b44ca1db0eebd3e Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Mon, 5 Nov 2018 18:19:11 +0100 Subject: [PATCH 17/58] Fix #155 --- Source/NETworkManager/Models/Network/HostInfo.cs | 2 ++ Source/NETworkManager/Views/IPScannerView.xaml | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/NETworkManager/Models/Network/HostInfo.cs b/Source/NETworkManager/Models/Network/HostInfo.cs index 169d4e88d0..a6614cdb28 100644 --- a/Source/NETworkManager/Models/Network/HostInfo.cs +++ b/Source/NETworkManager/Models/Network/HostInfo.cs @@ -9,6 +9,8 @@ public class HostInfo public PhysicalAddress MACAddress { get; set; } public string Vendor { get; set; } + public string MACAddressString => MACAddress.ToString(); + public HostInfo() { diff --git a/Source/NETworkManager/Views/IPScannerView.xaml b/Source/NETworkManager/Views/IPScannerView.xaml index 8106e463f4..ab0600b450 100644 --- a/Source/NETworkManager/Views/IPScannerView.xaml +++ b/Source/NETworkManager/Views/IPScannerView.xaml @@ -379,11 +379,11 @@ - + - + - + From bbca9e6536f1a507b5a9bf48aee51103c3ac4b53 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Mon, 5 Nov 2018 21:06:03 +0100 Subject: [PATCH 18/58] Ping export added --- .../Controls/MultiSelectScrollingDataGrid.cs | 41 +++++++++ .../Controls/ScrollingDataGrid.cs | 21 ----- .../Models/Export/ExportManager.cs | 81 +++++++++++++++++ Source/NETworkManager/NETworkManager.csproj | 2 +- .../Resources/Styles/DataGridStyles.xaml | 2 +- .../Resources/Styles/ScrollBarStyles.xaml | 2 +- .../ViewModels/IPScannerViewModel.cs | 6 +- .../ViewModels/PingViewModel.cs | 88 ++++++++++++++++--- .../ViewModels/PortScannerViewModel.cs | 6 +- .../NETworkManager/Views/IPScannerView.xaml | 2 +- Source/NETworkManager/Views/PingView.xaml | 39 ++++++-- Source/NETworkManager/Views/PingView.xaml.cs | 3 +- .../NETworkManager/Views/PortScannerView.xaml | 2 +- 13 files changed, 241 insertions(+), 54 deletions(-) create mode 100644 Source/NETworkManager/Controls/MultiSelectScrollingDataGrid.cs delete mode 100644 Source/NETworkManager/Controls/ScrollingDataGrid.cs diff --git a/Source/NETworkManager/Controls/MultiSelectScrollingDataGrid.cs b/Source/NETworkManager/Controls/MultiSelectScrollingDataGrid.cs new file mode 100644 index 0000000000..7d94c1e296 --- /dev/null +++ b/Source/NETworkManager/Controls/MultiSelectScrollingDataGrid.cs @@ -0,0 +1,41 @@ +using System.Collections; +using System.Collections.Specialized; +using System.Windows; +using System.Windows.Controls; + +namespace NETworkManager.Controls +{ + public class MultiSelectScrollingDataGrid : DataGrid + { + public MultiSelectScrollingDataGrid() + { + SelectionChanged += DataGridMultiItemSelect_SelectionChanged; + } + + private void DataGridMultiItemSelect_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + SelectedItemsList = SelectedItems; + } + + public IList SelectedItemsList + { + get => (IList)GetValue(SelectedItemsListProperty); + set => SetValue(SelectedItemsListProperty, value); + } + + public static readonly DependencyProperty SelectedItemsListProperty = DependencyProperty.Register("SelectedItemsList", typeof(IList), typeof(MultiSelectScrollingDataGrid), new PropertyMetadata(null)); + + protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) + { + if (e.NewItems == null) + return; + + var newItemCount = e.NewItems.Count; + + if (newItemCount > 0) + ScrollIntoView(e.NewItems[newItemCount - 1]); + + base.OnItemsChanged(e); + } + } +} \ No newline at end of file diff --git a/Source/NETworkManager/Controls/ScrollingDataGrid.cs b/Source/NETworkManager/Controls/ScrollingDataGrid.cs deleted file mode 100644 index d44f99cea7..0000000000 --- a/Source/NETworkManager/Controls/ScrollingDataGrid.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Specialized; -using System.Windows.Controls; - -namespace NETworkManager.Controls -{ - public class ScrollingDataGrid : DataGrid - { - protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) - { - if (e.NewItems == null) - return; - - var newItemCount = e.NewItems.Count; - - if (newItemCount > 0) - ScrollIntoView(e.NewItems[newItemCount - 1]); - - base.OnItemsChanged(e); - } - } -} \ No newline at end of file diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index 51b465d5aa..7a310122bb 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -18,6 +18,8 @@ public static class ExportManager #endregion #region Methods + + #region Export public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) { switch (fileType) @@ -54,6 +56,26 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo } } + public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) + { + switch (fileType) + { + case ExportFileType.CSV: + CreateCSV(collection, filePath); + break; + case ExportFileType.XML: + CreateXML(collection, filePath); + break; + case ExportFileType.JSON: + CreateJSON(collection, filePath); + break; + default: + throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); + } + } + #endregion + + #region CreateCSV private static void CreateCSV(IEnumerable collection, string filePath) { var stringBuilder = new StringBuilder(); @@ -78,6 +100,20 @@ private static void CreateCSV(IEnumerable collection, string filePath) System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); } + private static void CreateCSV(IEnumerable collection, string filePath) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.AppendLine($"{nameof(PingInfo.Timestamp)},{nameof(PingInfo.IPAddress)},{nameof(PingInfo.Hostname)},{nameof(PingInfo.Bytes)},{nameof(PingInfo.Time)},{nameof(PingInfo.TTL)},{nameof(PingInfo.Status)}"); + + foreach (var info in collection) + stringBuilder.AppendLine($"{info.Timestamp},{info.IPAddress},{info.Hostname},{info.Bytes},{Ping.TimeToString(info.Status, info.Time, true)},{info.TTL},{info.Status}"); + + System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); + } + #endregion + + #region CreateXML public static void CreateXML(IEnumerable collection, string filePath) { var document = new XDocument(DefaultXDeclaration, @@ -121,6 +157,29 @@ from info in collection document.Save(filePath); } + public static void CreateXML(IEnumerable collection, string filePath) + { + var document = new XDocument(DefaultXDeclaration, + + new XElement(ApplicationViewManager.Name.PortScanner.ToString(), + new XElement(nameof(PingInfo) + "s", + + from info in collection + select + new XElement(nameof(PingInfo), + new XElement(nameof(info.Timestamp), info.Timestamp), + new XElement(nameof(PingInfo.IPAddress), info.IPAddress), + new XElement(nameof(PingInfo.Hostname), info.Hostname), + new XElement(nameof(PingInfo.Bytes), info.Bytes), + new XElement(nameof(PingInfo.Time), Ping.TimeToString(info.Status, info.Time, true)), + new XElement(nameof(PingInfo.TTL), info.TTL), + new XElement(nameof(PingInfo.Status), info.Status))))); + + document.Save(filePath); + } + #endregion + + #region CreateJSON // This might be a horror to maintain, but i have no other idea... public static void CreateJSON(ObservableCollection collection, string filePath) { @@ -165,6 +224,28 @@ public static void CreateJSON(ObservableCollection collection, string System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); } + public static void CreateJSON(ObservableCollection collection, string filePath) + { + var jsonData = new object[collection.Count]; + + for (var i = 0; i < collection.Count; i++) + { + jsonData[i] = new + { + collection[i].Timestamp, + IPAddress = collection[i].IPAddress.ToString(), + collection[i].Hostname, + collection[i].Bytes, + Time = Ping.TimeToString(collection[i].Status, collection[i].Time, true), + collection[i].TTL, + Status = collection[i].Status.ToString() + }; + } + + System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); + } + #endregion + public static string GetFileExtensionAsString(ExportFileType fileExtension) { switch (fileExtension) diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj index e416397eec..60fac49e9b 100644 --- a/Source/NETworkManager/NETworkManager.csproj +++ b/Source/NETworkManager/NETworkManager.csproj @@ -955,7 +955,7 @@ Code - + diff --git a/Source/NETworkManager/Resources/Styles/DataGridStyles.xaml b/Source/NETworkManager/Resources/Styles/DataGridStyles.xaml index 3bde83a200..01750b0e75 100644 --- a/Source/NETworkManager/Resources/Styles/DataGridStyles.xaml +++ b/Source/NETworkManager/Resources/Styles/DataGridStyles.xaml @@ -12,7 +12,7 @@ - - + + diff --git a/Source/NETworkManager/Views/PingView.xaml.cs b/Source/NETworkManager/Views/PingView.xaml.cs index 6975a7c9c6..df110c762e 100644 --- a/Source/NETworkManager/Views/PingView.xaml.cs +++ b/Source/NETworkManager/Views/PingView.xaml.cs @@ -1,6 +1,7 @@ using NETworkManager.ViewModels; using System; using System.Windows.Controls; +using MahApps.Metro.Controls.Dialogs; namespace NETworkManager.Views { @@ -12,7 +13,7 @@ public PingView(int tabId, string host = null) { InitializeComponent(); - _viewModel = new PingViewModel(tabId, host); + _viewModel = new PingViewModel(DialogCoordinator.Instance,tabId, host); DataContext = _viewModel; diff --git a/Source/NETworkManager/Views/PortScannerView.xaml b/Source/NETworkManager/Views/PortScannerView.xaml index 72f54cb716..935d39f372 100644 --- a/Source/NETworkManager/Views/PortScannerView.xaml +++ b/Source/NETworkManager/Views/PortScannerView.xaml @@ -179,7 +179,7 @@ - + - - + + @@ -311,8 +335,8 @@ - - + + diff --git a/Source/NETworkManager/Views/TracerouteView.xaml.cs b/Source/NETworkManager/Views/TracerouteView.xaml.cs index 0e75433cb7..5bea0113b9 100644 --- a/Source/NETworkManager/Views/TracerouteView.xaml.cs +++ b/Source/NETworkManager/Views/TracerouteView.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Windows.Controls; +using MahApps.Metro.Controls.Dialogs; using NETworkManager.ViewModels; namespace NETworkManager.Views @@ -12,7 +13,7 @@ public TracerouteView(int tabId, string host = null) { InitializeComponent(); - _viewModel = new TracerouteViewModel(tabId, host); + _viewModel = new TracerouteViewModel(DialogCoordinator.Instance, tabId, host); DataContext = _viewModel; From 5f2e2c4a351225a8e7fba7bb46bc840ba84d1260 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sat, 10 Nov 2018 23:52:53 +0100 Subject: [PATCH 22/58] SplashScreen added --- {Icon => Images}/NETworkManager.ico | Bin {Icon => Images}/NETworkManager.svg | 0 Images/SplashScreen.png | Bin 0 -> 19509 bytes Images/SplashScreen.xcf | Bin 0 -> 58083 bytes .../Models/Export/ExportManager.cs | 58 ++++++++++++++++-- Source/NETworkManager/NETworkManager.csproj | 5 +- .../Resources/Images/NETworkManager.ico | Bin 302652 -> 302652 bytes .../Resources/Images/SplashScreen.png | Bin 0 -> 19509 bytes 8 files changed, 56 insertions(+), 7 deletions(-) rename {Icon => Images}/NETworkManager.ico (100%) rename {Icon => Images}/NETworkManager.svg (100%) create mode 100644 Images/SplashScreen.png create mode 100644 Images/SplashScreen.xcf create mode 100644 Source/NETworkManager/Resources/Images/SplashScreen.png diff --git a/Icon/NETworkManager.ico b/Images/NETworkManager.ico similarity index 100% rename from Icon/NETworkManager.ico rename to Images/NETworkManager.ico diff --git a/Icon/NETworkManager.svg b/Images/NETworkManager.svg similarity index 100% rename from Icon/NETworkManager.svg rename to Images/NETworkManager.svg diff --git a/Images/SplashScreen.png b/Images/SplashScreen.png new file mode 100644 index 0000000000000000000000000000000000000000..778e7ef100c950f341f1e17303f4cf7176e805f8 GIT binary patch literal 19509 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 a*4(eX_Wd;G(Vkrw*L!;Qewc04{{H|?wA?`e literal 0 HcmV?d00001 diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index c2597fa330..df7e722c34 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -81,10 +81,10 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo CreateCSV(collection, filePath); break; case ExportFileType.XML: - // CreateXML(collection, filePath); + CreateXML(collection, filePath); break; case ExportFileType.JSON: - // CreateJSON(collection, filePath); + CreateJSON(collection, filePath); break; default: throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); @@ -129,7 +129,7 @@ private static void CreateCSV(IEnumerable collection, string filePath) System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); } - private static void CreateCSV(IEnumerable collection, string filePath) + private static void CreateCSV(IEnumerable collection, string filePath) { var stringBuilder = new StringBuilder(); @@ -175,7 +175,7 @@ public static void CreateXML(IEnumerable collection, string filePath) from info in collection select new XElement(nameof(PortInfo), - new XElement(nameof(info.IPAddress), info.IPAddress), + new XElement(nameof(PortInfo.IPAddress), info.IPAddress), new XElement(nameof(PortInfo.Hostname), info.Hostname), new XElement(nameof(PortInfo.Port), info.Port), new XElement(nameof(PortLookupInfo.Protocol), info.LookupInfo.Protocol), @@ -190,13 +190,13 @@ public static void CreateXML(IEnumerable collection, string filePath) { var document = new XDocument(DefaultXDeclaration, - new XElement(ApplicationViewManager.Name.PortScanner.ToString(), + new XElement(ApplicationViewManager.Name.Ping.ToString(), new XElement(nameof(PingInfo) + "s", from info in collection select new XElement(nameof(PingInfo), - new XElement(nameof(info.Timestamp), info.Timestamp), + new XElement(nameof(PingInfo.Timestamp), info.Timestamp), new XElement(nameof(PingInfo.IPAddress), info.IPAddress), new XElement(nameof(PingInfo.Hostname), info.Hostname), new XElement(nameof(PingInfo.Bytes), info.Bytes), @@ -206,6 +206,29 @@ from info in collection document.Save(filePath); } + + public static void CreateXML(IEnumerable collection, string filePath) + { + var document = new XDocument(DefaultXDeclaration, + + new XElement(ApplicationViewManager.Name.Traceroute.ToString(), + new XElement(nameof(TracerouteHopInfo) + "s", + + from info in collection + select + new XElement(nameof(TracerouteHopInfo), + new XElement(nameof(TracerouteHopInfo.Hop), info.Hop), + new XElement(nameof(TracerouteHopInfo.Time1), Ping.TimeToString(info.Status1, info.Time1, true)), + new XElement(nameof(TracerouteHopInfo.Time2), Ping.TimeToString(info.Status2, info.Time2, true)), + new XElement(nameof(TracerouteHopInfo.Time3), Ping.TimeToString(info.Status3, info.Time3, true)), + new XElement(nameof(TracerouteHopInfo.IPAddress), info.IPAddress), + new XElement(nameof(TracerouteHopInfo.Hostname), info.Hostname), + new XElement(nameof(TracerouteHopInfo.Status1), info.Status1), + new XElement(nameof(TracerouteHopInfo.Status2), info.Status2), + new XElement(nameof(TracerouteHopInfo.Status3), info.Status3))))); + + document.Save(filePath); + } #endregion #region CreateJSON @@ -273,6 +296,29 @@ public static void CreateJSON(ObservableCollection collection, string System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); } + + public static void CreateJSON(ObservableCollection collection, string filePath) + { + var jsonData = new object[collection.Count]; + + for (var i = 0; i < collection.Count; i++) + { + jsonData[i] = new + { + collection[i].Hop, + Time1 = Ping.TimeToString(collection[i].Status1, collection[i].Time1, true), + Time2 = Ping.TimeToString(collection[i].Status2, collection[i].Time2, true), + Time3 = Ping.TimeToString(collection[i].Status3, collection[i].Time3, true), + IPAddress = collection[i].IPAddress.ToString(), + collection[i].Hostname, + Status1 = collection[i].Status1.ToString(), + Status2 = collection[i].Status2.ToString(), + Status3 = collection[i].Status3.ToString() + }; + } + + System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); + } #endregion public static string GetFileExtensionAsString(ExportFileType fileExtension) diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj index 81dc5a6be9..2535012f7a 100644 --- a/Source/NETworkManager/NETworkManager.csproj +++ b/Source/NETworkManager/NETworkManager.csproj @@ -642,7 +642,9 @@ PreserveNewest - + + PreserveNewest + PreserveNewest @@ -653,6 +655,7 @@ + PreserveNewest diff --git a/Source/NETworkManager/Resources/Images/NETworkManager.ico b/Source/NETworkManager/Resources/Images/NETworkManager.ico index 03586145aeb9b0a9322f69bb1e005504fed6f7d4..c54d6faf6dd64ca80402c3b990248821afbed379 100644 GIT binary patch delta 179 zcmdn9M`+I;p?U@eMg}ehUS0+e;5`SVmjM|E4uH4{3>ra93?+OF3=IuXJ_jSioCGF@ zgainG#S{jHBN@yL4h~RxCkBR`zsw8<22gob28Jp2EDRbN5PpUf1A|Q}3xk3JR9;4a rfniBK3xk9NM4*F{fx%}23xfbqoS|`c>uko>*-WjonYYenSvVH}R5>32 delta 179 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 Date: Sun, 11 Nov 2018 00:17:52 +0100 Subject: [PATCH 23/58] Update SplashScreen --- Images/SplashScreen.png | Bin 19509 -> 14868 bytes Images/SplashScreen.xcf | Bin 58083 -> 44053 bytes Images/SplashScreen_large.png | Bin 0 -> 19509 bytes Images/SplashScreen_large.xcf | Bin 0 -> 58083 bytes .../Resources/Images/SplashScreen.png | Bin 19509 -> 14868 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Images/SplashScreen_large.png create mode 100644 Images/SplashScreen_large.xcf diff --git a/Images/SplashScreen.png b/Images/SplashScreen.png index 778e7ef100c950f341f1e17303f4cf7176e805f8..6bffd7d2481d19e23ed0392fb06e67abbba4fd1a 100644 GIT binary patch literal 14868 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 L+Nv*=O|SnCfPHtX literal 19509 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 avkmGzTd#L+uP(a(8n_qe{tUSB?EimK6Hqn) literal 58083 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 a*4(eX_Wd;G(Vkrw*L!;Qewc04{{H|?wA?`e diff --git a/Images/SplashScreen_large.png b/Images/SplashScreen_large.png new file mode 100644 index 0000000000000000000000000000000000000000..778e7ef100c950f341f1e17303f4cf7176e805f8 GIT binary patch literal 19509 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 a*4(eX_Wd;G(Vkrw*L!;Qewc04{{H|?wA?`e literal 0 HcmV?d00001 diff --git a/Source/NETworkManager/Resources/Images/SplashScreen.png b/Source/NETworkManager/Resources/Images/SplashScreen.png index 778e7ef100c950f341f1e17303f4cf7176e805f8..6bffd7d2481d19e23ed0392fb06e67abbba4fd1a 100644 GIT binary patch literal 14868 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 L+Nv*=O|SnCfPHtX literal 19509 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 Date: Mon, 12 Nov 2018 02:38:55 +0100 Subject: [PATCH 24/58] Highlight ping timeout --- .../Converters/PingStatusToBoolConverter.cs | 20 +++++++++++ .../Models/Settings/SettingsInfo.cs | 35 ++++++++----------- Source/NETworkManager/NETworkManager.csproj | 1 + .../ViewModels/PingViewModel.cs | 4 +++ Source/NETworkManager/Views/PingView.xaml | 12 +++++++ 5 files changed, 51 insertions(+), 21 deletions(-) create mode 100644 Source/NETworkManager/Converters/PingStatusToBoolConverter.cs diff --git a/Source/NETworkManager/Converters/PingStatusToBoolConverter.cs b/Source/NETworkManager/Converters/PingStatusToBoolConverter.cs new file mode 100644 index 0000000000..bdfcbea23c --- /dev/null +++ b/Source/NETworkManager/Converters/PingStatusToBoolConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Net.NetworkInformation; +using System.Windows.Data; + +namespace NETworkManager.Converters +{ + public sealed class PingStatusToBoolConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value != null && (IPStatus) value == IPStatus.Success; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Source/NETworkManager/Models/Settings/SettingsInfo.cs b/Source/NETworkManager/Models/Settings/SettingsInfo.cs index feee067dbb..4f2b48e59a 100644 --- a/Source/NETworkManager/Models/Settings/SettingsInfo.cs +++ b/Source/NETworkManager/Models/Settings/SettingsInfo.cs @@ -687,9 +687,6 @@ public string IPScanner_ExportFilePath return; _ipScanner_ExportFilePath = value; - - OnPropertyChanged(); - SettingsChanged = true; } } @@ -704,9 +701,6 @@ public ExportManager.ExportFileType IPScanner_ExportFileType return; _ipScanner_ExportFileType = value; - - OnPropertyChanged(); - SettingsChanged = true; } } @@ -866,9 +860,6 @@ public string PortScanner_ExportFilePath return; _portScanner_ExportFilePath = value; - - OnPropertyChanged(); - SettingsChanged = true; } } @@ -883,9 +874,6 @@ public ExportManager.ExportFileType PortScanner_ExportFileType return; _portScanner_ExportFileType = value; - - OnPropertyChanged(); - SettingsChanged = true; } } @@ -1087,9 +1075,6 @@ public string Ping_ExportFilePath return; _ping_ExportFilePath = value; - - OnPropertyChanged(); - SettingsChanged = true; } } @@ -1104,7 +1089,21 @@ public ExportManager.ExportFileType Ping_ExportFileType return; _ping_ExportFileType = value; + SettingsChanged = true; + } + } + + private bool _ping_EnableVisualization = true; + public bool Ping_EnableVisualization + { + get => _ping_EnableVisualization; + set + { + if(value == _ping_EnableVisualization) + return; + _ping_EnableVisualization = value; + OnPropertyChanged(); SettingsChanged = true; @@ -1269,9 +1268,6 @@ public string Traceroute_ExportFilePath return; _traceroute_ExportFilePath = value; - - OnPropertyChanged(); - SettingsChanged = true; } } @@ -1286,9 +1282,6 @@ public ExportManager.ExportFileType Traceroute_ExportFileType return; _traceroute_ExportFileType = value; - - OnPropertyChanged(); - SettingsChanged = true; } } diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj index 2535012f7a..35eac8a52c 100644 --- a/Source/NETworkManager/NETworkManager.csproj +++ b/Source/NETworkManager/NETworkManager.csproj @@ -236,6 +236,7 @@ + diff --git a/Source/NETworkManager/ViewModels/PingViewModel.cs b/Source/NETworkManager/ViewModels/PingViewModel.cs index 8d9dcb9743..ee4ff9aa43 100644 --- a/Source/NETworkManager/ViewModels/PingViewModel.cs +++ b/Source/NETworkManager/ViewModels/PingViewModel.cs @@ -298,6 +298,7 @@ public string StatusMessage public bool ShowStatistics => SettingsManager.Current.Ping_ShowStatistics; + public bool EnableVisualization => SettingsManager.Current.Ping_EnableVisualization; #endregion #region Contructor, load settings @@ -708,6 +709,9 @@ private void SettingsManager_PropertyChanged(object sender, PropertyChangedEvent { if (e.PropertyName == nameof(SettingsInfo.Ping_ShowStatistics)) OnPropertyChanged(nameof(ShowStatistics)); + + if(e.PropertyName == nameof(SettingsInfo.Ping_EnableVisualization)) + OnPropertyChanged(nameof(EnableVisualization)); } #endregion } diff --git a/Source/NETworkManager/Views/PingView.xaml b/Source/NETworkManager/Views/PingView.xaml index 5b4357c332..952d66e7ca 100644 --- a/Source/NETworkManager/Views/PingView.xaml +++ b/Source/NETworkManager/Views/PingView.xaml @@ -21,6 +21,7 @@ + @@ -243,6 +244,17 @@ + @@ -32,5 +44,16 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/NETworkManager/Views/SubnetCalculatorWideSubnetView.xaml.cs b/Source/NETworkManager/Views/SubnetCalculatorWideSubnetView.xaml.cs new file mode 100644 index 0000000000..8a4c29ffbd --- /dev/null +++ b/Source/NETworkManager/Views/SubnetCalculatorWideSubnetView.xaml.cs @@ -0,0 +1,23 @@ +using MahApps.Metro.Controls.Dialogs; +using NETworkManager.ViewModels; + +namespace NETworkManager.Views +{ + public partial class SubnetCalculatorWideSubnetView + { + private readonly SubnetCalculatorWideSubnetViewModel _viewModel = new SubnetCalculatorWideSubnetViewModel(DialogCoordinator.Instance); + + public SubnetCalculatorWideSubnetView() + { + InitializeComponent(); + DataContext = _viewModel; + + Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted; + } + + private void Dispatcher_ShutdownStarted(object sender, System.EventArgs e) + { + _viewModel.OnShutdown(); + } + } +} From c653f227989f49e7064b50a69f1c824c26040f86 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sun, 18 Nov 2018 01:43:51 +0100 Subject: [PATCH 31/58] Export added --- .../Models/Export/ExportManager.cs | 63 +++++++++++++ .../Models/Settings/SettingsInfo.cs | 28 ++++++ .../ViewModels/SNMPViewModel.cs | 94 +++++++++++++++---- Source/NETworkManager/Views/SNMPView.xaml | 41 ++++++-- Source/NETworkManager/Views/SNMPView.xaml.cs | 3 +- 5 files changed, 203 insertions(+), 26 deletions(-) diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index f6dfdb8cb7..be1815b717 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -108,6 +108,24 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); } } + + public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) + { + switch (fileType) + { + case ExportFileType.CSV: + CreateCSV(collection, filePath); + break; + case ExportFileType.XML: + CreateXML(collection, filePath); + break; + case ExportFileType.JSON: + CreateJSON(collection, filePath); + break; + default: + throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); + } + } #endregion #region CreateCSV @@ -171,6 +189,18 @@ private static void CreateCSV(IEnumerable collection, strin System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); } + private static void CreateCSV(IEnumerable collection, string filePath) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.AppendLine($"{nameof(SNMPReceivedInfo.OID)},{nameof(SNMPReceivedInfo.Data)}"); + + foreach (var info in collection) + stringBuilder.AppendLine($"{info.OID},{info.Data}"); + + System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); + } + #endregion #region CreateXML @@ -281,6 +311,23 @@ from info in collection document.Save(filePath); } + + + public static void CreateXML(IEnumerable collection, string filePath) + { + var document = new XDocument(DefaultXDeclaration, + + new XElement(ApplicationViewManager.Name.Traceroute.ToString(), + new XElement(nameof(SNMPReceivedInfo) + "s", + + from info in collection + select + new XElement(nameof(SNMPReceivedInfo), + new XElement(nameof(SNMPReceivedInfo.OID), info.OID), + new XElement(nameof(SNMPReceivedInfo.Data), info.Data))))); + + document.Save(filePath); + } #endregion #region CreateJSON @@ -392,6 +439,22 @@ public static void CreateJSON(ObservableCollection collecti System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); } + + public static void CreateJSON(ObservableCollection collection, string filePath) + { + var jsonData = new object[collection.Count]; + + for (var i = 0; i < collection.Count; i++) + { + jsonData[i] = new + { + collection[i].OID, + collection[i].Data + }; + } + + System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); + } #endregion public static string GetFileExtensionAsString(ExportFileType fileExtension) diff --git a/Source/NETworkManager/Models/Settings/SettingsInfo.cs b/Source/NETworkManager/Models/Settings/SettingsInfo.cs index a32bd2d708..35e2173dd9 100644 --- a/Source/NETworkManager/Models/Settings/SettingsInfo.cs +++ b/Source/NETworkManager/Models/Settings/SettingsInfo.cs @@ -2388,6 +2388,34 @@ public bool SNMP_ShowStatistics SettingsChanged = true; } } + + private string _snmp_ExportFilePath; + public string SNMP_ExportFilePath + { + get => _snmp_ExportFilePath; + set + { + if (value == _snmp_ExportFilePath) + return; + + _snmp_ExportFilePath = value; + SettingsChanged = true; + } + } + + private ExportManager.ExportFileType _snmp_ExportFileType = ExportManager.ExportFileType.CSV; + public ExportManager.ExportFileType SNMP_ExportFileType + { + get => _snmp_ExportFileType; + set + { + if (value == _snmp_ExportFileType) + return; + + _snmp_ExportFileType = value; + SettingsChanged = true; + } + } #endregion #region WakeOnLAN diff --git a/Source/NETworkManager/ViewModels/SNMPViewModel.cs b/Source/NETworkManager/ViewModels/SNMPViewModel.cs index 3df3ed13b4..c369189624 100644 --- a/Source/NETworkManager/ViewModels/SNMPViewModel.cs +++ b/Source/NETworkManager/ViewModels/SNMPViewModel.cs @@ -2,6 +2,7 @@ using NETworkManager.Models.Network; using NETworkManager.Models.Settings; using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; @@ -16,12 +17,17 @@ using static NETworkManager.Models.Network.SNMP; using NETworkManager.Controls; using Dragablz; +using MahApps.Metro.Controls.Dialogs; +using NETworkManager.Models.Export; +using NETworkManager.Views; namespace NETworkManager.ViewModels { public class SNMPViewModel : ViewModelBase { #region Variables + private readonly IDialogCoordinator _dialogCoordinator; + private readonly DispatcherTimer _dispatcherTimer = new DispatcherTimer(); private readonly Stopwatch _stopwatch = new Stopwatch(); @@ -240,20 +246,20 @@ public bool IsWorking } } - private ObservableCollection _queryResult = new ObservableCollection(); - public ObservableCollection QueryResult + private ObservableCollection _queryResults = new ObservableCollection(); + public ObservableCollection QueryResults { - get => _queryResult; + get => _queryResults; set { - if (Equals(value, _queryResult)) + if (Equals(value, _queryResults)) return; - _queryResult = value; + _queryResults = value; } } - public ICollectionView QueryResultView { get; } + public ICollectionView QueryResultsView { get; } private SNMPReceivedInfo _selectedQueryResult; public SNMPReceivedInfo SelectedQueryResult @@ -269,6 +275,21 @@ public SNMPReceivedInfo SelectedQueryResult } } + private IList _selectedQueryResults = new ArrayList(); + public IList SelectedQueryResults + { + get => _selectedQueryResults; + set + { + if (Equals(value, _selectedQueryResults)) + return; + + _selectedQueryResults = value; + OnPropertyChanged(); + } + } + + private bool _displayStatusMessage; public bool DisplayStatusMessage { @@ -375,10 +396,12 @@ public bool ExpandStatistics #endregion #region Contructor, load settings - public SNMPViewModel(int tabId, string host) + public SNMPViewModel(IDialogCoordinator instance, int tabId, string host) { _isLoading = true; + _dialogCoordinator = instance; + _tabId = tabId; Host = host; @@ -387,21 +410,21 @@ public SNMPViewModel(int tabId, string host) OIDHistoryView = CollectionViewSource.GetDefaultView(SettingsManager.Current.SNMP_OIDHistory); // Result view - QueryResultView = CollectionViewSource.GetDefaultView(QueryResult); - QueryResultView.SortDescriptions.Add(new SortDescription(nameof(SNMPReceivedInfo.OID), ListSortDirection.Ascending)); + QueryResultsView = CollectionViewSource.GetDefaultView(QueryResults); + QueryResultsView.SortDescriptions.Add(new SortDescription(nameof(SNMPReceivedInfo.OID), ListSortDirection.Ascending)); // Versions (v1, v2c, v3) Versions = Enum.GetValues(typeof(SNMPVersion)).Cast().ToList(); // Modes - Modes = new List() { SNMPMode.Get, SNMPMode.Walk, SNMPMode.Set }; + Modes = new List { SNMPMode.Get, SNMPMode.Walk, SNMPMode.Set }; // Security - Securitys = new List() { SNMPV3Security.NoAuthNoPriv, SNMPV3Security.AuthNoPriv, SNMPV3Security.AuthPriv }; + Securitys = new List { SNMPV3Security.NoAuthNoPriv, SNMPV3Security.AuthNoPriv, SNMPV3Security.AuthPriv }; // Auth / Priv - AuthenticationProviders = new List() { SNMPV3AuthenticationProvider.MD5, SNMPV3AuthenticationProvider.SHA1 }; - PrivacyProviders = new List() { SNMPV3PrivacyProvider.DES, SNMPV3PrivacyProvider.AES }; + AuthenticationProviders = new List { SNMPV3AuthenticationProvider.MD5, SNMPV3AuthenticationProvider.SHA1 }; + PrivacyProviders = new List { SNMPV3PrivacyProvider.DES, SNMPV3PrivacyProvider.AES }; LoadSettings(); @@ -453,6 +476,45 @@ private void CopySelectedDataAction() CommonMethods.SetClipboard(SelectedQueryResult.Data); } + public ICommand ExportCommand + { + get { return new RelayCommand(p => ExportAction()); } + } + + private async void ExportAction() + { + var customDialog = new CustomDialog + { + Title = Resources.Localization.Strings.Export + }; + + var exportViewModel = new ExportViewModel(async instance => + { + await _dialogCoordinator.HideMetroDialogAsync(this, customDialog); + + try + { + ExportManager.Export(instance.FilePath, instance.FileType, instance.ExportAll ? QueryResults : new ObservableCollection(SelectedQueryResults.Cast().ToArray())); + } + catch (Exception ex) + { + var settings = AppearanceManager.MetroDialog; + settings.AffirmativeButtonText = Resources.Localization.Strings.OK; + + await _dialogCoordinator.ShowMessageAsync(this, Resources.Localization.Strings.Error, Resources.Localization.Strings.AnErrorOccurredWhileExportingTheData + Environment.NewLine + Environment.NewLine + ex.Message, MessageDialogStyle.Affirmative, settings); + } + + SettingsManager.Current.SNMP_ExportFileType = instance.FileType; + SettingsManager.Current.SNMP_ExportFilePath = instance.FilePath; + }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }, SettingsManager.Current.SNMP_ExportFileType, SettingsManager.Current.SNMP_ExportFilePath); + + customDialog.Content = new ExportDialog + { + DataContext = exportViewModel + }; + + await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); + } #endregion #region Methods @@ -469,7 +531,7 @@ private async void Work() _dispatcherTimer.Start(); EndTime = null; - QueryResult.Clear(); + QueryResults.Clear(); Responses = 0; // Change the tab title (not nice, but it works) @@ -622,8 +684,8 @@ private void Snmp_Received(object sender, SNMPReceivedArgs e) Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate { - lock (QueryResult) - QueryResult.Add(snmpReceivedInfo); + lock (QueryResults) + QueryResults.Add(snmpReceivedInfo); })); Responses++; diff --git a/Source/NETworkManager/Views/SNMPView.xaml b/Source/NETworkManager/Views/SNMPView.xaml index 11b3e56110..c61e0553e6 100644 --- a/Source/NETworkManager/Views/SNMPView.xaml +++ b/Source/NETworkManager/Views/SNMPView.xaml @@ -11,6 +11,9 @@ xmlns:viewModels="clr-namespace:NETworkManager.ViewModels" xmlns:network="clr-namespace:NETworkManager.Models.Network" xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" + xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro" + xmlns:controls="clr-namespace:NETworkManager.Controls" + dialogs:DialogParticipation.Register="{Binding}" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:SNMPViewModel}"> @@ -472,8 +475,8 @@ - - + + - - + + - + - + - diff --git a/Source/NETworkManager/Views/SNMPView.xaml.cs b/Source/NETworkManager/Views/SNMPView.xaml.cs index dcc3bc03c9..bb7365c5a5 100644 --- a/Source/NETworkManager/Views/SNMPView.xaml.cs +++ b/Source/NETworkManager/Views/SNMPView.xaml.cs @@ -1,5 +1,6 @@ using NETworkManager.ViewModels; using System.Windows.Controls; +using MahApps.Metro.Controls.Dialogs; namespace NETworkManager.Views { @@ -11,7 +12,7 @@ public SNMPView(int tabId, string host = null) { InitializeComponent(); - _viewModel = new SNMPViewModel(tabId, host); + _viewModel = new SNMPViewModel(DialogCoordinator.Instance, tabId, host); DataContext = _viewModel; } From 9ae60bd790287528f6695feaa44473af116dc0e7 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Tue, 20 Nov 2018 01:06:29 +0100 Subject: [PATCH 32/58] Export added --- .../Models/Export/ExportManager.cs | 68 ++++++++++- .../Models/Network/{ARPTable.cs => ARP.cs} | 10 +- .../Network/{ARPTableInfo.cs => ARPInfo.cs} | 6 +- .../Models/Network/IPScanner.cs | 2 +- .../Models/Settings/SettingsInfo.cs | 28 +++++ Source/NETworkManager/NETworkManager.csproj | 4 +- .../ViewModels/ARPTableViewModel.cs | 106 +++++++++++++----- Source/NETworkManager/Views/ARPTableView.xaml | 44 ++++++-- 8 files changed, 219 insertions(+), 49 deletions(-) rename Source/NETworkManager/Models/Network/{ARPTable.cs => ARP.cs} (95%) rename Source/NETworkManager/Models/Network/{ARPTableInfo.cs => ARPInfo.cs} (79%) diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index be1815b717..599da8bdca 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -126,6 +126,25 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); } } + + + public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) + { + switch (fileType) + { + case ExportFileType.CSV: + CreateCSV(collection, filePath); + break; + case ExportFileType.XML: + CreateXML(collection, filePath); + break; + case ExportFileType.JSON: + CreateJSON(collection, filePath); + break; + default: + throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); + } + } #endregion #region CreateCSV @@ -201,6 +220,18 @@ private static void CreateCSV(IEnumerable collection, string f System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); } + + private static void CreateCSV(IEnumerable collection, string filePath) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.AppendLine($"{nameof(ARPInfo.IPAddress)},{nameof(ARPInfo.MACAddress)},{nameof(ARPInfo.IsMulticast)}"); + + foreach (var info in collection) + stringBuilder.AppendLine($"{info.IPAddress},{info.MACAddress},{info.IsMulticast}"); + + System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); + } #endregion #region CreateXML @@ -311,8 +342,7 @@ from info in collection document.Save(filePath); } - - + public static void CreateXML(IEnumerable collection, string filePath) { var document = new XDocument(DefaultXDeclaration, @@ -328,6 +358,23 @@ from info in collection document.Save(filePath); } + + public static void CreateXML(IEnumerable collection, string filePath) + { + var document = new XDocument(DefaultXDeclaration, + + new XElement(ApplicationViewManager.Name.Traceroute.ToString(), + new XElement(nameof(ARPInfo) + "s", + + from info in collection + select + new XElement(nameof(ARPInfo), + new XElement(nameof(ARPInfo.IPAddress), info.IPAddress), + new XElement(nameof(ARPInfo.MACAddress), info.MACAddress), + new XElement(nameof(ARPInfo.IsMulticast), info.IsMulticast))))); + + document.Save(filePath); + } #endregion #region CreateJSON @@ -455,6 +502,23 @@ public static void CreateJSON(ObservableCollection collection, System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); } + + public static void CreateJSON(ObservableCollection collection, string filePath) + { + var jsonData = new object[collection.Count]; + + for (var i = 0; i < collection.Count; i++) + { + jsonData[i] = new + { + IPAddress = collection[i].IPAddress.ToString(), + MACAddress = collection[i].MACAddress.ToString(), + collection[i].IsMulticast + }; + } + + System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); + } #endregion public static string GetFileExtensionAsString(ExportFileType fileExtension) diff --git a/Source/NETworkManager/Models/Network/ARPTable.cs b/Source/NETworkManager/Models/Network/ARP.cs similarity index 95% rename from Source/NETworkManager/Models/Network/ARPTable.cs rename to Source/NETworkManager/Models/Network/ARP.cs index 1fc1004897..c0d0c8a202 100644 --- a/Source/NETworkManager/Models/Network/ARPTable.cs +++ b/Source/NETworkManager/Models/Network/ARP.cs @@ -14,7 +14,7 @@ namespace NETworkManager.Models.Network { [SuppressMessage("ReSharper", "InconsistentNaming")] - public class ARPTable + public class ARP { #region Variables // The max number of physical addresses. @@ -77,14 +77,14 @@ protected virtual void OnUserHasCanceled() #endregion #region Methods - public static Task> GetTableAsync() + public static Task> GetTableAsync() { return Task.Run(() => GetTable()); } - public static List GetTable() + public static List GetTable() { - var list = new List(); + var list = new List(); // The number of bytes needed. var bytesNeeded = 0; @@ -151,7 +151,7 @@ public static List GetTable() // Filter 0.0.0.0.0.0, 255.255.255.255.255.255 if (!macAddress.Equals(virtualMAC) && !macAddress.Equals(broadcastMAC)) - list.Add(new ARPTableInfo(ipAddress, macAddress, (ipAddress.IsIPv6Multicast || IPv4AddressHelper.IsMulticast(ipAddress)))); + list.Add(new ARPInfo(ipAddress, macAddress, (ipAddress.IsIPv6Multicast || IPv4AddressHelper.IsMulticast(ipAddress)))); } return list; diff --git a/Source/NETworkManager/Models/Network/ARPTableInfo.cs b/Source/NETworkManager/Models/Network/ARPInfo.cs similarity index 79% rename from Source/NETworkManager/Models/Network/ARPTableInfo.cs rename to Source/NETworkManager/Models/Network/ARPInfo.cs index e5b7f67e21..86c9dbf6a2 100644 --- a/Source/NETworkManager/Models/Network/ARPTableInfo.cs +++ b/Source/NETworkManager/Models/Network/ARPInfo.cs @@ -4,7 +4,7 @@ namespace NETworkManager.Models.Network { - public class ARPTableInfo + public class ARPInfo { public IPAddress IPAddress { get; set; } public PhysicalAddress MACAddress { get; set; } @@ -12,12 +12,12 @@ public class ARPTableInfo public int IPAddressInt32 => IPAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? IPv4AddressHelper.ConvertToInt32(IPAddress) : 0; - public ARPTableInfo() + public ARPInfo() { } - public ARPTableInfo(IPAddress ipAddress, PhysicalAddress macAddress, bool isMulticast) + public ARPInfo(IPAddress ipAddress, PhysicalAddress macAddress, bool isMulticast) { IPAddress = ipAddress; MACAddress = macAddress; diff --git a/Source/NETworkManager/Models/Network/IPScanner.cs b/Source/NETworkManager/Models/Network/IPScanner.cs index f80998fd5f..9fee3f12f4 100644 --- a/Source/NETworkManager/Models/Network/IPScanner.cs +++ b/Source/NETworkManager/Models/Network/IPScanner.cs @@ -128,7 +128,7 @@ public void ScanAsync(IPAddress[] ipAddresses, IPScannerOptions ipScannerOptions if (ipScannerOptions.ResolveMACAddress) { // Get info from arp table - var arpTableInfo = ARPTable.GetTable().FirstOrDefault(p => p.IPAddress.ToString() == ipAddress.ToString()); + var arpTableInfo = ARP.GetTable().FirstOrDefault(p => p.IPAddress.ToString() == ipAddress.ToString()); if (arpTableInfo != null) macAddress = arpTableInfo.MACAddress; diff --git a/Source/NETworkManager/Models/Settings/SettingsInfo.cs b/Source/NETworkManager/Models/Settings/SettingsInfo.cs index 35e2173dd9..a11afbcd69 100644 --- a/Source/NETworkManager/Models/Settings/SettingsInfo.cs +++ b/Source/NETworkManager/Models/Settings/SettingsInfo.cs @@ -2854,6 +2854,34 @@ public AutoRefreshTimeInfo ARPTable_AutoRefreshTime SettingsChanged = true; } } + + private string _arpTable_ExportFilePath; + public string ARPTable_ExportFilePath + { + get => _arpTable_ExportFilePath; + set + { + if (value == _arpTable_ExportFilePath) + return; + + _arpTable_ExportFilePath = value; + SettingsChanged = true; + } + } + + private ExportManager.ExportFileType _arpTable_ExportFileType = ExportManager.ExportFileType.CSV; + public ExportManager.ExportFileType ARPTable_ExportFileType + { + get => _arpTable_ExportFileType; + set + { + if (value == _arpTable_ExportFileType) + return; + + _arpTable_ExportFileType = value; + SettingsChanged = true; + } + } #endregion #endregion diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj index 2b18a1aa62..efa6535dfd 100644 --- a/Source/NETworkManager/NETworkManager.csproj +++ b/Source/NETworkManager/NETworkManager.csproj @@ -293,7 +293,7 @@ - + @@ -976,7 +976,7 @@ - + diff --git a/Source/NETworkManager/ViewModels/ARPTableViewModel.cs b/Source/NETworkManager/ViewModels/ARPTableViewModel.cs index cb783815d4..a076fc4b09 100644 --- a/Source/NETworkManager/ViewModels/ARPTableViewModel.cs +++ b/Source/NETworkManager/ViewModels/ARPTableViewModel.cs @@ -1,5 +1,6 @@ using NETworkManager.Models.Network; using System; +using System.Collections; using System.Windows.Input; using System.ComponentModel; using System.Windows.Data; @@ -10,6 +11,7 @@ using NETworkManager.Models.Settings; using System.Windows.Threading; using System.Linq; +using NETworkManager.Models.Export; namespace NETworkManager.ViewModels { @@ -32,42 +34,56 @@ public string Search _search = value; - ARPTableView.Refresh(); + ARPInfoResultsView.Refresh(); OnPropertyChanged(); } } - private ObservableCollection _arpTable = new ObservableCollection(); - public ObservableCollection ARPTable + private ObservableCollection _arpInfoResults = new ObservableCollection(); + public ObservableCollection ARPInfoResults { - get => _arpTable; + get => _arpInfoResults; set { - if (value == _arpTable) + if (value == _arpInfoResults) return; - _arpTable = value; + _arpInfoResults = value; OnPropertyChanged(); } } - public ICollectionView ARPTableView { get; } + public ICollectionView ARPInfoResultsView { get; } - private ARPTableInfo _selectedARPTableInfo; - public ARPTableInfo SelectedARPTableInfo + private ARPInfo _selectedARPInfo; + public ARPInfo SelectedARPInfo { - get => _selectedARPTableInfo; + get => _selectedARPInfo; set { - if (value == _selectedARPTableInfo) + if (value == _selectedARPInfo) return; - _selectedARPTableInfo = value; + _selectedARPInfo = value; OnPropertyChanged(); } } + private IList _selectedARPInfos = new ArrayList(); + public IList SelectedARPInfos + { + get => _selectedARPInfos; + set + { + if (Equals(value, _selectedARPInfos)) + return; + + _selectedARPInfos = value; + OnPropertyChanged(); + } + } + private bool _autoRefresh; public bool AutoRefresh { @@ -167,11 +183,11 @@ public ARPTableViewModel(IDialogCoordinator instance) _isLoading = true; _dialogCoordinator = instance; - ARPTableView = CollectionViewSource.GetDefaultView(ARPTable); - ARPTableView.SortDescriptions.Add(new SortDescription(nameof(ARPTableInfo.IPAddressInt32), ListSortDirection.Ascending)); - ARPTableView.Filter = o => + ARPInfoResultsView = CollectionViewSource.GetDefaultView(ARPInfoResults); + ARPInfoResultsView.SortDescriptions.Add(new SortDescription(nameof(ARPInfo.IPAddressInt32), ListSortDirection.Ascending)); + ARPInfoResultsView.Filter = o => { - if (!(o is ARPTableInfo info)) + if (!(o is ARPInfo info)) return false; if (string.IsNullOrEmpty(Search)) @@ -228,7 +244,7 @@ private async void DeleteTableAction() try { - var arpTable = new ARPTable(); + var arpTable = new ARP(); arpTable.UserHasCanceled += ArpTable_UserHasCanceled; @@ -254,11 +270,11 @@ private async void DeleteEntryAction() try { - var arpTable = new ARPTable(); + var arpTable = new ARP(); arpTable.UserHasCanceled += ArpTable_UserHasCanceled; - await arpTable.DeleteEntryAsync(SelectedARPTableInfo.IPAddress.ToString()); + await arpTable.DeleteEntryAsync(SelectedARPInfo.IPAddress.ToString()); Refresh(); } @@ -289,7 +305,7 @@ private async void AddEntryAction() try { - var arpTable = new ARPTable(); + var arpTable = new ARP(); arpTable.UserHasCanceled += ArpTable_UserHasCanceled; @@ -322,7 +338,7 @@ public ICommand CopySelectedIPAddressCommand private void CopySelectedIPAddressAction() { - CommonMethods.SetClipboard(SelectedARPTableInfo.IPAddress.ToString()); + CommonMethods.SetClipboard(SelectedARPInfo.IPAddress.ToString()); } public ICommand CopySelectedMACAddressCommand @@ -332,7 +348,7 @@ public ICommand CopySelectedMACAddressCommand private void CopySelectedMACAddressAction() { - CommonMethods.SetClipboard(MACAddressHelper.GetDefaultFormat(SelectedARPTableInfo.MACAddress.ToString())); + CommonMethods.SetClipboard(MACAddressHelper.GetDefaultFormat(SelectedARPInfo.MACAddress.ToString())); } public ICommand CopySelectedMulticastCommand @@ -342,7 +358,47 @@ public ICommand CopySelectedMulticastCommand private void CopySelectedMulticastAction() { - CommonMethods.SetClipboard(SelectedARPTableInfo.IsMulticast ? Resources.Localization.Strings.Yes : Resources.Localization.Strings.No); + CommonMethods.SetClipboard(SelectedARPInfo.IsMulticast ? Resources.Localization.Strings.Yes : Resources.Localization.Strings.No); + } + + public ICommand ExportCommand + { + get { return new RelayCommand(p => ExportAction()); } + } + + private async void ExportAction() + { + var customDialog = new CustomDialog + { + Title = Resources.Localization.Strings.Export + }; + + var exportViewModel = new ExportViewModel(async instance => + { + await _dialogCoordinator.HideMetroDialogAsync(this, customDialog); + + try + { + ExportManager.Export(instance.FilePath, instance.FileType, instance.ExportAll ? ARPInfoResults : new ObservableCollection(SelectedARPInfos.Cast().ToArray())); + } + catch (Exception ex) + { + var settings = AppearanceManager.MetroDialog; + settings.AffirmativeButtonText = Resources.Localization.Strings.OK; + + await _dialogCoordinator.ShowMessageAsync(this, Resources.Localization.Strings.Error, Resources.Localization.Strings.AnErrorOccurredWhileExportingTheData + Environment.NewLine + Environment.NewLine + ex.Message, MessageDialogStyle.Affirmative, settings); + } + + SettingsManager.Current.ARPTable_ExportFileType = instance.FileType; + SettingsManager.Current.ARPTable_ExportFilePath = instance.FilePath; + }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }, SettingsManager.Current.ARPTable_ExportFileType, SettingsManager.Current.ARPTable_ExportFilePath); + + customDialog.Content = new ExportDialog + { + DataContext = exportViewModel + }; + + await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); } #endregion @@ -351,9 +407,9 @@ private async void Refresh() { IsRefreshing = true; - ARPTable.Clear(); + ARPInfoResults.Clear(); - (await Models.Network.ARPTable.GetTableAsync()).ForEach(x => ARPTable.Add(x)); + (await ARP.GetTableAsync()).ForEach(x => ARPInfoResults.Add(x)); IsRefreshing = false; } diff --git a/Source/NETworkManager/Views/ARPTableView.xaml b/Source/NETworkManager/Views/ARPTableView.xaml index 65fb62c47b..2ba50659e5 100644 --- a/Source/NETworkManager/Views/ARPTableView.xaml +++ b/Source/NETworkManager/Views/ARPTableView.xaml @@ -11,6 +11,7 @@ xmlns:network="clr-namespace:NETworkManager.Models.Network" xmlns:utilities="clr-namespace:NETworkManager.Utilities" xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" + xmlns:controls="clr-namespace:NETworkManager.Controls" dialogs:DialogParticipation.Register="{Binding}" d:DataContext="{d:DesignInstance viewModels:ARPTableViewModel}" mc:Ignorable="d"> @@ -30,8 +31,8 @@ - - + + - - - - - - - + + + + + + + From b66a9bd732edde7ae6adb6a60fd26dd958b5252c Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Tue, 20 Nov 2018 01:26:07 +0100 Subject: [PATCH 33/58] Export added --- .../Models/Export/ExportManager.cs | 70 +++++++++++++- .../Models/Settings/SettingsInfo.cs | 28 ++++++ .../ViewModels/ListenersViewModel.cs | 91 ++++++++++++++++--- .../NETworkManager/Views/ListenersView.xaml | 38 ++++++-- .../Views/ListenersView.xaml.cs | 3 +- 5 files changed, 202 insertions(+), 28 deletions(-) diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index 599da8bdca..0f99fa6f01 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -99,10 +99,10 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo CreateCSV(collection, filePath); break; case ExportFileType.XML: - CreateXML(collection, filePath); + CreateXML(collection, filePath); break; case ExportFileType.JSON: - CreateJSON(collection, filePath); + CreateJSON(collection, filePath); break; default: throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); @@ -127,6 +127,23 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo } } + public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) + { + switch (fileType) + { + case ExportFileType.CSV: + CreateCSV(collection, filePath); + break; + case ExportFileType.XML: + CreateXML(collection, filePath); + break; + case ExportFileType.JSON: + CreateJSON(collection, filePath); + break; + default: + throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); + } + } public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) { @@ -220,6 +237,17 @@ private static void CreateCSV(IEnumerable collection, string f System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); } + private static void CreateCSV(IEnumerable collection, string filePath) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.AppendLine($"{nameof(ListenerInfo.Protocol)},{nameof(ListenerInfo.IPAddress)},{nameof(ListenerInfo.Port)}"); + + foreach (var info in collection) + stringBuilder.AppendLine($"{info.Protocol},{info.IPAddress},{info.Port}"); + + System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); + } private static void CreateCSV(IEnumerable collection, string filePath) { @@ -334,7 +362,7 @@ from info in collection new XElement(nameof(DNSLookupRecordInfo), new XElement(nameof(DNSLookupRecordInfo.Name), info.Name), new XElement(nameof(DNSLookupRecordInfo.TTL), info.TTL), - new XElement(nameof(DNSLookupRecordInfo.Class), info.Class ), + new XElement(nameof(DNSLookupRecordInfo.Class), info.Class), new XElement(nameof(DNSLookupRecordInfo.Type), info.Type), new XElement(nameof(DNSLookupRecordInfo.Result), info.Result), new XElement(nameof(DNSLookupRecordInfo.DNSServer), info.DNSServer), @@ -342,7 +370,7 @@ from info in collection document.Save(filePath); } - + public static void CreateXML(IEnumerable collection, string filePath) { var document = new XDocument(DefaultXDeclaration, @@ -359,6 +387,23 @@ from info in collection document.Save(filePath); } + public static void CreateXML(IEnumerable collection, string filePath) + { + var document = new XDocument(DefaultXDeclaration, + + new XElement(ApplicationViewManager.Name.Traceroute.ToString(), + new XElement(nameof(ListenerInfo) + "s", + + from info in collection + select + new XElement(nameof(ListenerInfo), + new XElement(nameof(ListenerInfo.Protocol), info.Protocol), + new XElement(nameof(ListenerInfo.IPAddress), info.IPAddress), + new XElement(nameof(ListenerInfo.Port), info.Port))))); + + document.Save(filePath); + } + public static void CreateXML(IEnumerable collection, string filePath) { var document = new XDocument(DefaultXDeclaration, @@ -503,6 +548,23 @@ public static void CreateJSON(ObservableCollection collection, System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); } + public static void CreateJSON(ObservableCollection collection, string filePath) + { + var jsonData = new object[collection.Count]; + + for (var i = 0; i < collection.Count; i++) + { + jsonData[i] = new + { + Protocol = collection[i].Protocol.ToString(), + IPAddress = collection[i].IPAddress.ToString(), + collection[i].Port + }; + } + + System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); + } + public static void CreateJSON(ObservableCollection collection, string filePath) { var jsonData = new object[collection.Count]; diff --git a/Source/NETworkManager/Models/Settings/SettingsInfo.cs b/Source/NETworkManager/Models/Settings/SettingsInfo.cs index a11afbcd69..2c646636f7 100644 --- a/Source/NETworkManager/Models/Settings/SettingsInfo.cs +++ b/Source/NETworkManager/Models/Settings/SettingsInfo.cs @@ -2824,6 +2824,34 @@ public AutoRefreshTimeInfo Listeners_AutoRefreshTime SettingsChanged = true; } } + + private string _listeners_ExportFilePath; + public string Listeners_ExportFilePath + { + get => _listeners_ExportFilePath; + set + { + if (value == _listeners_ExportFilePath) + return; + + _listeners_ExportFilePath = value; + SettingsChanged = true; + } + } + + private ExportManager.ExportFileType _listeners_ExportFileType = ExportManager.ExportFileType.CSV; + public ExportManager.ExportFileType Listeners_ExportFileType + { + get => _listeners_ExportFileType; + set + { + if (value == _listeners_ExportFileType) + return; + + _listeners_ExportFileType = value; + SettingsChanged = true; + } + } #endregion #region ARPTable diff --git a/Source/NETworkManager/ViewModels/ListenersViewModel.cs b/Source/NETworkManager/ViewModels/ListenersViewModel.cs index 5d85055ebe..5104c245d6 100644 --- a/Source/NETworkManager/ViewModels/ListenersViewModel.cs +++ b/Source/NETworkManager/ViewModels/ListenersViewModel.cs @@ -1,20 +1,25 @@ using NETworkManager.Models.Network; using System; +using System.Collections; using System.Windows.Input; using System.ComponentModel; using System.Windows.Data; using System.Collections.ObjectModel; using NETworkManager.Utilities; -using System.Windows; using NETworkManager.Models.Settings; using System.Windows.Threading; using System.Linq; +using MahApps.Metro.Controls.Dialogs; +using NETworkManager.Models.Export; +using NETworkManager.Views; namespace NETworkManager.ViewModels { public class ListenersViewModel : ViewModelBase { #region Variables + private readonly IDialogCoordinator _dialogCoordinator; + private readonly bool _isLoading; private readonly DispatcherTimer _autoRefreshTimer = new DispatcherTimer(); @@ -29,27 +34,27 @@ public string Search _search = value; - ListenersView.Refresh(); + ListenerResultsView.Refresh(); OnPropertyChanged(); } } - private ObservableCollection _listeners = new ObservableCollection(); - public ObservableCollection Listeners + private ObservableCollection _listenerResults = new ObservableCollection(); + public ObservableCollection ListenerResults { - get => _listeners; + get => _listenerResults; set { - if (value == _listeners) + if (value == _listenerResults) return; - _listeners = value; + _listenerResults = value; OnPropertyChanged(); } } - public ICollectionView ListenersView { get; } + public ICollectionView ListenerResultsView { get; } private ListenerInfo _selectedListenerInfo; public ListenerInfo SelectedListenerInfo @@ -65,6 +70,20 @@ public ListenerInfo SelectedListenerInfo } } + private IList _selectedListenerInfos = new ArrayList(); + public IList SelectedListenerInfos + { + get => _selectedListenerInfos; + set + { + if (Equals(value, _selectedListenerInfos)) + return; + + _selectedListenerInfos = value; + OnPropertyChanged(); + } + } + private bool _autoRefresh; public bool AutoRefresh { @@ -159,14 +178,16 @@ public string StatusMessage #endregion #region Contructor, load settings - public ListenersViewModel() + public ListenersViewModel(IDialogCoordinator instance) { _isLoading = true; - ListenersView = CollectionViewSource.GetDefaultView(Listeners); - ListenersView.SortDescriptions.Add(new SortDescription(nameof(ListenerInfo.Protocol), ListSortDirection.Ascending)); - ListenersView.SortDescriptions.Add(new SortDescription(nameof(ListenerInfo.IPAddressInt32), ListSortDirection.Ascending)); - ListenersView.Filter = o => + _dialogCoordinator = instance; + + ListenerResultsView = CollectionViewSource.GetDefaultView(ListenerResults); + ListenerResultsView.SortDescriptions.Add(new SortDescription(nameof(ListenerInfo.Protocol), ListSortDirection.Ascending)); + ListenerResultsView.SortDescriptions.Add(new SortDescription(nameof(ListenerInfo.IPAddressInt32), ListSortDirection.Ascending)); + ListenerResultsView.Filter = o => { if (!(o is ListenerInfo info)) @@ -244,6 +265,46 @@ private void CopySelectedPortAction() { CommonMethods.SetClipboard(SelectedListenerInfo.Port.ToString()); } + + public ICommand ExportCommand + { + get { return new RelayCommand(p => ExportAction()); } + } + + private async void ExportAction() + { + var customDialog = new CustomDialog + { + Title = Resources.Localization.Strings.Export + }; + + var exportViewModel = new ExportViewModel(async instance => + { + await _dialogCoordinator.HideMetroDialogAsync(this, customDialog); + + try + { + ExportManager.Export(instance.FilePath, instance.FileType, instance.ExportAll ? ListenerResults : new ObservableCollection(SelectedListenerInfos.Cast().ToArray())); + } + catch (Exception ex) + { + var settings = AppearanceManager.MetroDialog; + settings.AffirmativeButtonText = Resources.Localization.Strings.OK; + + await _dialogCoordinator.ShowMessageAsync(this, Resources.Localization.Strings.Error, Resources.Localization.Strings.AnErrorOccurredWhileExportingTheData + Environment.NewLine + Environment.NewLine + ex.Message, MessageDialogStyle.Affirmative, settings); + } + + SettingsManager.Current.Listeners_ExportFileType = instance.FileType; + SettingsManager.Current.Listeners_ExportFilePath = instance.FilePath; + }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }, SettingsManager.Current.Listeners_ExportFileType, SettingsManager.Current.Listeners_ExportFilePath); + + customDialog.Content = new ExportDialog + { + DataContext = exportViewModel + }; + + await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); + } #endregion #region Methods @@ -251,9 +312,9 @@ private async void Refresh() { IsRefreshing = true; - Listeners.Clear(); + ListenerResults.Clear(); - (await Listener.GetAllActiveListenersAsync()).ForEach(x => Listeners.Add(x)); + (await Listener.GetAllActiveListenersAsync()).ForEach(x => ListenerResults.Add(x)); IsRefreshing = false; } diff --git a/Source/NETworkManager/Views/ListenersView.xaml b/Source/NETworkManager/Views/ListenersView.xaml index 12d47452c0..9e217e8871 100644 --- a/Source/NETworkManager/Views/ListenersView.xaml +++ b/Source/NETworkManager/Views/ListenersView.xaml @@ -11,6 +11,7 @@ xmlns:network="clr-namespace:NETworkManager.Models.Network" xmlns:utilities="clr-namespace:NETworkManager.Utilities" xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" + xmlns:controls="clr-namespace:NETworkManager.Controls" dialog:DialogParticipation.Register="{Binding}" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:ListenersViewModel}"> @@ -28,8 +29,8 @@ - - + + - - + + - - + + diff --git a/Source/NETworkManager/Views/ListenersView.xaml.cs b/Source/NETworkManager/Views/ListenersView.xaml.cs index 3dc954982d..8dcedef443 100644 --- a/Source/NETworkManager/Views/ListenersView.xaml.cs +++ b/Source/NETworkManager/Views/ListenersView.xaml.cs @@ -1,11 +1,12 @@ using NETworkManager.ViewModels; using System.Windows.Controls; +using MahApps.Metro.Controls.Dialogs; namespace NETworkManager.Views { public partial class ListenersView { - private readonly ListenersViewModel _viewModel = new ListenersViewModel(); + private readonly ListenersViewModel _viewModel = new ListenersViewModel(DialogCoordinator.Instance); public ListenersView() { From 5b25cfd506fd6cb232e8935d72af6f8891fd7658 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Tue, 20 Nov 2018 01:47:35 +0100 Subject: [PATCH 34/58] Export added --- .../Models/Export/ExportManager.cs | 70 ++++++++++++++ .../Models/Network/ConnectionInfo.cs | 6 +- .../Models/Settings/SettingsInfo.cs | 28 ++++++ .../ViewModels/ConnectionsViewModel.cs | 93 +++++++++++++++---- .../NETworkManager/Views/ConnectionsView.xaml | 28 +++++- .../Views/ConnectionsView.xaml.cs | 3 +- 6 files changed, 205 insertions(+), 23 deletions(-) diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index 0f99fa6f01..0c8239412c 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -127,6 +127,24 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo } } + public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) + { + switch (fileType) + { + case ExportFileType.CSV: + CreateCSV(collection, filePath); + break; + case ExportFileType.XML: + CreateXML(collection, filePath); + break; + case ExportFileType.JSON: + CreateJSON(collection, filePath); + break; + default: + throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); + } + } + public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) { switch (fileType) @@ -237,6 +255,18 @@ private static void CreateCSV(IEnumerable collection, string f System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); } + private static void CreateCSV(IEnumerable collection, string filePath) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.AppendLine($"{nameof(ConnectionInfo.Protocol)},{nameof(ConnectionInfo.LocalIPAddress)},{nameof(ConnectionInfo.LocalPort)},{nameof(ConnectionInfo.RemoteIPAddress)},{nameof(ConnectionInfo.RemotePort)},{nameof(ConnectionInfo.TcpState)}"); + + foreach (var info in collection) + stringBuilder.AppendLine($"{info.Protocol},{info.LocalIPAddress},{info.LocalPort},{info.RemoteIPAddress},{info.RemotePort},{info.TcpState}"); + + System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); + } + private static void CreateCSV(IEnumerable collection, string filePath) { var stringBuilder = new StringBuilder(); @@ -387,6 +417,26 @@ from info in collection document.Save(filePath); } + public static void CreateXML(IEnumerable collection, string filePath) + { + var document = new XDocument(DefaultXDeclaration, + + new XElement(ApplicationViewManager.Name.Traceroute.ToString(), + new XElement(nameof(ConnectionInfo) + "s", + + from info in collection + select + new XElement(nameof(ConnectionInfo), + new XElement(nameof(ConnectionInfo.Protocol), info.Protocol), + new XElement(nameof(ConnectionInfo.LocalIPAddress), info.LocalIPAddress), + new XElement(nameof(ConnectionInfo.LocalPort), info.LocalPort), + new XElement(nameof(ConnectionInfo.RemoteIPAddress), info.RemoteIPAddress), + new XElement(nameof(ConnectionInfo.RemotePort), info.RemotePort), + new XElement(nameof(ConnectionInfo.TcpState), info.TcpState))))); + + document.Save(filePath); + } + public static void CreateXML(IEnumerable collection, string filePath) { var document = new XDocument(DefaultXDeclaration, @@ -547,6 +597,26 @@ public static void CreateJSON(ObservableCollection collection, System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); } + + public static void CreateJSON(ObservableCollection collection, string filePath) + { + var jsonData = new object[collection.Count]; + + for (var i = 0; i < collection.Count; i++) + { + jsonData[i] = new + { + Protocol = collection[i].Protocol.ToString(), + LocalIPAddress = collection[i].LocalIPAddress.ToString(), + collection[i].LocalPort, + RemoteIPAddress = collection[i].RemoteIPAddress.ToString(), + collection[i].RemotePort, + TcpState = collection[i].TcpState.ToString() + }; + } + + System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); + } public static void CreateJSON(ObservableCollection collection, string filePath) { diff --git a/Source/NETworkManager/Models/Network/ConnectionInfo.cs b/Source/NETworkManager/Models/Network/ConnectionInfo.cs index 4094a1c3fc..d0b77c3939 100644 --- a/Source/NETworkManager/Models/Network/ConnectionInfo.cs +++ b/Source/NETworkManager/Models/Network/ConnectionInfo.cs @@ -12,7 +12,7 @@ public class ConnectionInfo public int LocalPort { get; set; } public IPAddress RemoteIPAddress { get; set; } public int RemotePort { get; set; } - public TcpState State { get; set; } + public TcpState TcpState { get; set; } public int LocalIPAddressInt32 => LocalIPAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? IPv4AddressHelper.ConvertToInt32(LocalIPAddress) : 0; @@ -23,14 +23,14 @@ public ConnectionInfo() } - public ConnectionInfo(Protocol protocol, IPAddress localIPAddress, int localPort, IPAddress remoteIPAddress, int remotePort, TcpState state) + public ConnectionInfo(Protocol protocol, IPAddress localIPAddress, int localPort, IPAddress remoteIPAddress, int remotePort, TcpState tcpState) { Protocol = protocol; LocalIPAddress = localIPAddress; LocalPort = localPort; RemoteIPAddress = remoteIPAddress; RemotePort = remotePort; - State = state; + TcpState = tcpState; } } } \ No newline at end of file diff --git a/Source/NETworkManager/Models/Settings/SettingsInfo.cs b/Source/NETworkManager/Models/Settings/SettingsInfo.cs index 2c646636f7..9ca158c31e 100644 --- a/Source/NETworkManager/Models/Settings/SettingsInfo.cs +++ b/Source/NETworkManager/Models/Settings/SettingsInfo.cs @@ -2794,6 +2794,34 @@ public AutoRefreshTimeInfo Connections_AutoRefreshTime SettingsChanged = true; } } + + private string _connections_ExportFilePath; + public string Connections_ExportFilePath + { + get => _connections_ExportFilePath; + set + { + if (value == _connections_ExportFilePath) + return; + + _connections_ExportFilePath = value; + SettingsChanged = true; + } + } + + private ExportManager.ExportFileType _connections_ExportFileType = ExportManager.ExportFileType.CSV; + public ExportManager.ExportFileType Connections_ExportFileType + { + get => _connections_ExportFileType; + set + { + if (value == _connections_ExportFileType) + return; + + _connections_ExportFileType = value; + SettingsChanged = true; + } + } #endregion #region Listeners diff --git a/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs b/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs index fbb29727cf..32879492b8 100644 --- a/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs +++ b/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs @@ -1,20 +1,25 @@ using NETworkManager.Models.Network; using System; +using System.Collections; using System.Windows.Input; using System.ComponentModel; using System.Windows.Data; using System.Collections.ObjectModel; using NETworkManager.Utilities; -using System.Windows; using NETworkManager.Models.Settings; using System.Windows.Threading; using System.Linq; +using MahApps.Metro.Controls.Dialogs; +using NETworkManager.Models.Export; +using NETworkManager.Views; namespace NETworkManager.ViewModels { public class ConnectionsViewModel : ViewModelBase { #region Variables + private readonly IDialogCoordinator _dialogCoordinator; + private readonly bool _isLoading; private readonly DispatcherTimer _autoRefreshTimer = new DispatcherTimer(); @@ -29,27 +34,27 @@ public string Search _search = value; - ConnectionsView.Refresh(); + ConnectionResultsView.Refresh(); OnPropertyChanged(); } } - private ObservableCollection _connections = new ObservableCollection(); - public ObservableCollection Connections + private ObservableCollection _connectionResults = new ObservableCollection(); + public ObservableCollection ConnectionResults { - get => _connections; + get => _connectionResults; set { - if (value == _connections) + if (value == _connectionResults) return; - _connections = value; + _connectionResults = value; OnPropertyChanged(); } } - public ICollectionView ConnectionsView { get; } + public ICollectionView ConnectionResultsView { get; } private ConnectionInfo _selectedConnectionInfo; public ConnectionInfo SelectedConnectionInfo @@ -65,6 +70,20 @@ public ConnectionInfo SelectedConnectionInfo } } + private IList _selectedConnectionInfos = new ArrayList(); + public IList SelectedConnectionInfos + { + get => _selectedConnectionInfos; + set + { + if (Equals(value, _selectedConnectionInfos)) + return; + + _selectedConnectionInfos = value; + OnPropertyChanged(); + } + } + private bool _autoRefresh; public bool AutoRefresh { @@ -159,13 +178,15 @@ public string StatusMessage #endregion #region Contructor, load settings - public ConnectionsViewModel() + public ConnectionsViewModel(IDialogCoordinator instance) { _isLoading = true; - ConnectionsView = CollectionViewSource.GetDefaultView(Connections); - ConnectionsView.SortDescriptions.Add(new SortDescription(nameof(ConnectionInfo.LocalIPAddressInt32), ListSortDirection.Ascending)); - ConnectionsView.Filter = o => + _dialogCoordinator = instance; + + ConnectionResultsView = CollectionViewSource.GetDefaultView(ConnectionResults); + ConnectionResultsView.SortDescriptions.Add(new SortDescription(nameof(ConnectionInfo.LocalIPAddressInt32), ListSortDirection.Ascending)); + ConnectionResultsView.Filter = o => { if (string.IsNullOrEmpty(Search)) return true; @@ -173,7 +194,7 @@ public ConnectionsViewModel() var filter = Search.Replace(" ", "").Replace("-", "").Replace(":", ""); // Search by local/remote IP Address, local/remote Port, Protocol and State - return o is ConnectionInfo info && (info.LocalIPAddress.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.LocalPort.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.RemoteIPAddress.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.RemotePort.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.Protocol.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || Resources.Localization.Strings.ResourceManager.GetString("TcpState_" + info.State.ToString(), LocalizationManager.Culture).IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1); + return o is ConnectionInfo info && (info.LocalIPAddress.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.LocalPort.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.RemoteIPAddress.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.RemotePort.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || info.Protocol.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1 || Resources.Localization.Strings.ResourceManager.GetString("TcpState_" + info.TcpState.ToString(), LocalizationManager.Culture).IndexOf(filter, StringComparison.OrdinalIgnoreCase) > -1); }; AutoRefreshTimes = CollectionViewSource.GetDefaultView(AutoRefreshTime.Defaults); @@ -267,7 +288,47 @@ public ICommand CopySelectedStateCommand private void CopySelectedStateAction() { - CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString("TcpState_" + SelectedConnectionInfo.State, LocalizationManager.Culture)); + CommonMethods.SetClipboard(Resources.Localization.Strings.ResourceManager.GetString("TcpState_" + SelectedConnectionInfo.TcpState, LocalizationManager.Culture)); + } + + public ICommand ExportCommand + { + get { return new RelayCommand(p => ExportAction()); } + } + + private async void ExportAction() + { + var customDialog = new CustomDialog + { + Title = Resources.Localization.Strings.Export + }; + + var exportViewModel = new ExportViewModel(async instance => + { + await _dialogCoordinator.HideMetroDialogAsync(this, customDialog); + + try + { + ExportManager.Export(instance.FilePath, instance.FileType, instance.ExportAll ? ConnectionResults : new ObservableCollection(SelectedConnectionInfos.Cast().ToArray())); + } + catch (Exception ex) + { + var settings = AppearanceManager.MetroDialog; + settings.AffirmativeButtonText = Resources.Localization.Strings.OK; + + await _dialogCoordinator.ShowMessageAsync(this, Resources.Localization.Strings.Error, Resources.Localization.Strings.AnErrorOccurredWhileExportingTheData + Environment.NewLine + Environment.NewLine + ex.Message, MessageDialogStyle.Affirmative, settings); + } + + SettingsManager.Current.Connections_ExportFileType = instance.FileType; + SettingsManager.Current.Connections_ExportFilePath = instance.FilePath; + }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }, SettingsManager.Current.Connections_ExportFileType, SettingsManager.Current.Connections_ExportFilePath); + + customDialog.Content = new ExportDialog + { + DataContext = exportViewModel + }; + + await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); } #endregion @@ -276,9 +337,9 @@ private async void Refresh() { IsRefreshing = true; - Connections.Clear(); + ConnectionResults.Clear(); - (await Connection.GetActiveTcpConnectionsAsync()).ForEach(x => Connections.Add(x)); + (await Connection.GetActiveTcpConnectionsAsync()).ForEach(x => ConnectionResults.Add(x)); IsRefreshing = false; } diff --git a/Source/NETworkManager/Views/ConnectionsView.xaml b/Source/NETworkManager/Views/ConnectionsView.xaml index 4f7daae017..ad7d282776 100644 --- a/Source/NETworkManager/Views/ConnectionsView.xaml +++ b/Source/NETworkManager/Views/ConnectionsView.xaml @@ -11,6 +11,7 @@ xmlns:network="clr-namespace:NETworkManager.Models.Network" xmlns:utilities="clr-namespace:NETworkManager.Utilities" xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" + xmlns:controls="clr-namespace:NETworkManager.Controls" dialogs:DialogParticipation.Register="{Binding}" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:ConnectionsViewModel}"> @@ -29,7 +30,7 @@ - + - + - - + + - - + + diff --git a/Source/NETworkManager/Views/LookupOUILookupView.xaml.cs b/Source/NETworkManager/Views/LookupOUILookupView.xaml.cs index f7f85d7ae4..0db2070441 100644 --- a/Source/NETworkManager/Views/LookupOUILookupView.xaml.cs +++ b/Source/NETworkManager/Views/LookupOUILookupView.xaml.cs @@ -1,11 +1,12 @@ using NETworkManager.ViewModels; using System.Windows.Controls; +using MahApps.Metro.Controls.Dialogs; namespace NETworkManager.Views { public partial class LookupOUILookupView { - private readonly LookupOUILookupViewModel _viewModel = new LookupOUILookupViewModel(); + private readonly LookupOUILookupViewModel _viewModel = new LookupOUILookupViewModel(DialogCoordinator.Instance); public LookupOUILookupView() { diff --git a/Source/NETworkManager/Views/LookupPortLookupView.xaml b/Source/NETworkManager/Views/LookupPortLookupView.xaml index 88976ffcdb..82430324ae 100644 --- a/Source/NETworkManager/Views/LookupPortLookupView.xaml +++ b/Source/NETworkManager/Views/LookupPortLookupView.xaml @@ -12,6 +12,9 @@ xmlns:viewModels="clr-namespace:NETworkManager.ViewModels" xmlns:lookup="clr-namespace:NETworkManager.Models.Lookup" xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" + xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro" + xmlns:controls="clr-namespace:NETworkManager.Controls" + dialogs:DialogParticipation.Register="{Binding}" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:LookupPortLookupViewModel}"> @@ -86,8 +89,8 @@ - - + + - - + + - - + + diff --git a/Source/NETworkManager/Views/LookupPortLookupView.xaml.cs b/Source/NETworkManager/Views/LookupPortLookupView.xaml.cs index 781cdca404..0964b2d179 100644 --- a/Source/NETworkManager/Views/LookupPortLookupView.xaml.cs +++ b/Source/NETworkManager/Views/LookupPortLookupView.xaml.cs @@ -1,11 +1,12 @@ using NETworkManager.ViewModels; using System.Windows.Controls; +using MahApps.Metro.Controls.Dialogs; namespace NETworkManager.Views { public partial class LookupPortLookupView { - private readonly LookupPortLookupViewModel _viewModel = new LookupPortLookupViewModel(); + private readonly LookupPortLookupViewModel _viewModel = new LookupPortLookupViewModel(DialogCoordinator.Instance); public LookupPortLookupView() { From efe372f946b5787fb9d106efe97dbacc8d268644 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sat, 24 Nov 2018 03:02:43 +0100 Subject: [PATCH 38/58] Export added / Textbox scrollbar added --- Source/NETworkManager.sln.DotSettings | 1 + .../Models/Export/ExportManager.cs | 24 +++++++-- .../Models/Settings/SettingsInfo.cs | 28 +++++++++++ .../Resources/ContextMenu/ContextMenu.xaml | 41 +++++++++++++++ .../Localization/StaticStrings.Designer.cs | 36 +++++++++++++ .../Resources/Localization/StaticStrings.resx | 12 +++++ .../Localization/Strings.Designer.cs | 27 ---------- .../Resources/Localization/Strings.resx | 9 ---- .../Resources/Styles/ScrollBarStyles.xaml | 16 ++++++ .../ViewModels/ExportViewModel.cs | 43 ++++++++++++++-- .../ViewModels/WhoisViewModel.cs | 50 ++++++++++++++++++- Source/NETworkManager/Views/ExportDialog.xaml | 14 ++++-- Source/NETworkManager/Views/WhoisView.xaml | 24 ++++++++- Source/NETworkManager/Views/WhoisView.xaml.cs | 3 +- 14 files changed, 277 insertions(+), 51 deletions(-) diff --git a/Source/NETworkManager.sln.DotSettings b/Source/NETworkManager.sln.DotSettings index 38ce66f746..ced1c99aac 100644 --- a/Source/NETworkManager.sln.DotSettings +++ b/Source/NETworkManager.sln.DotSettings @@ -27,6 +27,7 @@ TCP TTL PuTTY + TXT VNC XML CIDR diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index c17ccdbd73..4422eb8a40 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -216,6 +216,11 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); } } + + public static void Export(string filePath, string content) + { + CreateTXT(content, filePath); + } #endregion #region CreateCSV @@ -326,7 +331,7 @@ private static void CreateCSV(IEnumerable collection, string fil System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); } - + private static void CreateCSV(IEnumerable collection, string filePath) { var stringBuilder = new StringBuilder(); @@ -492,7 +497,7 @@ from info in collection document.Save(filePath); } - + public static void CreateXML(IEnumerable collection, string filePath) { var document = new XDocument(DefaultXDeclaration, @@ -510,7 +515,7 @@ from info in collection document.Save(filePath); } - + public static void CreateXML(IEnumerable collection, string filePath) { var document = new XDocument(DefaultXDeclaration, @@ -781,6 +786,14 @@ public static void CreateJSON(ObservableCollection collection, string f } #endregion + #region CreateTXT + + public static void CreateTXT(string content, string filePath) + { + System.IO.File.WriteAllText(filePath, content); + } + #endregion + public static string GetFileExtensionAsString(ExportFileType fileExtension) { switch (fileExtension) @@ -791,6 +804,8 @@ public static string GetFileExtensionAsString(ExportFileType fileExtension) return "XML"; case ExportFileType.JSON: return "JSON"; + case ExportFileType.TXT: + return "TXT"; default: return string.Empty; } @@ -801,7 +816,8 @@ public enum ExportFileType { CSV, XML, - JSON + JSON, + TXT } } } diff --git a/Source/NETworkManager/Models/Settings/SettingsInfo.cs b/Source/NETworkManager/Models/Settings/SettingsInfo.cs index e766c3cbd0..8790af2a68 100644 --- a/Source/NETworkManager/Models/Settings/SettingsInfo.cs +++ b/Source/NETworkManager/Models/Settings/SettingsInfo.cs @@ -2820,6 +2820,34 @@ public bool Whois_ShowStatistics SettingsChanged = true; } } + + private string _whois_ExportFilePath; + public string Whois_ExportFilePath + { + get => _whois_ExportFilePath; + set + { + if (value == _whois_ExportFilePath) + return; + + _whois_ExportFilePath = value; + SettingsChanged = true; + } + } + + private ExportManager.ExportFileType _whois_ExportFileType = ExportManager.ExportFileType.TXT; + public ExportManager.ExportFileType Whois_ExportFileType + { + get => _whois_ExportFileType; + set + { + if (value == _whois_ExportFileType) + return; + + _whois_ExportFileType = value; + SettingsChanged = true; + } + } #endregion #region Connections diff --git a/Source/NETworkManager/Resources/ContextMenu/ContextMenu.xaml b/Source/NETworkManager/Resources/ContextMenu/ContextMenu.xaml index 845896203e..6e18a9949f 100644 --- a/Source/NETworkManager/Resources/ContextMenu/ContextMenu.xaml +++ b/Source/NETworkManager/Resources/ContextMenu/ContextMenu.xaml @@ -83,4 +83,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/NETworkManager/Resources/Localization/StaticStrings.Designer.cs b/Source/NETworkManager/Resources/Localization/StaticStrings.Designer.cs index d9c6ea6e2b..b557ecd467 100644 --- a/Source/NETworkManager/Resources/Localization/StaticStrings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/StaticStrings.Designer.cs @@ -60,6 +60,15 @@ internal StaticStrings() { } } + /// + /// Looks up a localized string similar to CSV. + /// + public static string CSV { + get { + return ResourceManager.GetString("CSV", resourceCulture); + } + } + /// /// Looks up a localized string similar to GitHub. /// @@ -87,6 +96,15 @@ public static string HiddenPassword { } } + /// + /// Looks up a localized string similar to JSON. + /// + public static string JSON { + get { + return ResourceManager.GetString("JSON", resourceCulture); + } + } + /// /// Looks up a localized string similar to NETworkManager. /// @@ -95,5 +113,23 @@ public static string ProductName { return ResourceManager.GetString("ProductName", resourceCulture); } } + + /// + /// Looks up a localized string similar to TXT. + /// + public static string TXT { + get { + return ResourceManager.GetString("TXT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to XML. + /// + public static string XML { + get { + return ResourceManager.GetString("XML", resourceCulture); + } + } } } diff --git a/Source/NETworkManager/Resources/Localization/StaticStrings.resx b/Source/NETworkManager/Resources/Localization/StaticStrings.resx index 2f068d6b21..3b85a592f0 100644 --- a/Source/NETworkManager/Resources/Localization/StaticStrings.resx +++ b/Source/NETworkManager/Resources/Localization/StaticStrings.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + CSV + GitHub @@ -126,7 +129,16 @@ •••••••••••• + + JSON + NETworkManager + + TXT + + + XML + \ No newline at end of file diff --git a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs index 9bd861bb3c..44a463dbff 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs @@ -1005,15 +1005,6 @@ public static string Crimson { } } - /// - /// Looks up a localized string similar to CSV. - /// - public static string CSV { - get { - return ResourceManager.GetString("CSV", resourceCulture); - } - } - /// /// Looks up a localized string similar to Custom. /// @@ -2850,15 +2841,6 @@ public static string IPv6DefaultGateway { } } - /// - /// Looks up a localized string similar to JSON. - /// - public static string JSON { - get { - return ResourceManager.GetString("JSON", resourceCulture); - } - } - /// /// Looks up a localized string similar to Keyboard. /// @@ -5948,15 +5930,6 @@ public static string WrongPasswordMessage { } } - /// - /// Looks up a localized string similar to XML. - /// - public static string XML { - get { - return ResourceManager.GetString("XML", resourceCulture); - } - } - /// /// Looks up a localized string similar to Yellow. /// diff --git a/Source/NETworkManager/Resources/Localization/Strings.resx b/Source/NETworkManager/Resources/Localization/Strings.resx index e4d34e71e7..9bbba0b975 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.resx +++ b/Source/NETworkManager/Resources/Localization/Strings.resx @@ -2042,15 +2042,9 @@ If you click "Move & Restart", the remaining files will be copied and the ap Export selected - - CSV - Location... - - XML - File @@ -2069,9 +2063,6 @@ If you click "Move & Restart", the remaining files will be copied and the ap An error occurred while exporting the data. See error message for details: - - JSON - Design diff --git a/Source/NETworkManager/Resources/Styles/ScrollBarStyles.xaml b/Source/NETworkManager/Resources/Styles/ScrollBarStyles.xaml index 76e52cd9c8..98bb9bb68b 100644 --- a/Source/NETworkManager/Resources/Styles/ScrollBarStyles.xaml +++ b/Source/NETworkManager/Resources/Styles/ScrollBarStyles.xaml @@ -9,6 +9,22 @@ + + - + @@ -44,9 +49,10 @@ - - - + + + + diff --git a/Source/NETworkManager/Views/WhoisView.xaml b/Source/NETworkManager/Views/WhoisView.xaml index 9429f1f66d..f0a0bb7943 100644 --- a/Source/NETworkManager/Views/WhoisView.xaml +++ b/Source/NETworkManager/Views/WhoisView.xaml @@ -10,6 +10,9 @@ xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks" xmlns:viewModels="clr-namespace:NETworkManager.ViewModels" xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" + xmlns:sys="clr-namespace:System;assembly=mscorlib" + xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro" + dialogs:DialogParticipation.Register="{Binding}" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:WhoisViewModel}" Loaded="UserControl_OnLoaded"> @@ -101,7 +104,24 @@ - + + + + + @@ -133,7 +153,7 @@ - diff --git a/Source/NETworkManager/Views/WhoisView.xaml.cs b/Source/NETworkManager/Views/WhoisView.xaml.cs index df281434f9..33e09c18aa 100644 --- a/Source/NETworkManager/Views/WhoisView.xaml.cs +++ b/Source/NETworkManager/Views/WhoisView.xaml.cs @@ -1,4 +1,5 @@ using System.Windows; +using MahApps.Metro.Controls.Dialogs; using NETworkManager.ViewModels; namespace NETworkManager.Views @@ -11,7 +12,7 @@ public WhoisView(int tabId, string domain = null) { InitializeComponent(); - _viewModel = new WhoisViewModel(tabId, domain); + _viewModel = new WhoisViewModel(DialogCoordinator.Instance, tabId, domain); DataContext = _viewModel; } From 03b8fde32f3e7c21b5a3365ea218135af55d08b8 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sat, 24 Nov 2018 23:21:29 +0100 Subject: [PATCH 39/58] Export added --- .../Models/Settings/SettingsInfo.cs | 28 +++++++++++ .../ViewModels/HTTPHeadersViewModel.cs | 50 ++++++++++++++++++- .../ViewModels/WhoisViewModel.cs | 2 +- .../NETworkManager/Views/HTTPHeadersView.xaml | 25 +++++++++- .../Views/HTTPHeadersView.xaml.cs | 3 +- Source/NETworkManager/Views/WhoisView.xaml | 36 ++++++------- 6 files changed, 121 insertions(+), 23 deletions(-) diff --git a/Source/NETworkManager/Models/Settings/SettingsInfo.cs b/Source/NETworkManager/Models/Settings/SettingsInfo.cs index 8790af2a68..bb332116af 100644 --- a/Source/NETworkManager/Models/Settings/SettingsInfo.cs +++ b/Source/NETworkManager/Models/Settings/SettingsInfo.cs @@ -2549,6 +2549,34 @@ public bool HTTPHeaders_ShowStatistics SettingsChanged = true; } } + + private string _httpHeaders_ExportFilePath; + public string HTTPHeaders_ExportFilePath + { + get => _httpHeaders_ExportFilePath; + set + { + if (value == _httpHeaders_ExportFilePath) + return; + + _httpHeaders_ExportFilePath = value; + SettingsChanged = true; + } + } + + private ExportManager.ExportFileType _httpHeaders_ExportFileType = ExportManager.ExportFileType.TXT; + public ExportManager.ExportFileType HTTPHeaders_ExportFileType + { + get => _httpHeaders_ExportFileType; + set + { + if (value == _httpHeaders_ExportFileType) + return; + + _httpHeaders_ExportFileType = value; + SettingsChanged = true; + } + } #endregion #region Subnet Calculator diff --git a/Source/NETworkManager/ViewModels/HTTPHeadersViewModel.cs b/Source/NETworkManager/ViewModels/HTTPHeadersViewModel.cs index 3244c37cdf..603fd75001 100644 --- a/Source/NETworkManager/ViewModels/HTTPHeadersViewModel.cs +++ b/Source/NETworkManager/ViewModels/HTTPHeadersViewModel.cs @@ -11,12 +11,18 @@ using System.Windows; using NETworkManager.Controls; using Dragablz; +using MahApps.Metro.Controls.Dialogs; +using NETworkManager.Models.Export; +using NETworkManager.Resources.Localization; +using NETworkManager.Views; namespace NETworkManager.ViewModels { public class HTTPHeadersViewModel : ViewModelBase { #region Variables + private readonly IDialogCoordinator _dialogCoordinator; + private readonly int _tabId; private bool _firstLoad = true; @@ -175,10 +181,12 @@ public bool ExpandStatistics #endregion #region Contructor, load settings - public HTTPHeadersViewModel(int tabId, string websiteUri) + public HTTPHeadersViewModel(IDialogCoordinator instance ,int tabId, string websiteUri) { _isLoading = true; + _dialogCoordinator = instance; + _tabId = tabId; WebsiteUri = websiteUri; @@ -220,6 +228,46 @@ private void CheckAction() { Check(); } + + public ICommand ExportCommand + { + get { return new RelayCommand(p => ExportAction()); } + } + + private async void ExportAction() + { + var customDialog = new CustomDialog + { + Title = Strings.Export + }; + + var exportViewModel = new ExportViewModel(async instance => + { + await _dialogCoordinator.HideMetroDialogAsync(this, customDialog); + + try + { + ExportManager.Export(instance.FilePath, Headers); + } + catch (Exception 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.HTTPHeaders_ExportFileType = instance.FileType; + SettingsManager.Current.HTTPHeaders_ExportFilePath = instance.FilePath; + }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }, SettingsManager.Current.HTTPHeaders_ExportFileType, SettingsManager.Current.HTTPHeaders_ExportFilePath); + + customDialog.Content = new ExportDialog + { + DataContext = exportViewModel + }; + + await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); + } #endregion #region Methods diff --git a/Source/NETworkManager/ViewModels/WhoisViewModel.cs b/Source/NETworkManager/ViewModels/WhoisViewModel.cs index 949625e3e4..681c58a18a 100644 --- a/Source/NETworkManager/ViewModels/WhoisViewModel.cs +++ b/Source/NETworkManager/ViewModels/WhoisViewModel.cs @@ -239,7 +239,7 @@ private async void ExportAction() { var customDialog = new CustomDialog { - Title = Resources.Localization.Strings.Export + Title = Strings.Export }; var exportViewModel = new ExportViewModel(async instance => diff --git a/Source/NETworkManager/Views/HTTPHeadersView.xaml b/Source/NETworkManager/Views/HTTPHeadersView.xaml index 3f8fb518fa..7351d11244 100644 --- a/Source/NETworkManager/Views/HTTPHeadersView.xaml +++ b/Source/NETworkManager/Views/HTTPHeadersView.xaml @@ -9,7 +9,11 @@ xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks" xmlns:viewModels="clr-namespace:NETworkManager.ViewModels" - xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" Loaded="UserControl_OnLoaded" + xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro" + dialogs:DialogParticipation.Register="{Binding}" + xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" + xmlns:sys="clr-namespace:System;assembly=mscorlib" + Loaded="UserControl_OnLoaded" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:HTTPHeadersViewModel}"> @@ -101,7 +105,24 @@ - + + + + + diff --git a/Source/NETworkManager/Views/HTTPHeadersView.xaml.cs b/Source/NETworkManager/Views/HTTPHeadersView.xaml.cs index 11238cb6a2..44c7772aff 100644 --- a/Source/NETworkManager/Views/HTTPHeadersView.xaml.cs +++ b/Source/NETworkManager/Views/HTTPHeadersView.xaml.cs @@ -1,4 +1,5 @@ using System.Windows; +using MahApps.Metro.Controls.Dialogs; using NETworkManager.ViewModels; namespace NETworkManager.Views @@ -11,7 +12,7 @@ public HTTPHeadersView(int tabId, string websiteUri = null) { InitializeComponent(); - _viewModel = new HTTPHeadersViewModel(tabId, websiteUri); + _viewModel = new HTTPHeadersViewModel(DialogCoordinator.Instance, tabId, websiteUri); DataContext = _viewModel; } diff --git a/Source/NETworkManager/Views/WhoisView.xaml b/Source/NETworkManager/Views/WhoisView.xaml index f0a0bb7943..e7f75e6f38 100644 --- a/Source/NETworkManager/Views/WhoisView.xaml +++ b/Source/NETworkManager/Views/WhoisView.xaml @@ -104,24 +104,24 @@ - - - - - + + + + + From 00c9ad9134b2fea90a47524cb87dd5bd72043c49 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sat, 24 Nov 2018 23:43:19 +0100 Subject: [PATCH 40/58] Supernet feature removed --- .../Models/Settings/SettingsInfo.cs | 34 +-- Source/NETworkManager/NETworkManager.csproj | 8 - .../Localization/Strings.Designer.cs | 18 -- .../Resources/Localization/Strings.resx | 6 - .../SubnetCalculatorSupernettingViewModel.cs | 289 ------------------ .../SubnetCalculatorWideSubnetViewModel.cs | 5 +- .../Views/SubnetCalculatorHostView.xaml | 3 - .../SubnetCalculatorSupernettingView.xaml | 168 ---------- .../SubnetCalculatorSupernettingView.xaml.cs | 23 -- 9 files changed, 4 insertions(+), 550 deletions(-) delete mode 100644 Source/NETworkManager/ViewModels/SubnetCalculatorSupernettingViewModel.cs delete mode 100644 Source/NETworkManager/Views/SubnetCalculatorSupernettingView.xaml delete mode 100644 Source/NETworkManager/Views/SubnetCalculatorSupernettingView.xaml.cs diff --git a/Source/NETworkManager/Models/Settings/SettingsInfo.cs b/Source/NETworkManager/Models/Settings/SettingsInfo.cs index bb332116af..0884584d65 100644 --- a/Source/NETworkManager/Models/Settings/SettingsInfo.cs +++ b/Source/NETworkManager/Models/Settings/SettingsInfo.cs @@ -2627,36 +2627,6 @@ public ObservableCollection SubnetCalculator_Subnetting_NewSubnetmaskOrC } #endregion - #region Supernetting - private ObservableCollection _subnetCalculator_Supernetting_Subnet1 = new ObservableCollection(); - public ObservableCollection SubnetCalculator_Supernetting_Subnet1 - { - get => _subnetCalculator_Supernetting_Subnet1; - set - { - if (value == _subnetCalculator_Supernetting_Subnet1) - return; - - _subnetCalculator_Supernetting_Subnet1 = value; - SettingsChanged = true; - } - } - - private ObservableCollection _subnetCalculator_Supernetting_Subnet2 = new ObservableCollection(); - public ObservableCollection SubnetCalculator_Supernetting_Subnet2 - { - get => _subnetCalculator_Supernetting_Subnet2; - set - { - if (value == _subnetCalculator_Supernetting_Subnet2) - return; - - _subnetCalculator_Supernetting_Subnet2 = value; - SettingsChanged = true; - } - } - #endregion - #region WideSubnet private ObservableCollection _subnetCalculator_WideSubnet_Subnet1 = new ObservableCollection(); public ObservableCollection SubnetCalculator_WideSubnet_Subnet1 @@ -3106,8 +3076,8 @@ public SettingsInfo() SubnetCalculator_Subnetting_NewSubnetmaskOrCIDRHistory.CollectionChanged += CollectionChanged; // Subnet Calculator / Supernetting - SubnetCalculator_Supernetting_Subnet1.CollectionChanged += CollectionChanged; - SubnetCalculator_Supernetting_Subnet2.CollectionChanged += CollectionChanged; + SubnetCalculator_WideSubnet_Subnet1.CollectionChanged += CollectionChanged; + SubnetCalculator_WideSubnet_Subnet2.CollectionChanged += CollectionChanged; // Lookup / OUI Lookup_OUI_MACAddressOrVendorHistory.CollectionChanged += CollectionChanged; diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj index efa6535dfd..536dfac2b4 100644 --- a/Source/NETworkManager/NETworkManager.csproj +++ b/Source/NETworkManager/NETworkManager.csproj @@ -339,7 +339,6 @@ - @@ -432,9 +431,6 @@ SNMPHostView.xaml - - SubnetCalculatorSupernettingView.xaml - ProfileDialog.xaml @@ -747,10 +743,6 @@ MSBuild:Compile Designer - - MSBuild:Compile - Designer - MSBuild:Compile Designer diff --git a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs index 44a463dbff..d4bebd7ab5 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs @@ -1653,15 +1653,6 @@ public static string EnterSubnetAndANewSubnetmaskToCreateSubnets { } } - /// - /// Looks up a localized string similar to Enter subnets to create a supernet.... - /// - public static string EnterSubnetsToCreateASupernet { - get { - return ResourceManager.GetString("EnterSubnetsToCreateASupernet", resourceCulture); - } - } - /// /// Looks up a localized string similar to Enter subnets to create a wide subnet.... /// @@ -5147,15 +5138,6 @@ public static string Success { } } - /// - /// Looks up a localized string similar to Supernetting. - /// - public static string Supernetting { - get { - return ResourceManager.GetString("Supernetting", resourceCulture); - } - } - /// /// Looks up a localized string similar to Tags. /// diff --git a/Source/NETworkManager/Resources/Localization/Strings.resx b/Source/NETworkManager/Resources/Localization/Strings.resx index 9bbba0b975..6161689f59 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.resx +++ b/Source/NETworkManager/Resources/Localization/Strings.resx @@ -375,9 +375,6 @@ Success! - - Supernetting - Theme @@ -873,9 +870,6 @@ Enter subnet and a new subnet mask to create subnets... - - Enter subnets to create a supernet... - Error checking 'api.github.com', check your network connection! diff --git a/Source/NETworkManager/ViewModels/SubnetCalculatorSupernettingViewModel.cs b/Source/NETworkManager/ViewModels/SubnetCalculatorSupernettingViewModel.cs deleted file mode 100644 index 6c2e104964..0000000000 --- a/Source/NETworkManager/ViewModels/SubnetCalculatorSupernettingViewModel.cs +++ /dev/null @@ -1,289 +0,0 @@ -using System; -using NETworkManager.Models.Settings; -using System.Windows.Input; -using NETworkManager.Utilities; -using System.Windows.Data; -using System.ComponentModel; -using System.Linq; -using System.Net; -using System.Numerics; -using MahApps.Metro.Controls.Dialogs; - -namespace NETworkManager.ViewModels -{ - public class SubnetCalculatorSupernettingViewModel : ViewModelBase - { - #region Variables - private readonly IDialogCoordinator _dialogCoordinator; - - private string _subnet1; - public string Subnet1 - { - get => _subnet1; - set - { - if (value == _subnet1) - return; - - _subnet1 = value; - OnPropertyChanged(); - } - } - - public ICollectionView Subnet1HistoryView { get; } - - private string _subnet2; - public string Subnet2 - { - get => _subnet2; - set - { - if (value == _subnet2) - return; - - _subnet2 = value; - OnPropertyChanged(); - } - } - - public ICollectionView Subnet2HistoryView { get; } - - private bool _isCalculationRunning; - public bool IsCalculationRunning - { - get => _isCalculationRunning; - set - { - if (value == _isCalculationRunning) - return; - - _isCalculationRunning = value; - OnPropertyChanged(); - } - } - - private bool _isResultVisible; - public bool IsResultVisible - { - get => _isResultVisible; - set - { - if (value == _isResultVisible) - return; - - - _isResultVisible = value; - OnPropertyChanged(); - } - } - - private IPAddress _networkAddress; - public IPAddress NetworkAddress - { - get => _networkAddress; - set - { - if (Equals(value, _networkAddress)) - return; - - _networkAddress = value; - OnPropertyChanged(); - } - } - - private IPAddress _broadcast; - public IPAddress Broadcast - { - get => _broadcast; - set - { - if (Equals(value, _broadcast)) - return; - - _broadcast = value; - OnPropertyChanged(); - } - } - - private BigInteger _ipAddresses; - public BigInteger IPAddresses - { - get => _ipAddresses; - set - { - if (value == _ipAddresses) - return; - - _ipAddresses = value; - OnPropertyChanged(); - } - } - - private IPAddress _subnetmask; - public IPAddress Subnetmask - { - get => _subnetmask; - set - { - if (Equals(value, _subnetmask)) - return; - - _subnetmask = value; - OnPropertyChanged(); - } - } - - private int _cidr; - public int CIDR - { - get => _cidr; - set - { - if (value == _cidr) - return; - - _cidr = value; - OnPropertyChanged(); - } - } - - private IPAddress _firstIPAddress; - public IPAddress FirstIPAddress - { - get => _firstIPAddress; - set - { - if (Equals(value, _firstIPAddress)) - return; - - _firstIPAddress = value; - OnPropertyChanged(); - } - } - - private IPAddress _lastIPAddress; - public IPAddress LastIPAddress - { - get => _lastIPAddress; - set - { - if (Equals(value, _lastIPAddress)) - return; - - _lastIPAddress = value; - OnPropertyChanged(); - } - } - - private BigInteger _hosts; - public BigInteger Hosts - { - get => _hosts; - set - { - if (value == _hosts) - return; - - _hosts = value; - OnPropertyChanged(); - } - } - #endregion - - #region Constructor, load settings - public SubnetCalculatorSupernettingViewModel(IDialogCoordinator instance) - { - _dialogCoordinator = instance; - - // Set collection view - Subnet1HistoryView = CollectionViewSource.GetDefaultView(SettingsManager.Current.SubnetCalculator_Supernetting_Subnet1); - Subnet2HistoryView = CollectionViewSource.GetDefaultView(SettingsManager.Current.SubnetCalculator_Supernetting_Subnet2); - } - #endregion - - #region ICommands & Actions - public ICommand CalculateCommand - { - get { return new RelayCommand(p => CalculateAction()); } - } - - private void CalculateAction() - { - Calculate(); - } - #endregion - - #region Methods - private async void Calculate() - { - IsCalculationRunning = true; - - var subnet1 = IPNetwork.Parse(Subnet1); - var subnet2 = IPNetwork.Parse(Subnet2); - - try - { - var subnet = subnet1.Supernet(subnet2); - - NetworkAddress = subnet.Network; - Broadcast = subnet.Broadcast; - Subnetmask = subnet.Netmask; - CIDR = subnet.Cidr; - IPAddresses = subnet.Total; - FirstIPAddress = subnet.FirstUsable; - LastIPAddress = subnet.LastUsable; - Hosts = subnet.Usable; - - IsResultVisible = true; - - AddSubnet1ToHistory(Subnet1); - AddSubnet2ToHistory(Subnet2); - } - catch(ArgumentOutOfRangeException) - { - var settings = AppearanceManager.MetroDialog; - settings.AffirmativeButtonText = Resources.Localization.Strings.OK; - - ConfigurationManager.Current.IsDialogOpen = true; - - await _dialogCoordinator.ShowMessageAsync(this, Resources.Localization.Strings.Note, Resources.Localization.Strings.WideSubnetFeatureNote, MessageDialogStyle.Affirmative, settings); - - ConfigurationManager.Current.IsDialogOpen = false; - } - - IsCalculationRunning = false; - } - - private void AddSubnet1ToHistory(string subnet) - { - // Create the new list - var list = ListHelper.Modify(SettingsManager.Current.SubnetCalculator_Supernetting_Subnet1.ToList(), subnet, SettingsManager.Current.General_HistoryListEntries); - - // Clear the old items - SettingsManager.Current.SubnetCalculator_Supernetting_Subnet1.Clear(); - OnPropertyChanged(nameof(Subnet1)); // Raise property changed again, after the collection has been cleared - - // Fill with the new items - list.ForEach(x => SettingsManager.Current.SubnetCalculator_Supernetting_Subnet1.Add(x)); - } - - private void AddSubnet2ToHistory(string subnet) - { - // Create the new list - var list = ListHelper.Modify(SettingsManager.Current.SubnetCalculator_Supernetting_Subnet2.ToList(), subnet, SettingsManager.Current.General_HistoryListEntries); - - // Clear the old items - SettingsManager.Current.SubnetCalculator_Supernetting_Subnet2.Clear(); - OnPropertyChanged(nameof(Subnet2)); // Raise property changed again, after the collection has been cleared - - // Fill with the new items - list.ForEach(x => SettingsManager.Current.SubnetCalculator_Supernetting_Subnet2.Add(x)); - } - - public void OnShutdown() - { - - } - #endregion - } -} \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs b/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs index 5e6e0d54b4..61ccf2dbd8 100644 --- a/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs +++ b/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs @@ -1,5 +1,4 @@ -using System; -using NETworkManager.Models.Settings; +using NETworkManager.Models.Settings; using System.Windows.Input; using NETworkManager.Utilities; using System.Windows.Data; @@ -221,7 +220,7 @@ private void Calculate() var subnet1 = IPNetwork.Parse(Subnet1); var subnet2 = IPNetwork.Parse(Subnet2); - var subnet = IPNetwork.WideSubnet(new[] { subnet1, subnet2 }); + var subnet = IPNetwork.WideSubnet(new [] {subnet1, subnet2}); //IPNetwork.WideSubnet(new[] { subnet1, subnet2 }); NetworkAddress = subnet.Network; Broadcast = subnet.Broadcast; diff --git a/Source/NETworkManager/Views/SubnetCalculatorHostView.xaml b/Source/NETworkManager/Views/SubnetCalculatorHostView.xaml index aa529e3a0f..4d2d70d0c6 100644 --- a/Source/NETworkManager/Views/SubnetCalculatorHostView.xaml +++ b/Source/NETworkManager/Views/SubnetCalculatorHostView.xaml @@ -23,9 +23,6 @@ - - - diff --git a/Source/NETworkManager/Views/SubnetCalculatorSupernettingView.xaml b/Source/NETworkManager/Views/SubnetCalculatorSupernettingView.xaml deleted file mode 100644 index c122d9657c..0000000000 --- a/Source/NETworkManager/Views/SubnetCalculatorSupernettingView.xaml +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Source/NETworkManager/Views/SubnetCalculatorSupernettingView.xaml.cs b/Source/NETworkManager/Views/SubnetCalculatorSupernettingView.xaml.cs deleted file mode 100644 index ce5549a78d..0000000000 --- a/Source/NETworkManager/Views/SubnetCalculatorSupernettingView.xaml.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MahApps.Metro.Controls.Dialogs; -using NETworkManager.ViewModels; - -namespace NETworkManager.Views -{ - public partial class SubnetCalculatorSupernettingView - { - private readonly SubnetCalculatorSupernettingViewModel _viewModel = new SubnetCalculatorSupernettingViewModel(DialogCoordinator.Instance); - - public SubnetCalculatorSupernettingView() - { - InitializeComponent(); - DataContext = _viewModel; - - Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted; - } - - private void Dispatcher_ShutdownStarted(object sender, System.EventArgs e) - { - _viewModel.OnShutdown(); - } - } -} From 13c9622c5820fc4c34aeaf2e6b53aac9d9280589 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sun, 25 Nov 2018 03:29:17 +0100 Subject: [PATCH 41/58] Export added --- .../Models/Export/ExportManager.cs | 75 +++++++++++++++++++ .../Models/Settings/SettingsInfo.cs | 28 +++++++ .../SubnetCalculatorSubnettingViewModel.cs | 68 ++++++++++++++++- .../Views/SubnetCalculatorSubnettingView.xaml | 38 ++++++++-- 4 files changed, 197 insertions(+), 12 deletions(-) diff --git a/Source/NETworkManager/Models/Export/ExportManager.cs b/Source/NETworkManager/Models/Export/ExportManager.cs index 4422eb8a40..41737c2f2c 100644 --- a/Source/NETworkManager/Models/Export/ExportManager.cs +++ b/Source/NETworkManager/Models/Export/ExportManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Net; using System.Text; using System.Xml.Linq; using Newtonsoft.Json; @@ -127,6 +128,24 @@ public static void Export(string filePath, ExportFileType fileType, ObservableCo } } + public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) + { + switch (fileType) + { + case ExportFileType.CSV: + CreateCSV(collection, filePath); + break; + case ExportFileType.XML: + CreateXML(collection, filePath); + break; + case ExportFileType.JSON: + CreateJSON(collection, filePath); + break; + default: + throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null); + } + } + public static void Export(string filePath, ExportFileType fileType, ObservableCollection collection) { switch (fileType) @@ -296,6 +315,18 @@ private static void CreateCSV(IEnumerable collection, string f System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); } + private static void CreateCSV(IEnumerable collection, string filePath) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.AppendLine($"{nameof(IPNetwork.Network)},{nameof(IPNetwork.Broadcast)},{nameof(IPNetwork.Total)},{nameof(IPNetwork.Netmask)},{nameof(IPNetwork.Cidr)},{nameof(IPNetwork.FirstUsable)},{nameof(IPNetwork.LastUsable)},{nameof(IPNetwork.Usable)}"); + + foreach (var info in collection) + stringBuilder.AppendLine($"{info.Network},{info.Broadcast},{info.Total},{info.Netmask},{info.Cidr},{info.FirstUsable},{info.LastUsable},{info.Usable}"); + + System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); + } + private static void CreateCSV(IEnumerable collection, string filePath) { var stringBuilder = new StringBuilder(); @@ -482,6 +513,28 @@ from info in collection document.Save(filePath); } + public static void CreateXML(IEnumerable collection, string filePath) + { + var document = new XDocument(DefaultXDeclaration, + + new XElement(ApplicationViewManager.Name.SNMP.ToString(), + new XElement(nameof(IPNetwork) + "s", + + from info in collection + select + new XElement(nameof(IPNetwork), + new XElement(nameof(IPNetwork.Network), info.Network), + new XElement(nameof(IPNetwork.Broadcast), info.Broadcast), + new XElement(nameof(IPNetwork.Total), info.Total), + new XElement(nameof(IPNetwork.Netmask), info.Netmask), + new XElement(nameof(IPNetwork.Cidr), info.Cidr), + new XElement(nameof(IPNetwork.FirstUsable), info.FirstUsable), + new XElement(nameof(IPNetwork.LastUsable), info.LastUsable), + new XElement(nameof(IPNetwork.Usable), info.Usable))))); + + document.Save(filePath); + } + public static void CreateXML(IEnumerable collection, string filePath) { var document = new XDocument(DefaultXDeclaration, @@ -697,6 +750,28 @@ public static void CreateJSON(ObservableCollection collection, System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); } + public static void CreateJSON(ObservableCollection collection, string filePath) + { + var jsonData = new object[collection.Count]; + + for (var i = 0; i < collection.Count; i++) + { + jsonData[i] = new + { + Network = collection[i].Network.ToString(), + Broadcast = collection[i].Broadcast.ToString(), + collection[i].Total, + Netmask = collection[i].Netmask.ToString(), + collection[i].Cidr, + FirstUsable = collection[i].FirstUsable.ToString(), + LastUsable = collection[i].LastUsable.ToString(), + collection[i].Usable + }; + } + + System.IO.File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented)); + } + public static void CreateJSON(ObservableCollection collection, string filePath) { var jsonData = new object[collection.Count]; diff --git a/Source/NETworkManager/Models/Settings/SettingsInfo.cs b/Source/NETworkManager/Models/Settings/SettingsInfo.cs index 0884584d65..58cd09c153 100644 --- a/Source/NETworkManager/Models/Settings/SettingsInfo.cs +++ b/Source/NETworkManager/Models/Settings/SettingsInfo.cs @@ -2625,6 +2625,34 @@ public ObservableCollection SubnetCalculator_Subnetting_NewSubnetmaskOrC SettingsChanged = true; } } + + private string _subnetCalculator_Subnetting_ExportFilePath; + public string SubnetCalculator_Subnetting_ExportFilePath + { + get => _subnetCalculator_Subnetting_ExportFilePath; + set + { + if (value == _subnetCalculator_Subnetting_ExportFilePath) + return; + + _subnetCalculator_Subnetting_ExportFilePath = value; + SettingsChanged = true; + } + } + + private ExportManager.ExportFileType _subnetCalculator_Subnetting_ExportFileType = ExportManager.ExportFileType.CSV; + public ExportManager.ExportFileType SubnetCalculator_Subnetting_ExportFileType + { + get => _subnetCalculator_Subnetting_ExportFileType; + set + { + if (value == _subnetCalculator_Subnetting_ExportFileType) + return; + + _subnetCalculator_Subnetting_ExportFileType = value; + SettingsChanged = true; + } + } #endregion #region WideSubnet diff --git a/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs b/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs index 77a2ea8842..a17e1a6ab2 100644 --- a/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs +++ b/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs @@ -9,8 +9,12 @@ using System.Linq; using System.Net; using System; +using System.Collections; using System.Threading.Tasks; using System.Windows.Threading; +using NETworkManager.Models.Export; +using NETworkManager.Models.Network; +using NETworkManager.Views; namespace NETworkManager.ViewModels { @@ -94,7 +98,7 @@ public ObservableCollection SubnetsResult } } - public ICollectionView SubnetsResultView { get; } + public ICollectionView SubnetsResultsView { get; } private IPNetwork _selectedSubnetResult; public IPNetwork SelectedSubnetResult @@ -110,6 +114,22 @@ public IPNetwork SelectedSubnetResult } } + + private IList _selectedSubnetResults = new ArrayList(); + public IList SelectedSubnetResults + { + get => _selectedSubnetResults; + set + { + if (Equals(value, _selectedSubnetResults)) + return; + + _selectedSubnetResults = value; + OnPropertyChanged(); + } + } + + private bool _displayStatusMessage; public bool DisplayStatusMessage { @@ -149,7 +169,7 @@ public SubnetCalculatorSubnettingViewModel(IDialogCoordinator instance) NewSubnetmaskOrCIDRHistoryView = CollectionViewSource.GetDefaultView(SettingsManager.Current.SubnetCalculator_Subnetting_NewSubnetmaskOrCIDRHistory); // Result view - SubnetsResultView = CollectionViewSource.GetDefaultView(SubnetsResult); + SubnetsResultsView = CollectionViewSource.GetDefaultView(SubnetsResult); } #endregion @@ -243,6 +263,46 @@ private void CopySelectedHostAction() { CommonMethods.SetClipboard(SelectedSubnetResult.Usable.ToString()); } + + public ICommand ExportCommand + { + get { return new RelayCommand(p => ExportAction()); } + } + + private async void ExportAction() + { + var customDialog = new CustomDialog + { + Title = Resources.Localization.Strings.Export + }; + + var exportViewModel = new ExportViewModel(async instance => + { + await _dialogCoordinator.HideMetroDialogAsync(this, customDialog); + + try + { + ExportManager.Export(instance.FilePath, instance.FileType, instance.ExportAll ? SubnetsResult : new ObservableCollection(SelectedSubnetResults.Cast().ToArray())); + } + catch (Exception ex) + { + var settings = AppearanceManager.MetroDialog; + settings.AffirmativeButtonText = Resources.Localization.Strings.OK; + + await _dialogCoordinator.ShowMessageAsync(this, Resources.Localization.Strings.Error, Resources.Localization.Strings.AnErrorOccurredWhileExportingTheData + Environment.NewLine + Environment.NewLine + ex.Message, MessageDialogStyle.Affirmative, settings); + } + + SettingsManager.Current.SubnetCalculator_Subnetting_ExportFileType = instance.FileType; + SettingsManager.Current.SubnetCalculator_Subnetting_ExportFilePath = instance.FilePath; + }, instance => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }, SettingsManager.Current.SubnetCalculator_Subnetting_ExportFileType, SettingsManager.Current.SubnetCalculator_Subnetting_ExportFilePath); + + customDialog.Content = new ExportDialog + { + DataContext = exportViewModel + }; + + await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); + } #endregion #region Methods @@ -259,7 +319,7 @@ private async void Subnetting() // Ask the user if there is a large calculation... var baseCidr = subnet.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128; - if (65535 < (Math.Pow(2, baseCidr - subnet.Cidr) / Math.Pow(2, (baseCidr - newCidr)))) + if (65535 < Math.Pow(2, baseCidr - subnet.Cidr) / Math.Pow(2, (baseCidr - newCidr))) { var settings = AppearanceManager.MetroDialog; @@ -281,7 +341,7 @@ await Task.Run(() => { foreach (var network in subnet.Subnet(newCidr)) { - Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate () + Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate { lock (SubnetsResult) SubnetsResult.Add(network); diff --git a/Source/NETworkManager/Views/SubnetCalculatorSubnettingView.xaml b/Source/NETworkManager/Views/SubnetCalculatorSubnettingView.xaml index da8977ebd4..daa2adf8c1 100644 --- a/Source/NETworkManager/Views/SubnetCalculatorSubnettingView.xaml +++ b/Source/NETworkManager/Views/SubnetCalculatorSubnettingView.xaml @@ -12,6 +12,7 @@ xmlns:viewModels="clr-namespace:NETworkManager.ViewModels" xmlns:net="clr-namespace:System.Net;assembly=System.Net.IPNetwork" xmlns:localization="clr-namespace:NETworkManager.Resources.Localization" + xmlns:controls="clr-namespace:NETworkManager.Controls" dialogs:DialogParticipation.Register="{Binding}" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:SubnetCalculatorSubnettingViewModel}"> @@ -134,8 +135,8 @@ - - + + - - + + @@ -239,8 +261,8 @@ - - + + \ No newline at end of file From e8882ea3ebe0dd37d8babe5a14553e2025e4d0ff Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sun, 25 Nov 2018 03:40:13 +0100 Subject: [PATCH 42/58] Fix #151 --- .../Resources/Localization/Strings.Designer.cs | 11 ++++++++++- .../Resources/Localization/Strings.resx | 5 ++++- .../Views/SubnetCalculatorWideSubnetView.xaml | 8 ++++---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs index d4bebd7ab5..79f763d80c 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs @@ -2014,7 +2014,7 @@ public static string ExampleIPv4IPv6Subnet3 { } /// - /// Looks up a localized string similar to 192.168.178.133/22 or 192.168.178.133/255.255.252.0. + /// Looks up a localized string similar to 192.168.178.0/24 or 192.168.178.0/255.255.255.0. /// public static string ExampleIPv4Subnet { get { @@ -2022,6 +2022,15 @@ public static string ExampleIPv4Subnet { } } + /// + /// Looks up a localized string similar to 192.168.179.0/24 or 192.168.179.0/255.255.255.0. + /// + public static string ExampleIPv4Subnet2 { + get { + return ResourceManager.GetString("ExampleIPv4Subnet2", resourceCulture); + } + } + /// /// Looks up a localized string similar to 255.255.255.0. /// diff --git a/Source/NETworkManager/Resources/Localization/Strings.resx b/Source/NETworkManager/Resources/Localization/Strings.resx index 6161689f59..ed7a8fdcc2 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.resx +++ b/Source/NETworkManager/Resources/Localization/Strings.resx @@ -1383,7 +1383,7 @@ If you click "Move & Restart", the remaining files will be copied and the ap 192.168.178.134/255.255.255.0 or 2001:0db9::/64 - 192.168.178.133/22 or 192.168.178.133/255.255.252.0 + 192.168.178.0/24 or 192.168.178.0/255.255.255.0 255.255.255.0 @@ -2075,4 +2075,7 @@ If you click "Move & Restart", the remaining files will be copied and the ap Supernet requires consecutive subnet without missing IP addresses. Try the Wide Subnet feature to create a larger subnet of two smaller ones that are far apart. + + 192.168.179.0/24 or 192.168.179.0/255.255.255.0 + \ No newline at end of file diff --git a/Source/NETworkManager/Views/SubnetCalculatorWideSubnetView.xaml b/Source/NETworkManager/Views/SubnetCalculatorWideSubnetView.xaml index 7756432ef1..760930078c 100644 --- a/Source/NETworkManager/Views/SubnetCalculatorWideSubnetView.xaml +++ b/Source/NETworkManager/Views/SubnetCalculatorWideSubnetView.xaml @@ -46,23 +46,23 @@ - + - + - + - + From a2a3d3fdd8884e1f50b402cc0bf30d76b2e3d882 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Sun, 25 Nov 2018 03:53:13 +0100 Subject: [PATCH 43/58] Moved example strings from Strings.resx to StaticString.resx --- .../Localization/StaticStrings.Designer.cs | 405 ++++++++++++++++++ .../Resources/Localization/StaticStrings.resx | 135 ++++++ .../Localization/Strings.Designer.cs | 405 ------------------ .../Resources/Localization/Strings.resx | 135 ------ .../Views/ARPTableAddEntryDialog.xaml | 4 +- .../Views/CredentialDialog.xaml | 4 +- .../Views/DNSLookupSettingsView.xaml | 2 +- .../NETworkManager/Views/DNSLookupView.xaml | 2 +- .../NETworkManager/Views/DNSServerDialog.xaml | 6 +- Source/NETworkManager/Views/GroupDialog.xaml | 2 +- .../NETworkManager/Views/HTTPHeadersView.xaml | 2 +- .../Views/IPScannerSettingsView.xaml | 4 +- .../NETworkManager/Views/IPScannerView.xaml | 2 +- .../Views/LookupOUILookupView.xaml | 2 +- .../Views/LookupPortLookupView.xaml | 2 +- .../Views/NetworkInterfaceView.xaml | 10 +- Source/NETworkManager/Views/PingView.xaml | 2 +- .../NETworkManager/Views/PortScannerView.xaml | 4 +- .../NETworkManager/Views/ProfileDialog.xaml | 56 +-- .../Views/PuTTYConnectDialog.xaml | 10 +- .../Views/PuTTYSettingsView.xaml | 10 +- .../Views/RemoteDesktopConnectDialog.xaml | 6 +- .../Views/RemoteDesktopSettingsView.xaml | 2 +- .../Views/SNMPSettingsView.xaml | 2 +- Source/NETworkManager/Views/SNMPView.xaml | 8 +- .../Views/SubnetCalculatorCalculatorView.xaml | 2 +- .../Views/SubnetCalculatorSubnettingView.xaml | 4 +- .../Views/SubnetCalculatorWideSubnetView.xaml | 4 +- .../Views/TightVNCConnectDialog.xaml | 4 +- .../Views/TightVNCSettingsView.xaml | 2 +- .../NETworkManager/Views/TracerouteView.xaml | 2 +- .../Views/WakeOnLANSettingsView.xaml | 2 +- .../NETworkManager/Views/WakeOnLANView.xaml | 6 +- Source/NETworkManager/Views/WhoisView.xaml | 2 +- 34 files changed, 625 insertions(+), 625 deletions(-) diff --git a/Source/NETworkManager/Resources/Localization/StaticStrings.Designer.cs b/Source/NETworkManager/Resources/Localization/StaticStrings.Designer.cs index b557ecd467..7afad591a4 100644 --- a/Source/NETworkManager/Resources/Localization/StaticStrings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/StaticStrings.Designer.cs @@ -69,6 +69,411 @@ public static string CSV { } } + /// + /// Looks up a localized string similar to 9600. + /// + public static string ExampleBaud9600 { + get { + return ResourceManager.GetString("ExampleBaud9600", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Windows-Testuser. + /// + public static string ExampleCredentialName { + get { + return ResourceManager.GetString("ExampleCredentialName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to example.com. + /// + public static string ExampleDomain { + get { + return ResourceManager.GetString("ExampleDomain", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Servers. + /// + public static string ExampleGroupServers { + get { + return ResourceManager.GetString("ExampleGroupServers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Switche. + /// + public static string ExampleGroupSwitche { + get { + return ResourceManager.GetString("ExampleGroupSwitche", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SERVER-01. + /// + public static string ExampleHostname { + get { + return ResourceManager.GetString("ExampleHostname", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.178.1; fritz.box. + /// + public static string ExampleHostnameAndOrIPAddress { + get { + return ResourceManager.GetString("ExampleHostnameAndOrIPAddress", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SERVER-01 or 172.16.0.100. + /// + public static string ExampleHostnameOrIPAddress { + get { + return ResourceManager.GetString("ExampleHostnameOrIPAddress", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.1.0/24; 192.168.178.1 - 192.168.178.128; 192.168.[178-179].[1,100,150-200]; 192.168.178.150; server-01/24. + /// + public static string ExampleIPScanRange { + get { + return ResourceManager.GetString("ExampleIPScanRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.178.55. + /// + public static string ExampleIPv4Address { + get { + return ResourceManager.GetString("ExampleIPv4Address", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.178.255. + /// + public static string ExampleIPv4Broadcast { + get { + return ResourceManager.GetString("ExampleIPv4Broadcast", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 8.8.8.8. + /// + public static string ExampleIPv4DNSServer { + get { + return ResourceManager.GetString("ExampleIPv4DNSServer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.178.1; 8.8.8.8; 8.8.4.4. + /// + public static string ExampleIPv4DNSServers { + get { + return ResourceManager.GetString("ExampleIPv4DNSServers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.178.1. + /// + public static string ExampleIPv4Gateway { + get { + return ResourceManager.GetString("ExampleIPv4Gateway", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.178.133/255.255.255.0 or 2001:0db8::/64. + /// + public static string ExampleIPv4IPv6Subnet { + get { + return ResourceManager.GetString("ExampleIPv4IPv6Subnet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.178.134/255.255.255.0 or 2001:0db9::/64. + /// + public static string ExampleIPv4IPv6Subnet2 { + get { + return ResourceManager.GetString("ExampleIPv4IPv6Subnet2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.180.0/24 or 2001:0db9::/54. + /// + public static string ExampleIPv4IPv6Subnet3 { + get { + return ResourceManager.GetString("ExampleIPv4IPv6Subnet3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.178.0/24 or 192.168.178.0/255.255.255.0. + /// + public static string ExampleIPv4Subnet { + get { + return ResourceManager.GetString("ExampleIPv4Subnet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.179.0/24 or 192.168.179.0/255.255.255.0. + /// + public static string ExampleIPv4Subnet2 { + get { + return ResourceManager.GetString("ExampleIPv4Subnet2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 255.255.255.0. + /// + public static string ExampleIPv4Subnetmask { + get { + return ResourceManager.GetString("ExampleIPv4Subnetmask", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /24 or 255.255.255.0. + /// + public static string ExampleIPv4SubnetmaskIPv4CIDR { + get { + return ResourceManager.GetString("ExampleIPv4SubnetmaskIPv4CIDR", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 255.255.255.0 or /64. + /// + public static string ExampleIPv4SubnetmaskIPv6CIDR { + get { + return ResourceManager.GetString("ExampleIPv4SubnetmaskIPv6CIDR", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 192.168.100.0/24. + /// + public static string ExampleIPv4SubnetWithCIDR { + get { + return ResourceManager.GetString("ExampleIPv4SubnetWithCIDR", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 00:F1:21:AB:0B:35. + /// + public static string ExampleMACAddress { + get { + return ResourceManager.GetString("ExampleMACAddress", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 01:23:45:67:89:AB; 01-23-45; AA11BB; 00F1A2C3D4E5; Intel Corp; Asus. + /// + public static string ExampleMACAddressesOrVendor { + get { + return ResourceManager.GetString("ExampleMACAddressesOrVendor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 161. + /// + public static string ExamplePort161 { + get { + return ResourceManager.GetString("ExamplePort161", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 22. + /// + public static string ExamplePort22 { + get { + return ResourceManager.GetString("ExamplePort22", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 23. + /// + public static string ExamplePort23 { + get { + return ResourceManager.GetString("ExamplePort23", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 3389. + /// + public static string ExamplePort3389 { + get { + return ResourceManager.GetString("ExamplePort3389", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 513. + /// + public static string ExamplePort513 { + get { + return ResourceManager.GetString("ExamplePort513", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 53. + /// + public static string ExamplePort53 { + get { + return ResourceManager.GetString("ExamplePort53", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 5900. + /// + public static string ExamplePort5900 { + get { + return ResourceManager.GetString("ExamplePort5900", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 7. + /// + public static string ExamplePort7 { + get { + return ResourceManager.GetString("ExamplePort7", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 22; 80; https; ldaps; 777 - 999; 8080. + /// + public static string ExamplePortPortRangeOrService { + get { + return ResourceManager.GetString("ExamplePortPortRangeOrService", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 22; 80; 443; 500 - 999; 8080. + /// + public static string ExamplePortScanRange { + get { + return ResourceManager.GetString("ExamplePortScanRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Webserver. + /// + public static string ExampleRemoteDesktopProfileName { + get { + return ResourceManager.GetString("ExampleRemoteDesktopProfileName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to COM5. + /// + public static string ExampleSerialLine { + get { + return ResourceManager.GetString("ExampleSerialLine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to public. + /// + public static string ExampleSNMPCommunity { + get { + return ResourceManager.GetString("ExampleSNMPCommunity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 1.3.6.1.2.1.1. + /// + public static string ExampleSNMPOID { + get { + return ResourceManager.GetString("ExampleSNMPOID", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to snmp-user. + /// + public static string ExampleSNMPUsername { + get { + return ResourceManager.GetString("ExampleSNMPUsername", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to fritz.box. + /// + public static string ExampleSuffix { + get { + return ResourceManager.GetString("ExampleSuffix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to server; dmz. + /// + public static string ExampleTags { + get { + return ResourceManager.GetString("ExampleTags", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Admin. + /// + public static string ExampleUsername { + get { + return ResourceManager.GetString("ExampleUsername", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 80; 443; 8080; 8443. + /// + public static string ExampleWebserverPorts { + get { + return ResourceManager.GetString("ExampleWebserverPorts", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to http(s)://example.com/index.html. + /// + public static string ExampleWebsiteUri { + get { + return ResourceManager.GetString("ExampleWebsiteUri", resourceCulture); + } + } + /// /// Looks up a localized string similar to GitHub. /// diff --git a/Source/NETworkManager/Resources/Localization/StaticStrings.resx b/Source/NETworkManager/Resources/Localization/StaticStrings.resx index 3b85a592f0..8f2cdab618 100644 --- a/Source/NETworkManager/Resources/Localization/StaticStrings.resx +++ b/Source/NETworkManager/Resources/Localization/StaticStrings.resx @@ -120,6 +120,141 @@ CSV + + 9600 + + + Windows-Testuser + + + example.com + + + Servers + + + Switche + + + SERVER-01 + + + 192.168.178.1; fritz.box + + + SERVER-01 or 172.16.0.100 + + + 192.168.1.0/24; 192.168.178.1 - 192.168.178.128; 192.168.[178-179].[1,100,150-200]; 192.168.178.150; server-01/24 + + + 192.168.178.55 + + + 192.168.178.255 + + + 8.8.8.8 + + + 192.168.178.1; 8.8.8.8; 8.8.4.4 + + + 192.168.178.1 + + + 192.168.178.133/255.255.255.0 or 2001:0db8::/64 + + + 192.168.178.134/255.255.255.0 or 2001:0db9::/64 + + + 192.168.180.0/24 or 2001:0db9::/54 + + + 192.168.178.0/24 or 192.168.178.0/255.255.255.0 + + + 192.168.179.0/24 or 192.168.179.0/255.255.255.0 + + + 255.255.255.0 + + + /24 or 255.255.255.0 + + + 255.255.255.0 or /64 + + + 192.168.100.0/24 + + + 00:F1:21:AB:0B:35 + + + 01:23:45:67:89:AB; 01-23-45; AA11BB; 00F1A2C3D4E5; Intel Corp; Asus + + + 161 + + + 22 + + + 23 + + + 3389 + + + 513 + + + 53 + + + 5900 + + + 7 + + + 22; 80; https; ldaps; 777 - 999; 8080 + + + 22; 80; 443; 500 - 999; 8080 + + + Webserver + + + COM5 + + + public + + + 1.3.6.1.2.1.1 + + + snmp-user + + + fritz.box + + + server; dmz + + + Admin + + + 80; 443; 8080; 8443 + + + http(s)://example.com/index.html + GitHub diff --git a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs index 79f763d80c..60ddb3577c 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs @@ -1860,411 +1860,6 @@ public static string Everything { } } - /// - /// Looks up a localized string similar to 9600. - /// - public static string ExampleBaud9600 { - get { - return ResourceManager.GetString("ExampleBaud9600", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Windows-Testuser. - /// - public static string ExampleCredentialName { - get { - return ResourceManager.GetString("ExampleCredentialName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to example.com. - /// - public static string ExampleDomain { - get { - return ResourceManager.GetString("ExampleDomain", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Servers. - /// - public static string ExampleGroupServers { - get { - return ResourceManager.GetString("ExampleGroupServers", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Switche. - /// - public static string ExampleGroupSwitche { - get { - return ResourceManager.GetString("ExampleGroupSwitche", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SERVER-01. - /// - public static string ExampleHostname { - get { - return ResourceManager.GetString("ExampleHostname", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.178.1; fritz.box. - /// - public static string ExampleHostnameAndOrIPAddress { - get { - return ResourceManager.GetString("ExampleHostnameAndOrIPAddress", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SERVER-01 or 172.16.0.100. - /// - public static string ExampleHostnameOrIPAddress { - get { - return ResourceManager.GetString("ExampleHostnameOrIPAddress", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.1.0/24; 192.168.178.1 - 192.168.178.128; 192.168.[178-179].[1,100,150-200]; 192.168.178.150; server-01/24. - /// - public static string ExampleIPScanRange { - get { - return ResourceManager.GetString("ExampleIPScanRange", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.178.55. - /// - public static string ExampleIPv4Address { - get { - return ResourceManager.GetString("ExampleIPv4Address", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.178.255. - /// - public static string ExampleIPv4Broadcast { - get { - return ResourceManager.GetString("ExampleIPv4Broadcast", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 8.8.8.8. - /// - public static string ExampleIPv4DNSServer { - get { - return ResourceManager.GetString("ExampleIPv4DNSServer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.178.1; 8.8.8.8; 8.8.4.4. - /// - public static string ExampleIPv4DNSServers { - get { - return ResourceManager.GetString("ExampleIPv4DNSServers", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.178.1. - /// - public static string ExampleIPv4Gateway { - get { - return ResourceManager.GetString("ExampleIPv4Gateway", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.178.133/255.255.255.0 or 2001:0db8::/64. - /// - public static string ExampleIPv4IPv6Subnet { - get { - return ResourceManager.GetString("ExampleIPv4IPv6Subnet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.178.134/255.255.255.0 or 2001:0db9::/64. - /// - public static string ExampleIPv4IPv6Subnet2 { - get { - return ResourceManager.GetString("ExampleIPv4IPv6Subnet2", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.180.0/24 or 2001:0db9::/54. - /// - public static string ExampleIPv4IPv6Subnet3 { - get { - return ResourceManager.GetString("ExampleIPv4IPv6Subnet3", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.178.0/24 or 192.168.178.0/255.255.255.0. - /// - public static string ExampleIPv4Subnet { - get { - return ResourceManager.GetString("ExampleIPv4Subnet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.179.0/24 or 192.168.179.0/255.255.255.0. - /// - public static string ExampleIPv4Subnet2 { - get { - return ResourceManager.GetString("ExampleIPv4Subnet2", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 255.255.255.0. - /// - public static string ExampleIPv4Subnetmask { - get { - return ResourceManager.GetString("ExampleIPv4Subnetmask", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /24 or 255.255.255.0. - /// - public static string ExampleIPv4SubnetmaskIPv4CIDR { - get { - return ResourceManager.GetString("ExampleIPv4SubnetmaskIPv4CIDR", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 255.255.255.0 or /64. - /// - public static string ExampleIPv4SubnetmaskIPv6CIDR { - get { - return ResourceManager.GetString("ExampleIPv4SubnetmaskIPv6CIDR", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 192.168.100.0/24. - /// - public static string ExampleIPv4SubnetWithCIDR { - get { - return ResourceManager.GetString("ExampleIPv4SubnetWithCIDR", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 00:F1:21:AB:0B:35. - /// - public static string ExampleMACAddress { - get { - return ResourceManager.GetString("ExampleMACAddress", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 01:23:45:67:89:AB; 01-23-45; AA11BB; 00F1A2C3D4E5; Intel Corp; Asus. - /// - public static string ExampleMACAddressesOrVendor { - get { - return ResourceManager.GetString("ExampleMACAddressesOrVendor", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 161. - /// - public static string ExamplePort161 { - get { - return ResourceManager.GetString("ExamplePort161", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 22. - /// - public static string ExamplePort22 { - get { - return ResourceManager.GetString("ExamplePort22", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 23. - /// - public static string ExamplePort23 { - get { - return ResourceManager.GetString("ExamplePort23", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 3389. - /// - public static string ExamplePort3389 { - get { - return ResourceManager.GetString("ExamplePort3389", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 513. - /// - public static string ExamplePort513 { - get { - return ResourceManager.GetString("ExamplePort513", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 53. - /// - public static string ExamplePort53 { - get { - return ResourceManager.GetString("ExamplePort53", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 5900. - /// - public static string ExamplePort5900 { - get { - return ResourceManager.GetString("ExamplePort5900", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 7. - /// - public static string ExamplePort7 { - get { - return ResourceManager.GetString("ExamplePort7", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 22; 80; https; ldaps; 777 - 999; 8080. - /// - public static string ExamplePortPortRangeOrService { - get { - return ResourceManager.GetString("ExamplePortPortRangeOrService", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 22; 80; 443; 500 - 999; 8080. - /// - public static string ExamplePortScanRange { - get { - return ResourceManager.GetString("ExamplePortScanRange", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Webserver. - /// - public static string ExampleRemoteDesktopProfileName { - get { - return ResourceManager.GetString("ExampleRemoteDesktopProfileName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to COM5. - /// - public static string ExampleSerialLine { - get { - return ResourceManager.GetString("ExampleSerialLine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to public. - /// - public static string ExampleSNMPCommunity { - get { - return ResourceManager.GetString("ExampleSNMPCommunity", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 1.3.6.1.2.1.1. - /// - public static string ExampleSNMPOID { - get { - return ResourceManager.GetString("ExampleSNMPOID", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to snmp-user. - /// - public static string ExampleSNMPUsername { - get { - return ResourceManager.GetString("ExampleSNMPUsername", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to fritz.box. - /// - public static string ExampleSuffix { - get { - return ResourceManager.GetString("ExampleSuffix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to server; dmz. - /// - public static string ExampleTags { - get { - return ResourceManager.GetString("ExampleTags", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Admin. - /// - public static string ExampleUsername { - get { - return ResourceManager.GetString("ExampleUsername", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 80; 443; 8080; 8443. - /// - public static string ExampleWebserverPorts { - get { - return ResourceManager.GetString("ExampleWebserverPorts", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to http(s)://example.com/index.html. - /// - public static string ExampleWebsiteUri { - get { - return ResourceManager.GetString("ExampleWebsiteUri", resourceCulture); - } - } - /// /// Looks up a localized string similar to Expand. /// diff --git a/Source/NETworkManager/Resources/Localization/Strings.resx b/Source/NETworkManager/Resources/Localization/Strings.resx index ed7a8fdcc2..18346847ec 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.resx +++ b/Source/NETworkManager/Resources/Localization/Strings.resx @@ -1337,129 +1337,6 @@ If you click "Move & Restart", the remaining files will be copied and the ap Everything - - 9600 - - - Windows-Testuser - - - Servers - - - Switche - - - SERVER-01 - - - 192.168.178.1; fritz.box - - - SERVER-01 or 172.16.0.100 - - - 192.168.1.0/24; 192.168.178.1 - 192.168.178.128; 192.168.[178-179].[1,100,150-200]; 192.168.178.150; server-01/24 - - - 192.168.178.55 - - - 192.168.178.255 - - - 8.8.8.8 - - - 192.168.178.1; 8.8.8.8; 8.8.4.4 - - - 192.168.178.1 - - - 192.168.178.133/255.255.255.0 or 2001:0db8::/64 - - - 192.168.178.134/255.255.255.0 or 2001:0db9::/64 - - - 192.168.178.0/24 or 192.168.178.0/255.255.255.0 - - - 255.255.255.0 - - - /24 or 255.255.255.0 - - - 255.255.255.0 or /64 - - - 192.168.100.0/24 - - - 00:F1:21:AB:0B:35 - - - 01:23:45:67:89:AB; 01-23-45; AA11BB; 00F1A2C3D4E5; Intel Corp; Asus - - - 161 - - - 22 - - - 23 - - - 3389 - - - 513 - - - 53 - - - 7 - - - 22; 80; https; ldaps; 777 - 999; 8080 - - - 22; 80; 443; 500 - 999; 8080 - - - Webserver - - - COM5 - - - public - - - 1.3.6.1.2.1.1 - - - snmp-user - - - fritz.box - - - server; dmz - - - Admin - - - 80; 443; 8080; 8443 - - - http(s)://example.com/index.html - Filter... @@ -1958,9 +1835,6 @@ If you click "Move & Restart", the remaining files will be copied and the ap Whois server not found for the domain: "{0}" - - example.com - Routing @@ -1994,9 +1868,6 @@ If you click "Move & Restart", the remaining files will be copied and the ap Restart session - - 5900 - TightVNC @@ -2066,16 +1937,10 @@ If you click "Move & Restart", the remaining files will be copied and the ap Enter subnets to create a wide subnet... - - 192.168.180.0/24 or 2001:0db9::/54 - Wide Subnet Supernet requires consecutive subnet without missing IP addresses. Try the Wide Subnet feature to create a larger subnet of two smaller ones that are far apart. - - 192.168.179.0/24 or 192.168.179.0/255.255.255.0 - \ No newline at end of file diff --git a/Source/NETworkManager/Views/ARPTableAddEntryDialog.xaml b/Source/NETworkManager/Views/ARPTableAddEntryDialog.xaml index 9be8bd659f..dd7e420acb 100644 --- a/Source/NETworkManager/Views/ARPTableAddEntryDialog.xaml +++ b/Source/NETworkManager/Views/ARPTableAddEntryDialog.xaml @@ -30,7 +30,7 @@ - + @@ -41,7 +41,7 @@ - + diff --git a/Source/NETworkManager/Views/CredentialDialog.xaml b/Source/NETworkManager/Views/CredentialDialog.xaml index f668f5a35a..aeef5e25cf 100644 --- a/Source/NETworkManager/Views/CredentialDialog.xaml +++ b/Source/NETworkManager/Views/CredentialDialog.xaml @@ -34,7 +34,7 @@ - + - + - + @@ -814,7 +814,7 @@ - + @@ -824,7 +824,7 @@ - + @@ -835,7 +835,7 @@ - + @@ -904,7 +904,7 @@ - + - + @@ -77,7 +77,7 @@ - + @@ -99,7 +99,7 @@ - + @@ -109,7 +109,7 @@ - + @@ -120,7 +120,7 @@ - + diff --git a/Source/NETworkManager/Views/PuTTYSettingsView.xaml b/Source/NETworkManager/Views/PuTTYSettingsView.xaml index ad105970ef..106cd17d81 100644 --- a/Source/NETworkManager/Views/PuTTYSettingsView.xaml +++ b/Source/NETworkManager/Views/PuTTYSettingsView.xaml @@ -26,7 +26,7 @@ - + @@ -37,7 +37,7 @@ - + @@ -48,7 +48,7 @@ - + @@ -59,9 +59,9 @@ - + - + diff --git a/Source/NETworkManager/Views/RemoteDesktopConnectDialog.xaml b/Source/NETworkManager/Views/RemoteDesktopConnectDialog.xaml index 4a55fe195c..268fa5c74e 100644 --- a/Source/NETworkManager/Views/RemoteDesktopConnectDialog.xaml +++ b/Source/NETworkManager/Views/RemoteDesktopConnectDialog.xaml @@ -43,7 +43,7 @@ - + @@ -53,7 +53,7 @@ - + @@ -88,7 +88,7 @@ - + diff --git a/Source/NETworkManager/Views/RemoteDesktopSettingsView.xaml b/Source/NETworkManager/Views/RemoteDesktopSettingsView.xaml index 3781d80896..7d9a28910d 100644 --- a/Source/NETworkManager/Views/RemoteDesktopSettingsView.xaml +++ b/Source/NETworkManager/Views/RemoteDesktopSettingsView.xaml @@ -60,7 +60,7 @@ - + diff --git a/Source/NETworkManager/Views/SNMPSettingsView.xaml b/Source/NETworkManager/Views/SNMPSettingsView.xaml index d5ff561822..466c7a926d 100644 --- a/Source/NETworkManager/Views/SNMPSettingsView.xaml +++ b/Source/NETworkManager/Views/SNMPSettingsView.xaml @@ -15,7 +15,7 @@ - + diff --git a/Source/NETworkManager/Views/SNMPView.xaml b/Source/NETworkManager/Views/SNMPView.xaml index c61e0553e6..03f3d33a71 100644 --- a/Source/NETworkManager/Views/SNMPView.xaml +++ b/Source/NETworkManager/Views/SNMPView.xaml @@ -61,7 +61,7 @@ - + @@ -75,7 +75,7 @@ - + @@ -97,7 +97,7 @@ - + - + - - - - + diff --git a/Source/NETworkManager/Views/HTTPHeadersHostView.xaml b/Source/NETworkManager/Views/HTTPHeadersHostView.xaml index 9b27d8febd..3681ddc94b 100644 --- a/Source/NETworkManager/Views/HTTPHeadersHostView.xaml +++ b/Source/NETworkManager/Views/HTTPHeadersHostView.xaml @@ -62,26 +62,42 @@ - - - - + diff --git a/Source/NETworkManager/Views/IPScannerHostView.xaml b/Source/NETworkManager/Views/IPScannerHostView.xaml index f40d997ea0..942577e8ac 100644 --- a/Source/NETworkManager/Views/IPScannerHostView.xaml +++ b/Source/NETworkManager/Views/IPScannerHostView.xaml @@ -61,26 +61,42 @@ - - - - + diff --git a/Source/NETworkManager/Views/PingHostView.xaml b/Source/NETworkManager/Views/PingHostView.xaml index 2157a9a7d9..1289f2fbf4 100644 --- a/Source/NETworkManager/Views/PingHostView.xaml +++ b/Source/NETworkManager/Views/PingHostView.xaml @@ -61,210 +61,226 @@ - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + - - - - - + + + + + + + + + - + + + diff --git a/Source/NETworkManager/Views/PortScannerHostView.xaml b/Source/NETworkManager/Views/PortScannerHostView.xaml index 74f92db0bd..6cbe0fd686 100644 --- a/Source/NETworkManager/Views/PortScannerHostView.xaml +++ b/Source/NETworkManager/Views/PortScannerHostView.xaml @@ -61,26 +61,42 @@ - - - - + diff --git a/Source/NETworkManager/Views/PuTTYHostView.xaml b/Source/NETworkManager/Views/PuTTYHostView.xaml index aa4ab36814..5da37e4d24 100644 --- a/Source/NETworkManager/Views/PuTTYHostView.xaml +++ b/Source/NETworkManager/Views/PuTTYHostView.xaml @@ -143,26 +143,42 @@ - - - - + diff --git a/Source/NETworkManager/Views/RemoteDesktopHostView.xaml b/Source/NETworkManager/Views/RemoteDesktopHostView.xaml index 5158f2ad3a..f111bb53f7 100644 --- a/Source/NETworkManager/Views/RemoteDesktopHostView.xaml +++ b/Source/NETworkManager/Views/RemoteDesktopHostView.xaml @@ -80,26 +80,42 @@ - - - - + diff --git a/Source/NETworkManager/Views/SNMPHostView.xaml b/Source/NETworkManager/Views/SNMPHostView.xaml index 99d46d26ba..6cdb35a848 100644 --- a/Source/NETworkManager/Views/SNMPHostView.xaml +++ b/Source/NETworkManager/Views/SNMPHostView.xaml @@ -49,26 +49,42 @@ - - - - + diff --git a/Source/NETworkManager/Views/TightVNCHostView.xaml b/Source/NETworkManager/Views/TightVNCHostView.xaml index c0b050e35a..82acda4571 100644 --- a/Source/NETworkManager/Views/TightVNCHostView.xaml +++ b/Source/NETworkManager/Views/TightVNCHostView.xaml @@ -85,26 +85,42 @@ - - - - + diff --git a/Source/NETworkManager/Views/TracerouteHostView.xaml b/Source/NETworkManager/Views/TracerouteHostView.xaml index 84c38d1ff1..66f98a1af8 100644 --- a/Source/NETworkManager/Views/TracerouteHostView.xaml +++ b/Source/NETworkManager/Views/TracerouteHostView.xaml @@ -61,26 +61,42 @@ - - - - + diff --git a/Source/NETworkManager/Views/WhoisHostView.xaml b/Source/NETworkManager/Views/WhoisHostView.xaml index bae684f0e7..e3e2d19552 100644 --- a/Source/NETworkManager/Views/WhoisHostView.xaml +++ b/Source/NETworkManager/Views/WhoisHostView.xaml @@ -61,26 +61,42 @@ - - - - + @@ -199,54 +215,18 @@ - - - - - - - - + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - - - - - - + + + + + + + - + - - + + diff --git a/Source/NETworkManager/Views/ProfilesView.xaml b/Source/NETworkManager/Views/ProfilesView.xaml index 24d17193a6..eac6a27da7 100644 --- a/Source/NETworkManager/Views/ProfilesView.xaml +++ b/Source/NETworkManager/Views/ProfilesView.xaml @@ -123,19 +123,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + From 4f4f134326fdc19e7707debc0a221a04775422d6 Mon Sep 17 00:00:00 2001 From: BornToBeRoot Date: Thu, 6 Dec 2018 00:02:31 +0100 Subject: [PATCH 52/58] Button added --- .../Resources/Localization/Strings.Designer.cs | 9 +++++++++ .../Resources/Localization/Strings.resx | 3 +++ .../ViewModels/NetworkInterfaceViewModel.cs | 11 +++++++++++ .../Views/NetworkInterfaceView.xaml | 17 +++++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs index a372025266..a2c733e92a 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.Designer.cs +++ b/Source/NETworkManager/Resources/Localization/Strings.Designer.cs @@ -222,6 +222,15 @@ public static string AddEntryDots { } } + /// + /// Looks up a localized string similar to Add IPv4 address.... + /// + public static string AddIPv4AddressDots { + get { + return ResourceManager.GetString("AddIPv4AddressDots", resourceCulture); + } + } + /// /// Looks up a localized string similar to Additional command line. /// diff --git a/Source/NETworkManager/Resources/Localization/Strings.resx b/Source/NETworkManager/Resources/Localization/Strings.resx index 8fe2c3d31d..94d53dc3d9 100644 --- a/Source/NETworkManager/Resources/Localization/Strings.resx +++ b/Source/NETworkManager/Resources/Localization/Strings.resx @@ -1948,4 +1948,7 @@ If you click "Move & Restart", the remaining files will be copied and the ap If you have forgotten your password and want to start over, delete the following file and restart the application. Path: "{0}" + + Add IPv4 address... + \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs index 0f7b56712d..1d17842150 100644 --- a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs +++ b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs @@ -1028,6 +1028,17 @@ private async void IPConfigRenewAction() IsConfigurationRunning = false; } + + public ICommand AddIPv4AddressCommand + { + get { return new RelayCommand(p => AddIPv4AddressAction());} + } + + private void AddIPv4AddressAction() + { + // netsh int ip add address "NIC name" 10.0.0.2 255.255.255.0 + } + #endregion #region Methods diff --git a/Source/NETworkManager/Views/NetworkInterfaceView.xaml b/Source/NETworkManager/Views/NetworkInterfaceView.xaml index a2fcf0b7d9..d4a2c45d93 100644 --- a/Source/NETworkManager/Views/NetworkInterfaceView.xaml +++ b/Source/NETworkManager/Views/NetworkInterfaceView.xaml @@ -576,6 +576,22 @@ + + @@ -623,6 +639,7 @@ + + + + diff --git a/Source/NETworkManager/Views/NetworkInterfaceAddIPAddressDialog.xaml.cs b/Source/NETworkManager/Views/NetworkInterfaceAddIPAddressDialog.xaml.cs new file mode 100644 index 0000000000..1d648079e1 --- /dev/null +++ b/Source/NETworkManager/Views/NetworkInterfaceAddIPAddressDialog.xaml.cs @@ -0,0 +1,16 @@ +namespace NETworkManager.Views +{ + public partial class NetworkInterfaceAddIPAddressDialog + { + public NetworkInterfaceAddIPAddressDialog() + { + InitializeComponent(); + } + + private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e) + { + // Need to be in loaded event, focusmanger won't work... + TextBoxIPAddress.Focus(); + } + } +} diff --git a/Source/NETworkManager/Views/NetworkInterfaceView.xaml b/Source/NETworkManager/Views/NetworkInterfaceView.xaml index 7cbf8ed76d..61455fb3db 100644 --- a/Source/NETworkManager/Views/NetworkInterfaceView.xaml +++ b/Source/NETworkManager/Views/NetworkInterfaceView.xaml @@ -562,7 +562,7 @@ -