diff --git a/Dashboard/Controls/CriticalIssuesContent.xaml b/Dashboard/Controls/CriticalIssuesContent.xaml index 545f9cf4..17ce8acb 100644 --- a/Dashboard/Controls/CriticalIssuesContent.xaml +++ b/Dashboard/Controls/CriticalIssuesContent.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"> @@ -107,6 +108,7 @@ - + + diff --git a/Dashboard/Controls/CriticalIssuesContent.xaml.cs b/Dashboard/Controls/CriticalIssuesContent.xaml.cs index 93ded66c..cecc99c2 100644 --- a/Dashboard/Controls/CriticalIssuesContent.xaml.cs +++ b/Dashboard/Controls/CriticalIssuesContent.xaml.cs @@ -93,6 +93,13 @@ private async System.Threading.Tasks.Task LoadCriticalIssuesAsync() try { + // Only show loading overlay on initial load (no existing data) + if (CriticalIssuesDataGrid.ItemsSource == null) + { + CriticalIssuesLoading.IsLoading = true; + CriticalIssuesNoDataMessage.Visibility = Visibility.Collapsed; + } + var data = await _databaseService.GetCriticalIssuesAsync( _criticalIssuesHoursBack, _criticalIssuesFromDate, @@ -111,6 +118,10 @@ private async System.Threading.Tasks.Task LoadCriticalIssuesAsync() { Logger.Error($"Error loading critical issues: {ex.Message}"); } + finally + { + CriticalIssuesLoading.IsLoading = false; + } } #region Filter Popup Handlers 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..e7d77b5b 100644 --- a/Dashboard/Controls/DailySummaryContent.xaml.cs +++ b/Dashboard/Controls/DailySummaryContent.xaml.cs @@ -79,6 +79,13 @@ private async System.Threading.Tasks.Task LoadDailySummaryAsync() try { + // Only show loading overlay on initial load (no existing data) + if (DailySummaryDataGrid.ItemsSource == null) + { + 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 +107,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..90399ecb 100644 --- a/Dashboard/Controls/MemoryContent.xaml.cs +++ b/Dashboard/Controls/MemoryContent.xaml.cs @@ -383,6 +383,13 @@ private async System.Threading.Tasks.Task RefreshMemoryGrantsAsync() try { + // Only show loading overlay on initial load (no existing chart data) + if (!MemoryGrantsChart.Plot.GetPlottables().Any()) + { + 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 +399,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 +496,13 @@ private async System.Threading.Tasks.Task RefreshMemoryClerksAsync() try { + // Only show loading overlay on initial load (no existing chart data) + if (!MemoryClerksChart.Plot.GetPlottables().Any()) + { + 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 +512,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..cf0ce161 100644 --- a/Dashboard/Controls/QueryPerformanceContent.xaml.cs +++ b/Dashboard/Controls/QueryPerformanceContent.xaml.cs @@ -217,6 +217,13 @@ public async Task RefreshAllDataAsync() if (_databaseService == null) return; + // Only show loading overlay on initial load (no existing data) + if (QueryStatsDataGrid.ItemsSource == null) + { + 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 +270,10 @@ await Task.WhenAll( { Logger.Error($"Error refreshing QueryPerformance data: {ex.Message}", ex); } + finally + { + QueryStatsLoading.IsLoading = false; + } } private void SetStatus(string message) @@ -367,7 +378,14 @@ private async Task RefreshActiveQueriesAsync() try { + // Only show loading overlay on initial load (no existing data) + if (ActiveQueriesDataGrid.ItemsSource == null) + { + 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 +396,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 @@ + + +