From 750e201f30dfc30c54b96938cdacae82453662f3 Mon Sep 17 00:00:00 2001 From: EonaCat Date: Sun, 28 Sep 2025 01:14:33 +0200 Subject: [PATCH] Initial version --- .../Converters/StringToVisibilityConverter.cs | 25 + .../EonaCat.ConnectionMonitor.csproj | 5 + .../Helpers/LeafLetHelper.cs | 918 ++++++++++++++++++ EonaCat.ConnectionMonitor/MainWindow.xaml | 373 ++++--- EonaCat.ConnectionMonitor/MainWindow.xaml.cs | 512 ++++++---- .../Models/ConfiguredConnection.cs | 42 + .../Models/ConnectionEvent.cs | 14 + .../Models/ConnectionInfo.cs | 89 ++ .../Models/CountryConnection.cs | 21 + .../Models/CountryProcess.cs | 8 + .../Models/CountryStatistic.cs | 11 + .../Models/GeolocationInfo.cs | 11 + .../Models/ProcessStatistic.cs | 9 + .../Models/TopProcessInfo.cs | 8 + 14 files changed, 1712 insertions(+), 334 deletions(-) create mode 100644 EonaCat.ConnectionMonitor/Converters/StringToVisibilityConverter.cs create mode 100644 EonaCat.ConnectionMonitor/Helpers/LeafLetHelper.cs create mode 100644 EonaCat.ConnectionMonitor/Models/ConfiguredConnection.cs create mode 100644 EonaCat.ConnectionMonitor/Models/ConnectionEvent.cs create mode 100644 EonaCat.ConnectionMonitor/Models/ConnectionInfo.cs create mode 100644 EonaCat.ConnectionMonitor/Models/CountryConnection.cs create mode 100644 EonaCat.ConnectionMonitor/Models/CountryProcess.cs create mode 100644 EonaCat.ConnectionMonitor/Models/CountryStatistic.cs create mode 100644 EonaCat.ConnectionMonitor/Models/GeolocationInfo.cs create mode 100644 EonaCat.ConnectionMonitor/Models/ProcessStatistic.cs create mode 100644 EonaCat.ConnectionMonitor/Models/TopProcessInfo.cs diff --git a/EonaCat.ConnectionMonitor/Converters/StringToVisibilityConverter.cs b/EonaCat.ConnectionMonitor/Converters/StringToVisibilityConverter.cs new file mode 100644 index 0000000..969d8dc --- /dev/null +++ b/EonaCat.ConnectionMonitor/Converters/StringToVisibilityConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace EonaCat.ConnectionMonitor.Converters +{ + public class StringToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return string.IsNullOrWhiteSpace(value as string) ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + +} diff --git a/EonaCat.ConnectionMonitor/EonaCat.ConnectionMonitor.csproj b/EonaCat.ConnectionMonitor/EonaCat.ConnectionMonitor.csproj index 4b31f99..4e03768 100644 --- a/EonaCat.ConnectionMonitor/EonaCat.ConnectionMonitor.csproj +++ b/EonaCat.ConnectionMonitor/EonaCat.ConnectionMonitor.csproj @@ -9,6 +9,11 @@ EonaCat.ico + + + + + diff --git a/EonaCat.ConnectionMonitor/Helpers/LeafLetHelper.cs b/EonaCat.ConnectionMonitor/Helpers/LeafLetHelper.cs new file mode 100644 index 0000000..c241457 --- /dev/null +++ b/EonaCat.ConnectionMonitor/Helpers/LeafLetHelper.cs @@ -0,0 +1,918 @@ +using EonaCat.ConnectionMonitor.Models; +using EonaCat.Json; +using EonaCat.Json.Linq; +using Microsoft.Web.WebView2.Wpf; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Net.Http; + +namespace EonaCat.ConnectionMonitor.Helpers +{ + internal class LeafLetHelper + { + private readonly Dictionary _countryCentroidCache = new(); + private readonly Dictionary _countryFlagCache = new(); + private readonly HttpClient _mapHttpClient = new HttpClient(); + private WebView2 _mapWebView; + private (double Lat, double Lon)? _localCoord; + + private const string _mapHtml = @" + + + + + + + + + + + + + +
+
Live Map
+
Historical Map
+
+ +
+
+
+
Stats
+
+
Connections: 0 | Top Country: N/A
+ +
+ + + 8s +
+
+ + +
+
Top Connections:
No data yet
+
+
+
+ +
+
+
+ + + 0/0 +
+
+
Stats
+
+
Connections: 0 | Top Country: N/A
+
Top Connections:
No data yet
+
+
+
+ + + + + + + + + +"; + + + public async Task InitializeMapAsync(WebView2 mapWebView) + { + await mapWebView.EnsureCoreWebView2Async(); + + // Disable default dialogs + mapWebView.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false; + + // Disable developer tools and context menu + mapWebView.CoreWebView2.Settings.AreDevToolsEnabled = Debugger.IsAttached; + + // Prevent right-click, F12, Ctrl+Shift+I, Ctrl+R, Ctrl+P + string disableContextAndKeys = @" + document.addEventListener('contextmenu', e => e.preventDefault()); + document.addEventListener('keydown', function(e) { + if ( + e.key === 'F12' || + (e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'i')) || + (e.ctrlKey && (e.key === 'R' || e.key === 'r')) || + (e.ctrlKey && (e.key === 'P' || e.key === 'p')) + ) { + e.preventDefault(); + } + }); + "; + + if (!Debugger.IsAttached) + { + await mapWebView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(disableContextAndKeys); + } + + // Now navigate to the HTML + mapWebView.NavigateToString(_mapHtml); + + _mapWebView = mapWebView; + } + + public async Task<(double Lat, double Lon)?> GetCountryLatLonAsync(string countryCode) + { + if (string.IsNullOrEmpty(countryCode)) return null; + countryCode = countryCode.Trim().ToUpperInvariant(); + _countryCentroidCache.TryGetValue(countryCode, out var cachedLatLon); + + if (cachedLatLon != default) + { + return (cachedLatLon.Lat, cachedLatLon.Lon); + } + + try + { + var url = $"https://restcountries.com/v3.1/alpha/{countryCode}"; + var str = await _mapHttpClient.GetStringAsync(url); + var root = JArray.Parse(str); + if (root.Count > 0) + { + var latlng = root[0]["latlng"] as JArray; + var flagUrl = root[0]["flags"]?["png"]?.ToString(); + if (latlng != null && latlng.Count >= 2) + { + var lat = latlng[0].ToObject(); + var lon = latlng[1].ToObject(); + _countryCentroidCache[countryCode] = (lat, lon); + if (flagUrl != null) + { + _countryFlagCache[countryCode] = flagUrl; + } + return (lat, lon); + } + } + } + catch { } + return null; + } + + private async Task<(double Lat, double Lon)?> GetLocalCoordinatesAsync() + { + if (_localCoord != null) return _localCoord; + + try + { + var ipInfo = await _mapHttpClient.GetStringAsync("http://ip-api.com/json"); + var root = JObject.Parse(ipInfo); + if (root["status"]?.ToString() == "success") + { + _localCoord = (root["lat"].ToObject(), root["lon"].ToObject()); + return _localCoord; + } + } + catch { } + + _localCoord = (0.0, 0.0); + return _localCoord; + } + + public async Task UpdateMapAsync( + ObservableCollection countryStats, + List connections = null) + { + try + { + var localCoord = await GetLocalCoordinatesAsync(); + + // Prepare payload for countries + var payloadCountries = new List(); + foreach (var cs in countryStats) + { + if (string.IsNullOrWhiteSpace(cs.CountryCode)) continue; + + var coord = await GetCountryLatLonAsync(cs.CountryCode); + if (coord == null) continue; + + payloadCountries.Add(new + { + countryCode = cs.CountryCode, + countryName = cs.CountryName, + count = cs.ConnectionCount, + lat = coord.Value.Lat, + lon = coord.Value.Lon, + topProcesses = cs.TopProcesses?.Select(p => new { p.ProcessName, p.ConnectionCount }).ToList(), + flag = _countryFlagCache.ContainsKey(cs.CountryCode) ? _countryFlagCache[cs.CountryCode] : null + }); + } + + // Prepare payload for connections and track live top connections + var payloadConnections = new List(); + var liveConnectionCounts = new Dictionary(); + + foreach (var conn in connections ?? Enumerable.Empty()) + { + var fromCoord = await GetCountryLatLonAsync(conn.FromCountryCode) ?? localCoord ?? (0.0, 0.0); + var toCoord = await GetCountryLatLonAsync(conn.ToCountryCode); + if (toCoord == null) continue; + + var fromCountry = countryStats.FirstOrDefault(c => c.CountryCode == conn.FromCountryCode); + var toCountry = countryStats.FirstOrDefault(c => c.CountryCode == conn.ToCountryCode); + + payloadConnections.Add(new + { + fromLat = fromCoord.Lat, + fromLon = fromCoord.Lon, + toLat = toCoord.Value.Lat, + toLon = toCoord.Value.Lon, + connectionCount = conn.ConnectionCount, + fromCountryCode = conn.FromCountryCode, + fromCountryName = fromCountry?.CountryName, + fromFlagUrl = _countryFlagCache.ContainsKey(conn.FromCountryCode) ? _countryFlagCache[conn.FromCountryCode] : null, + toFlagUrl = _countryFlagCache.ContainsKey(conn.ToCountryCode) ? _countryFlagCache[conn.ToCountryCode] : null, + fromIp = conn.FromIp, + fromTopProcesses = fromCountry?.TopProcesses?.Select(p => new { p.ProcessName, p.ConnectionCount }).ToList(), + toCountryCode = conn.ToCountryCode, + toCountryName = toCountry?.CountryName, + toIp = conn.ToIp, + toTopProcesses = toCountry?.TopProcesses?.Select(p => new { p.ProcessName, p.ConnectionCount }).ToList() + }); + + // Track live top connections + if (!string.IsNullOrEmpty(conn.FromCountryCode) && !string.IsNullOrEmpty(conn.ToCountryCode)) + { + var key = $"{conn.FromCountryCode}→{conn.ToCountryCode}"; + if (!liveConnectionCounts.ContainsKey(key)) + liveConnectionCounts[key] = 0; + liveConnectionCounts[key] += conn.ConnectionCount; + } + } + + // Compute live totals + var totalConnections = countryStats.Sum(c => c.ConnectionCount); + + var topCountry = countryStats + .OrderByDescending(c => c.ConnectionCount) + .FirstOrDefault()?.CountryCode ?? "N/A"; + + var topConnections = liveConnectionCounts + .OrderByDescending(kv => kv.Value) + .Take(10) + .ToDictionary(kv => kv.Key, kv => kv.Value); + + // Send minimal payload to WebView2 + if (_mapWebView?.CoreWebView2 != null) + { + var jsonCountries = JsonHelper.ToJson(payloadCountries); + var jsonConnections = JsonHelper.ToJson(payloadConnections); + var jsonLiveTotals = JsonHelper.ToJson(new + { + totalConnections, + topCountry, + topConnections + }); + + await _mapWebView.CoreWebView2.ExecuteScriptAsync($@" + window.updateMarkersAndLines({jsonCountries}, {jsonConnections}); + window.liveTotals = {jsonLiveTotals}; + "); + } + } + catch (Exception ex) + { + Debug.WriteLine($"UpdateMapAsync error: {ex.Message}"); + } + } + } +} diff --git a/EonaCat.ConnectionMonitor/MainWindow.xaml b/EonaCat.ConnectionMonitor/MainWindow.xaml index 640a254..80661ec 100644 --- a/EonaCat.ConnectionMonitor/MainWindow.xaml +++ b/EonaCat.ConnectionMonitor/MainWindow.xaml @@ -1,10 +1,13 @@  + + + + @@ -231,7 +278,24 @@ - + + + + + + + +