Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dashboard/Controls/AlertsHistoryContent.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
Margin="4,0,0,0" Padding="10,4" MinWidth="70"/>
<TextBlock x:Name="AlertCountIndicator" Text="" Margin="12,0,0,0" VerticalAlignment="Center"
FontStyle="Italic" Foreground="{DynamicResource AccentBrush}" FontWeight="SemiBold"/>
<TextBlock x:Name="LastRefreshedIndicator" Text="" Margin="12,0,0,0" VerticalAlignment="Center"
FontSize="11" Foreground="{DynamicResource ForegroundMutedBrush}"/>
</StackPanel>

<!-- DataGrid -->
Expand Down
34 changes: 34 additions & 0 deletions Dashboard/Controls/AlertsHistoryContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using Microsoft.Win32;
using PerformanceMonitorDashboard.Helpers;
using PerformanceMonitorDashboard.Models;
Expand All @@ -28,6 +29,8 @@ public partial class AlertsHistoryContent : UserControl
public MuteRuleService? MuteRuleService { get; set; }

private List<AlertHistoryDisplayItem> _allAlerts = new();
private DateTime? _lastRefreshed;
private readonly DispatcherTimer _staleDataTimer;

/* Column filter state */
private readonly Dictionary<string, ColumnFilterState> _columnFilters = new();
Expand All @@ -37,6 +40,9 @@ public partial class AlertsHistoryContent : UserControl
public AlertsHistoryContent()
{
InitializeComponent();
_staleDataTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(10) };
_staleDataTimer.Tick += StaleDataTimer_Tick;
_staleDataTimer.Start();
}

/// <summary>
Expand Down Expand Up @@ -78,6 +84,7 @@ private void LoadAlerts()
DetailText = e.DetailText
}).ToList();

_lastRefreshed = DateTime.UtcNow;
ApplyFilters();
}

Expand Down Expand Up @@ -112,6 +119,7 @@ private void ApplyFilters()
AlertsDataGrid.ItemsSource = list;
NoAlertsMessage.Visibility = list.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
AlertCountIndicator.Text = list.Count > 0 ? $"{list.Count} alert(s)" : "";
UpdateStaleDataIndicator();

/* Populate server filter if needed */
PopulateServerFilter();
Expand Down Expand Up @@ -184,6 +192,28 @@ private static string GetStatusDisplay(AlertLogEntry entry)
return entry.AlertSent ? "Delivered" : "Shown";
}

#region Stale Data Indicator

private void StaleDataTimer_Tick(object? sender, EventArgs e)
{
UpdateStaleDataIndicator();
}

private void UpdateStaleDataIndicator()
{
if (_lastRefreshed.HasValue)
{
var elapsed = DateTime.UtcNow - _lastRefreshed.Value;
LastRefreshedIndicator.Text = elapsed.TotalSeconds < 5
? "Refreshed just now"
: elapsed.TotalMinutes < 1
? $"Refreshed {(int)elapsed.TotalSeconds}s ago"
: $"Refreshed {(int)elapsed.TotalMinutes}m ago";
}
}

#endregion

#region Event Handlers

private void TimeRangeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
Expand Down Expand Up @@ -223,7 +253,9 @@ private void DismissSelected_Click(object sender, RoutedEventArgs e)
.Select(s => (s.AlertTime, s.ServerName, s.MetricName))
.ToList();

Logger.Info($"[AlertDismiss] Action=DismissSelected, Requested={selected.Count}");
service.HideAlerts(keys);
Logger.Info($"[AlertDismiss] Action=DismissSelected, Result=Complete, Hidden={selected.Count}");
LoadAlerts();
AlertsDismissed?.Invoke(this, EventArgs.Empty);
}
Expand Down Expand Up @@ -252,7 +284,9 @@ private void DismissAll_Click(object sender, RoutedEventArgs e)
serverName = serverItem.Content?.ToString();
}

Logger.Info($"[AlertDismiss] Action=DismissAll, HoursBack={hoursBack}, Server={serverName ?? "all"}");
service.HideAllAlerts(hoursBack > 0 ? hoursBack : 8760, serverName);
Logger.Info($"[AlertDismiss] Action=DismissAll, Result=Complete");
LoadAlerts();
AlertsDismissed?.Invoke(this, EventArgs.Empty);
}
Expand Down
4 changes: 4 additions & 0 deletions Lite/Controls/AlertsHistoryTab.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@
Margin="4,0,0,0" Padding="10,4" MinWidth="70"/>
<TextBlock x:Name="AlertCountIndicator" Text="" Margin="12,0,0,0" VerticalAlignment="Center"
FontStyle="Italic" Foreground="{DynamicResource AccentBrush}" FontWeight="SemiBold"/>
<TextBlock x:Name="LastRefreshedIndicator" Text="" Margin="12,0,0,0" VerticalAlignment="Center"
FontSize="11" Foreground="{DynamicResource ForegroundMutedBrush}"/>
<TextBlock x:Name="ArchivalWarning" Text="" Margin="8,0,0,0" VerticalAlignment="Center"
FontSize="11" Foreground="#FFCC6600" FontWeight="SemiBold" Visibility="Collapsed"/>
</StackPanel>

