From a1cc269cb29f8936f1394a9f0c1b7d153c56fa1f Mon Sep 17 00:00:00 2001 From: Orestes Zoupanos Date: Wed, 18 Feb 2026 17:41:15 +0000 Subject: [PATCH 01/24] add sln file --- PerformanceMonitor.sln | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 PerformanceMonitor.sln diff --git a/PerformanceMonitor.sln b/PerformanceMonitor.sln new file mode 100644 index 00000000..3d235b77 --- /dev/null +++ b/PerformanceMonitor.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36930.0 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard", "Dashboard\Dashboard.csproj", "{15117113-EE40-8DCA-BC4D-4D4D68604E97}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PerformanceMonitorLite", "Lite\PerformanceMonitorLite.csproj", "{48718A1C-3679-AB80-1D5F-A9C20CC7EF9A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InstallerGui", "InstallerGui\InstallerGui.csproj", "{B9F4D012-5FA6-6773-E3A5-63482B482B9A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lite.Tests", "Lite.Tests\Lite.Tests.csproj", "{C4E76DA8-A115-7291-3AD0-12C648E24FF2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PerformanceMonitorInstaller", "Installer\PerformanceMonitorInstaller.csproj", "{0F1EFD0D-61B6-D475-3A2E-ED7FD83BCE6E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {15117113-EE40-8DCA-BC4D-4D4D68604E97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15117113-EE40-8DCA-BC4D-4D4D68604E97}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15117113-EE40-8DCA-BC4D-4D4D68604E97}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15117113-EE40-8DCA-BC4D-4D4D68604E97}.Release|Any CPU.Build.0 = Release|Any CPU + {48718A1C-3679-AB80-1D5F-A9C20CC7EF9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48718A1C-3679-AB80-1D5F-A9C20CC7EF9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48718A1C-3679-AB80-1D5F-A9C20CC7EF9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48718A1C-3679-AB80-1D5F-A9C20CC7EF9A}.Release|Any CPU.Build.0 = Release|Any CPU + {B9F4D012-5FA6-6773-E3A5-63482B482B9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9F4D012-5FA6-6773-E3A5-63482B482B9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9F4D012-5FA6-6773-E3A5-63482B482B9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9F4D012-5FA6-6773-E3A5-63482B482B9A}.Release|Any CPU.Build.0 = Release|Any CPU + {C4E76DA8-A115-7291-3AD0-12C648E24FF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4E76DA8-A115-7291-3AD0-12C648E24FF2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4E76DA8-A115-7291-3AD0-12C648E24FF2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4E76DA8-A115-7291-3AD0-12C648E24FF2}.Release|Any CPU.Build.0 = Release|Any CPU + {0F1EFD0D-61B6-D475-3A2E-ED7FD83BCE6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F1EFD0D-61B6-D475-3A2E-ED7FD83BCE6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F1EFD0D-61B6-D475-3A2E-ED7FD83BCE6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F1EFD0D-61B6-D475-3A2E-ED7FD83BCE6E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {88C537F3-44C6-43A9-AFDC-65D59D9FB5E7} + EndGlobalSection +EndGlobal From 2ee24e8a099d0c966306375c2782b1cec8193647 Mon Sep 17 00:00:00 2001 From: Orestes Zoupanos Date: Wed, 18 Feb 2026 17:44:33 +0000 Subject: [PATCH 02/24] fix some warnings --- Dashboard/ColumnFilterPopup.xaml.cs | 2 +- Lite/Controls/ColumnFilterPopup.xaml.cs | 2 +- Lite/Services/AlertStateService.cs | 9 +++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Dashboard/ColumnFilterPopup.xaml.cs b/Dashboard/ColumnFilterPopup.xaml.cs index e4d27978..c71d3c3f 100644 --- a/Dashboard/ColumnFilterPopup.xaml.cs +++ b/Dashboard/ColumnFilterPopup.xaml.cs @@ -32,7 +32,7 @@ private void PopulateOperatorComboBox() { OperatorComboBox.Items.Clear(); - foreach (FilterOperator op in Enum.GetValues(typeof(FilterOperator))) + foreach (FilterOperator op in Enum.GetValues()) { OperatorComboBox.Items.Add(new ComboBoxItem { diff --git a/Lite/Controls/ColumnFilterPopup.xaml.cs b/Lite/Controls/ColumnFilterPopup.xaml.cs index b431eb21..3252f2a4 100644 --- a/Lite/Controls/ColumnFilterPopup.xaml.cs +++ b/Lite/Controls/ColumnFilterPopup.xaml.cs @@ -32,7 +32,7 @@ private void PopulateOperatorComboBox() { OperatorComboBox.Items.Clear(); - foreach (FilterOperator op in Enum.GetValues(typeof(FilterOperator))) + foreach (FilterOperator op in Enum.GetValues()) { OperatorComboBox.Items.Add(new ComboBoxItem { diff --git a/Lite/Services/AlertStateService.cs b/Lite/Services/AlertStateService.cs index 02b8d5e3..f25830b0 100644 --- a/Lite/Services/AlertStateService.cs +++ b/Lite/Services/AlertStateService.cs @@ -21,7 +21,7 @@ namespace PerformanceMonitorLite.Services /// public class AlertStateService { - private readonly object _lock = new object(); + private readonly object _lock = new(); private readonly string _stateFilePath; private readonly HashSet _silencedServers; @@ -154,6 +154,11 @@ private bool ShouldShowAlertsInternal(string serverId) return true; } + private static readonly JsonSerializerOptions s_writeOptions = new() + { + WriteIndented = true + }; + private void Save() { try @@ -163,7 +168,7 @@ private void Save() SilencedServers = _silencedServers.ToList(), AcknowledgedAlerts = _acknowledgedAlerts.ToDictionary(kv => kv.Key, kv => kv.Value) }; - var json = JsonSerializer.Serialize(state, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(state, s_writeOptions); File.WriteAllText(_stateFilePath, json); } catch From 9926d1d0c27a879586abde6520d7cfaca7f262be Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:18:00 -0500 Subject: [PATCH 03/24] Add loading overlay and contextual empty states (#106, PR 1) Reusable LoadingOverlay control with spinning animation and randomized loading messages. Wired into DailySummary, Memory, and QueryPerformance tabs. Extracted shared LoadingMessages helper from ServerTab, added EmptyStateMessage style for context-specific empty states. Lite gets the LoadingMessages helper (UI wiring in PR 2). Co-Authored-By: Claude Opus 4.6 --- Dashboard/Controls/DailySummaryContent.xaml | 5 +- .../Controls/DailySummaryContent.xaml.cs | 7 +++ Dashboard/Controls/LoadingOverlay.xaml | 48 +++++++++++++++++++ Dashboard/Controls/LoadingOverlay.xaml.cs | 41 ++++++++++++++++ Dashboard/Controls/MemoryContent.xaml | 9 +++- Dashboard/Controls/MemoryContent.xaml.cs | 14 ++++++ .../Controls/QueryPerformanceContent.xaml | 8 +++- .../Controls/QueryPerformanceContent.xaml.cs | 14 ++++++ Dashboard/Helpers/LoadingMessages.cs | 46 ++++++++++++++++++ Dashboard/ServerTab.xaml.cs | 31 +----------- Dashboard/Themes/DarkTheme.xaml | 9 ++++ Lite/Helpers/LoadingMessages.cs | 43 +++++++++++++++++ 12 files changed, 240 insertions(+), 35 deletions(-) create mode 100644 Dashboard/Controls/LoadingOverlay.xaml create mode 100644 Dashboard/Controls/LoadingOverlay.xaml.cs create mode 100644 Dashboard/Helpers/LoadingMessages.cs create mode 100644 Lite/Helpers/LoadingMessages.cs diff --git a/Dashboard/Controls/DailySummaryContent.xaml b/Dashboard/Controls/DailySummaryContent.xaml index e6d2ab0c..652a8b8f 100644 --- a/Dashboard/Controls/DailySummaryContent.xaml +++ b/Dashboard/Controls/DailySummaryContent.xaml @@ -3,6 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:controls="clr-namespace:PerformanceMonitorDashboard.Controls" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> @@ -144,7 +145,9 @@ - + + diff --git a/Dashboard/Controls/DailySummaryContent.xaml.cs b/Dashboard/Controls/DailySummaryContent.xaml.cs index 802c1a47..eda60644 100644 --- a/Dashboard/Controls/DailySummaryContent.xaml.cs +++ b/Dashboard/Controls/DailySummaryContent.xaml.cs @@ -79,6 +79,9 @@ private async System.Threading.Tasks.Task LoadDailySummaryAsync() try { + DailySummaryLoading.IsLoading = true; + DailySummaryNoDataMessage.Visibility = Visibility.Collapsed; + var data = await _databaseService.GetDailySummaryAsync(_dailySummaryDate); // Store unfiltered data and reset filters when new data is loaded @@ -100,6 +103,10 @@ private async System.Threading.Tasks.Task LoadDailySummaryAsync() { Logger.Error($"Error loading daily summary: {ex.Message}"); } + finally + { + DailySummaryLoading.IsLoading = false; + } } #region Event Handlers diff --git a/Dashboard/Controls/LoadingOverlay.xaml b/Dashboard/Controls/LoadingOverlay.xaml new file mode 100644 index 00000000..4fbbeaa6 --- /dev/null +++ b/Dashboard/Controls/LoadingOverlay.xaml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Dashboard/Controls/LoadingOverlay.xaml.cs b/Dashboard/Controls/LoadingOverlay.xaml.cs new file mode 100644 index 00000000..b9b6810f --- /dev/null +++ b/Dashboard/Controls/LoadingOverlay.xaml.cs @@ -0,0 +1,41 @@ +using System.Windows; +using System.Windows.Controls; +using PerformanceMonitorDashboard.Helpers; + +namespace PerformanceMonitorDashboard.Controls +{ + public partial class LoadingOverlay : UserControl + { + public static readonly DependencyProperty IsLoadingProperty = + DependencyProperty.Register( + nameof(IsLoading), + typeof(bool), + typeof(LoadingOverlay), + new PropertyMetadata(false, OnIsLoadingChanged)); + + public bool IsLoading + { + get => (bool)GetValue(IsLoadingProperty); + set => SetValue(IsLoadingProperty, value); + } + + public LoadingOverlay() + { + InitializeComponent(); + } + + private static void OnIsLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is LoadingOverlay overlay) + { + var isLoading = (bool)e.NewValue; + overlay.Visibility = isLoading ? Visibility.Visible : Visibility.Collapsed; + + if (isLoading) + { + overlay.MessageText.Text = LoadingMessages.GetRandom(); + } + } + } + } +} diff --git a/Dashboard/Controls/MemoryContent.xaml b/Dashboard/Controls/MemoryContent.xaml index 298686c1..bae6cf3b 100644 --- a/Dashboard/Controls/MemoryContent.xaml +++ b/Dashboard/Controls/MemoryContent.xaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF" + xmlns:controls="clr-namespace:PerformanceMonitorDashboard.Controls" mc:Ignorable="d" d:DesignHeight="600" d:DesignWidth="1200"> @@ -90,7 +91,9 @@ - + + @@ -116,7 +119,9 @@ - + + diff --git a/Dashboard/Controls/MemoryContent.xaml.cs b/Dashboard/Controls/MemoryContent.xaml.cs index b9c7c9e6..f2b35d6f 100644 --- a/Dashboard/Controls/MemoryContent.xaml.cs +++ b/Dashboard/Controls/MemoryContent.xaml.cs @@ -383,6 +383,9 @@ private async System.Threading.Tasks.Task RefreshMemoryGrantsAsync() try { + MemoryGrantsLoading.IsLoading = true; + MemoryGrantsNoDataMessage.Visibility = Visibility.Collapsed; + var data = await _databaseService.GetMemoryGrantStatsAsync(_memoryGrantsHoursBack, _memoryGrantsFromDate, _memoryGrantsToDate); var dataList = data.ToList(); MemoryGrantsNoDataMessage.Visibility = dataList.Count == 0 ? Visibility.Visible : Visibility.Collapsed; @@ -392,6 +395,10 @@ private async System.Threading.Tasks.Task RefreshMemoryGrantsAsync() { Logger.Error($"Error loading memory grants: {ex.Message}"); } + finally + { + MemoryGrantsLoading.IsLoading = false; + } } private void LoadMemoryGrantsChart(IEnumerable data, int hoursBack, DateTime? fromDate, DateTime? toDate) @@ -485,6 +492,9 @@ private async System.Threading.Tasks.Task RefreshMemoryClerksAsync() try { + MemoryClerksLoading.IsLoading = true; + MemoryClerksNoDataMessage.Visibility = Visibility.Collapsed; + var data = await _databaseService.GetMemoryClerksTopNAsync(5, _memoryClerksHoursBack, _memoryClerksFromDate, _memoryClerksToDate); var dataList = data.ToList(); MemoryClerksNoDataMessage.Visibility = dataList.Count == 0 ? Visibility.Visible : Visibility.Collapsed; @@ -494,6 +504,10 @@ private async System.Threading.Tasks.Task RefreshMemoryClerksAsync() { Logger.Error($"Error loading memory clerks: {ex.Message}"); } + finally + { + MemoryClerksLoading.IsLoading = false; + } } private void LoadMemoryClerksChart(List data, int hoursBack, DateTime? fromDate, DateTime? toDate) diff --git a/Dashboard/Controls/QueryPerformanceContent.xaml b/Dashboard/Controls/QueryPerformanceContent.xaml index 12bf9709..ca8c9dd3 100644 --- a/Dashboard/Controls/QueryPerformanceContent.xaml +++ b/Dashboard/Controls/QueryPerformanceContent.xaml @@ -314,7 +314,9 @@ - + + @@ -585,7 +587,9 @@ - + + diff --git a/Dashboard/Controls/QueryPerformanceContent.xaml.cs b/Dashboard/Controls/QueryPerformanceContent.xaml.cs index e903040d..b93a0805 100644 --- a/Dashboard/Controls/QueryPerformanceContent.xaml.cs +++ b/Dashboard/Controls/QueryPerformanceContent.xaml.cs @@ -217,6 +217,9 @@ public async Task RefreshAllDataAsync() if (_databaseService == null) return; + QueryStatsLoading.IsLoading = true; + QueryStatsNoDataMessage.Visibility = Visibility.Collapsed; + // Fetch grid data (summary views aggregated per query/procedure) var queryStatsTask = _databaseService.GetQueryStatsAsync(_queryStatsHoursBack, _queryStatsFromDate, _queryStatsToDate); var procStatsTask = _databaseService.GetProcedureStatsAsync(_procStatsHoursBack, _procStatsFromDate, _procStatsToDate); @@ -263,6 +266,10 @@ await Task.WhenAll( { Logger.Error($"Error refreshing QueryPerformance data: {ex.Message}", ex); } + finally + { + QueryStatsLoading.IsLoading = false; + } } private void SetStatus(string message) @@ -367,7 +374,10 @@ private async Task RefreshActiveQueriesAsync() try { + ActiveQueriesLoading.IsLoading = true; + ActiveQueriesNoDataMessage.Visibility = Visibility.Collapsed; SetStatus("Loading active queries..."); + var data = await _databaseService.GetQuerySnapshotsAsync(_activeQueriesHoursBack, _activeQueriesFromDate, _activeQueriesToDate); ActiveQueriesDataGrid.ItemsSource = data; ActiveQueriesNoDataMessage.Visibility = data.Count == 0 ? Visibility.Visible : Visibility.Collapsed; @@ -378,6 +388,10 @@ private async Task RefreshActiveQueriesAsync() Logger.Error($"Error loading active queries: {ex.Message}"); SetStatus("Error loading active queries"); } + finally + { + ActiveQueriesLoading.IsLoading = false; + } } private void ActiveQueriesFilter_Click(object sender, RoutedEventArgs e) diff --git a/Dashboard/Helpers/LoadingMessages.cs b/Dashboard/Helpers/LoadingMessages.cs new file mode 100644 index 00000000..0f4f9102 --- /dev/null +++ b/Dashboard/Helpers/LoadingMessages.cs @@ -0,0 +1,46 @@ +using System; + +namespace PerformanceMonitorDashboard.Helpers +{ + /// + /// Randomized loading messages displayed while data is being fetched. + /// + public static class LoadingMessages + { + private static readonly string[] Messages = + [ + "Reticulating splines...", + "Consulting the oracle...", + "Asking the database nicely...", + "Crunching numbers...", + "Thinking really hard...", + "Mulling it over...", + "Communing with SQL Server...", + "Summoning data spirits...", + "Decoding the matrix...", + "Interrogating indexes...", + "Parsing the void...", + "Calibrating flux capacitors...", + "Negotiating with stored procedures...", + "Convincing queries to run faster...", + "Herding cursors...", + "Massaging execution plans...", + "Whispering to wait stats...", + "Befriending buffer pools...", + "Coaxing data from disk...", + "Pondering performance...", + "Untangling spaghetti queries...", + "Poking the query optimizer...", + "Warming up the plan cache...", + "Dusting off statistics...", + "Reasoning with RESOURCE_SEMAPHORE...", + "Apologizing to tempdb...", + "Flattering the cardinality estimator...", + "Bribing the lock manager...", + "Decrypting wait stats hieroglyphics...", + "Teaching cursors to fly...", + ]; + + public static string GetRandom() => Messages[Random.Shared.Next(Messages.Length)]; + } +} diff --git a/Dashboard/ServerTab.xaml.cs b/Dashboard/ServerTab.xaml.cs index 51c17f69..b1d47686 100644 --- a/Dashboard/ServerTab.xaml.cs +++ b/Dashboard/ServerTab.xaml.cs @@ -35,36 +35,7 @@ public partial class ServerTab : UserControl public int UtcOffsetMinutes { get; } public DatabaseService DatabaseService => _databaseService; - // Fun AI-powered loading messages - private static readonly string[] _loadingMessages = new[] - { - "Reticulating splines...", - "Consulting the oracle...", - "Asking the database nicely...", - "Crunching numbers...", - "Thinking really hard...", - "Mulling it over...", - "Communing with SQL Server...", - "Summoning data spirits...", - "Decoding the matrix...", - "Interrogating indexes...", - "Parsing the void...", - "Calibrating flux capacitors...", - "Negotiating with stored procedures...", - "Convincing queries to run faster...", - "Herding cursors...", - "Massaging execution plans...", - "Whispering to wait stats...", - "Befriending buffer pools...", - "Coaxing data from disk...", - "Pondering performance..." - }; - private static readonly Random _loadingRandom = Random.Shared; - - private string GetLoadingMessage() - { - return _loadingMessages[_loadingRandom.Next(_loadingMessages.Length)]; - } + private static string GetLoadingMessage() => LoadingMessages.GetRandom(); private readonly UserPreferencesService _preferencesService; diff --git a/Dashboard/Themes/DarkTheme.xaml b/Dashboard/Themes/DarkTheme.xaml index 686dcef6..7390435c 100644 --- a/Dashboard/Themes/DarkTheme.xaml +++ b/Dashboard/Themes/DarkTheme.xaml @@ -157,6 +157,15 @@ + + + From ddb080f25bfc29e6139e24754007a681ddf556d5 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:38:16 -0500 Subject: [PATCH 15/24] Fix duration chart hover tooltips, Dashboard grid lines, time range button hover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wire ChartHoverHelper Clear/Add calls into all 4 Lite duration chart update methods (collector, query, proc, query store) — hover tooltips now show series name, value, and timestamp - Bump Dashboard chart grid line alpha from 40 to 50 (was invisible) - Add TimeRangeButton style with accent border + text brighten on hover for the hour picker buttons (1h/4h/8h/12h/24h/7d/30d) Partial fix for #110, fixes Lite #121 Co-Authored-By: Claude Opus 4.6 --- Dashboard/Helpers/TabHelpers.cs | 2 +- Dashboard/ServerTab.xaml | 14 ++++++------ Dashboard/Themes/DarkTheme.xaml | 38 +++++++++++++++++++++++++++++++++ Lite/Controls/ServerTab.xaml.cs | 8 +++++++ 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/Dashboard/Helpers/TabHelpers.cs b/Dashboard/Helpers/TabHelpers.cs index a9cc90c9..67938549 100644 --- a/Dashboard/Helpers/TabHelpers.cs +++ b/Dashboard/Helpers/TabHelpers.cs @@ -132,7 +132,7 @@ public static void ApplyDarkModeToChart(WpfPlot chart) var darkBackground = ScottPlot.Color.FromHex("#22252b"); var darkerBackground = ScottPlot.Color.FromHex("#111217"); var textColor = ScottPlot.Color.FromHex("#9DA5B4"); - var gridColor = ScottPlot.Colors.White.WithAlpha(40); + var gridColor = ScottPlot.Colors.White.WithAlpha(50); chart.Plot.FigureBackground.Color = darkBackground; chart.Plot.DataBackground.Color = darkerBackground; diff --git a/Dashboard/ServerTab.xaml b/Dashboard/ServerTab.xaml index a0b1d7f7..f98c1980 100644 --- a/Dashboard/ServerTab.xaml +++ b/Dashboard/ServerTab.xaml @@ -91,13 +91,13 @@ -