@@ -170,15 +170,13 @@ private async Task RefreshMetricsAndWaitStatsAsync(CancellationToken ct)
170170 _masterConnectionString , _activeDbs , _slicerStartUtc , _slicerEndUtc , _maxDop , ct ) ;
171171 _waitSlices = slices ;
172172
173- if ( errors . Count > 0 )
174- {
175- await Dispatcher . UIThread . InvokeAsync ( ( ) =>
176- ShowWaitStatsErrors ( errors ) ) ;
177- }
173+ await Dispatcher . UIThread . InvokeAsync ( ( ) => UpdateWaitStatsWarning ( errors ) ) ;
178174 }
179175 else
180176 {
181177 _waitSlices . Clear ( ) ;
178+ await Dispatcher . UIThread . InvokeAsync ( ( ) =>
179+ UpdateWaitStatsWarning ( new List < ( string Database , string Error ) > ( ) ) ) ;
182180 }
183181
184182 await Dispatcher . UIThread . InvokeAsync ( ( ) =>
@@ -193,31 +191,23 @@ await Dispatcher.UIThread.InvokeAsync(() =>
193191 }
194192 }
195193
196- private void ShowWaitStatsErrors ( List < ( string Database , string Error ) > errors )
194+ private void UpdateWaitStatsWarning ( List < ( string Database , string Error ) > errors )
197195 {
198- var msg = string . Join ( "\n " , errors . Select ( e => $ "[{ e . Database } ] { e . Error } ") ) ;
199- var window = new Avalonia . Controls . Window
196+ if ( errors . Count == 0 )
200197 {
201- Title = "Wait Stats Errors" ,
202- Width = 600 ,
203- Height = 300 ,
204- Content = new ScrollViewer
205- {
206- Content = new TextBlock
207- {
208- Text = msg ,
209- TextWrapping = Avalonia . Media . TextWrapping . Wrap ,
210- Margin = new Thickness ( 10 ) ,
211- Foreground = new SolidColorBrush ( Color . Parse ( "#E4E6EB" ) ) ,
212- FontSize = 12 ,
213- }
214- }
215- } ;
216- var topLevel = TopLevel . GetTopLevel ( this ) ;
217- if ( topLevel is Avalonia . Controls . Window owner )
218- window . ShowDialog ( owner ) ;
219- else
220- window . Show ( ) ;
198+ WaitStatsWarning . IsVisible = false ;
199+ ToolTip . SetTip ( WaitStatsWarning , null ) ;
200+ return ;
201+ }
202+
203+ var header = errors . Count == 1
204+ ? "Wait stats incomplete (1 error):"
205+ : $ "Wait stats incomplete ({ errors . Count } errors):";
206+ var msg = string . Join ( "\n " , errors . Select ( e => $ "[{ e . Database } ] { e . Error } ") ) ;
207+
208+ ToolTip . SetTip ( WaitStatsWarning , $ "{ header } \n { msg } ") ;
209+ ToolTip . SetShowDelay ( WaitStatsWarning , 200 ) ;
210+ WaitStatsWarning . IsVisible = true ;
221211 }
222212
223213 // ── Donut Chart ──────────────────────────────────────────────────────────
@@ -438,15 +428,35 @@ private async void OnSlicerRangeChanged(object? sender, TimeRangeChangedEventArg
438428 _slicerStartUtc = e . StartUtc ;
439429 _slicerEndUtc = e . EndUtc ;
440430
431+ // Don't dispose the previous CTS — the in-flight refresh still holds its token.
432+ // Cancel signals the previous run; GC reclaims the source after both runs unwind.
441433 _cts ? . Cancel ( ) ;
442- _cts ? . Dispose ( ) ;
443- _cts = new CancellationTokenSource ( ) ;
434+ var newCts = new CancellationTokenSource ( ) ;
435+ _cts = newCts ;
436+
437+ ClearRefreshError ( ) ;
444438 try
445439 {
446- await RefreshMetricsAndWaitStatsAsync ( _cts . Token ) ;
440+ await RefreshMetricsAndWaitStatsAsync ( newCts . Token ) ;
447441 }
448442 catch ( OperationCanceledException ) { }
449- catch { /* swallow errors from refresh */ }
443+ catch ( Exception ex )
444+ {
445+ ShowRefreshError ( ex ) ;
446+ }
447+ }
448+
449+ private void ShowRefreshError ( Exception ex )
450+ {
451+ ToolTip . SetTip ( RefreshErrorBadge , $ "Last refresh failed:\n { ex . Message } ") ;
452+ ToolTip . SetShowDelay ( RefreshErrorBadge , 200 ) ;
453+ RefreshErrorBadge . IsVisible = true ;
454+ }
455+
456+ private void ClearRefreshError ( )
457+ {
458+ RefreshErrorBadge . IsVisible = false ;
459+ ToolTip . SetTip ( RefreshErrorBadge , null ) ;
450460 }
451461
452462 // ── Wait Stats Chart (stacked by database) ──────────────────────────────
@@ -460,7 +470,7 @@ private void DrawWaitStatsChart()
460470 {
461471 Text = _supportsWaitStats ? "No wait stats data" : "Wait stats not supported (SQL 2017+ required)" ,
462472 FontSize = 10 ,
463- Foreground = new SolidColorBrush ( Color . Parse ( "#888888 " ) ) ,
473+ Foreground = this . FindResource ( "ForegroundMutedBrush" ) as IBrush ?? new SolidColorBrush ( Color . Parse ( "#B0B6C0 " ) ) ,
464474 TextWrapping = Avalonia . Media . TextWrapping . Wrap ,
465475 } ;
466476 Canvas . SetLeft ( msg , 10 ) ;
0 commit comments