<!-- DataGrid -->
Expand Down
42 changes: 42 additions & 0 deletions Lite/Controls/AlertsHistoryTab.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Threading;
using Microsoft.Win32;
using PerformanceMonitorLite.Controls;
using PerformanceMonitorLite.Models;
Expand All @@ -28,12 +29,16 @@ public partial class AlertsHistoryTab : UserControl
private DataGridFilterManager<AlertHistoryRow>? _filterManager;
private Popup? _filterPopup;
private ColumnFilterPopup? _filterPopupContent;
private DateTime? _lastRefreshed;
private readonly DispatcherTimer _staleDataTimer;

public MuteRuleService? MuteRuleService { get; set; }

public AlertsHistoryTab()
{
InitializeComponent();
_staleDataTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(10) };
_staleDataTimer.Tick += StaleDataTimer_Tick;
}

/// <summary>
Expand All @@ -43,6 +48,7 @@ public void Initialize(LocalDataService dataService)
{
_dataService = dataService;
_filterManager = new DataGridFilterManager<AlertHistoryRow>(AlertsDataGrid);
_staleDataTimer.Start();
}

/// <summary>
Expand Down Expand Up @@ -74,6 +80,9 @@ private async System.Threading.Tasks.Task LoadAlertsAsync()
AlertCountIndicator.Text = displayCount > 0 ? $"{displayCount} alert(s)" : "";
AppLogger.Debug("AlertsHistory", $"Loaded {displayCount} alert(s) (query returned {alerts.Count}, hoursBack={hoursBack}, serverId={serverId?.ToString() ?? "all"})");

_lastRefreshed = DateTime.UtcNow;
UpdateStaleDataIndicator();

PopulateServerFilter(alerts);
}
catch (Exception ex)
Expand Down Expand Up @@ -197,6 +206,39 @@ private void FilterPopup_FilterCleared(object? sender, EventArgs e)

#endregion

#region Stale Data Indicator

private void StaleDataTimer_Tick(object? sender, EventArgs e)
{
UpdateStaleDataIndicator();
}

private void UpdateStaleDataIndicator()
{
if (_lastRefreshed.HasValue)
{
var elapsed = DateTime.UtcNow - _lastRefreshed.Value;
LastRefreshedIndicator.Text = elapsed.TotalSeconds < 5
? "Refreshed just now"
: elapsed.TotalMinutes < 1
? $"Refreshed {(int)elapsed.TotalSeconds}s ago"
: $"Refreshed {(int)elapsed.TotalMinutes}m ago";
}

// Show archival warning if ArchiveService is currently running
if (ArchiveService.IsArchiving)
{
ArchivalWarning.Text = "⚠ Archival in progress";
ArchivalWarning.Visibility = Visibility.Visible;
}
else
{
ArchivalWarning.Visibility = Visibility.Collapsed;
}
}

#endregion

#region Event Handlers

private async void TimeRangeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
Expand Down
10 changes: 10 additions & 0 deletions Lite/Services/ArchiveService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ public class ArchiveService
private readonly ILogger<ArchiveService>? _logger;
private static readonly SemaphoreSlim s_archiveLock = new(1, 1);

/// <summary>
/// Indicates whether an archival operation is currently in progress.
/// UI code can check this to warn users before dismiss or show a status indicator.
/// </summary>
public static bool IsArchiving { get; private set; }

/* Tables eligible for archival with their time column.
IMPORTANT: Every table with time-series data must be listed here,
or it will grow unbounded and push the DB past the 512 MB reset threshold. */
Expand Down Expand Up @@ -88,6 +94,7 @@ public async Task ArchiveOldDataAsync(int hotDataDays = 7, int? hotDataHours = n
return;
}

IsArchiving = true;
try
{
var cutoffDate = hotDataHours.HasValue
Expand Down Expand Up @@ -146,6 +153,7 @@ Archive views use glob (*_table.parquet) to pick up all files. */
}
finally
{
IsArchiving = false;
s_archiveLock.Release();
}
}
Expand Down Expand Up @@ -416,6 +424,7 @@ public async Task ArchiveAllAndResetAsync()
return;
}

IsArchiving = true;
try
{
var timestamp = DateTime.UtcNow.ToString("yyyyMMdd_HHmm");
Expand Down Expand Up @@ -475,6 +484,7 @@ and only touches filesystem files — no contention with collectors. */
}
finally
{
IsArchiving = false;
s_archiveLock.Release();
}
}
Expand Down
Loading
Loading