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
3 changes: 3 additions & 0 deletions Dashboard/Models/UserPreferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ private static string GetDefaultCsvSeparator()
// Default mute rule expiration ("1 hour", "24 hours", "7 days", "Never")
public string MuteRuleDefaultExpiration { get; set; } = "24 hours";

// Log alert dismiss/mute actions to file
public bool LogAlertDismissals { get; set; } = true;

// Alert suppression (persisted)
public List<string> SilencedServers { get; set; } = new();
public List<string> SilencedServerTabs { get; set; } = new();
Expand Down
12 changes: 12 additions & 0 deletions Dashboard/Services/EmailAlertService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,15 +203,22 @@ public void HideAlerts(List<(DateTime AlertTime, string ServerName, string Metri
if (keys.Count == 0) return;

var keySet = new HashSet<(DateTime, string, string)>(keys);
int hidden = 0;

lock (_alertLogLock)
{
foreach (var alert in _alertLog)
{
if (keySet.Contains((alert.AlertTime, alert.ServerName, alert.MetricName)))
{
alert.Hidden = true;
hidden++;
}
}
}

if (_preferencesService.GetPreferences().LogAlertDismissals)
Logger.Info($"[AlertDismiss] Dismissed {hidden} of {keys.Count} selected alert(s)");
}

/// <summary>
Expand All @@ -220,6 +227,7 @@ public void HideAlerts(List<(DateTime AlertTime, string ServerName, string Metri
public void HideAllAlerts(int hoursBack, string? serverName = null)
{
var cutoff = DateTime.UtcNow.AddHours(-hoursBack);
int hidden = 0;

lock (_alertLogLock)
{
Expand All @@ -230,9 +238,13 @@ public void HideAllAlerts(int hoursBack, string? serverName = null)
(serverName == null || alert.ServerName == serverName))
{
alert.Hidden = true;
hidden++;
}
}
}

if (_preferencesService.GetPreferences().LogAlertDismissals)
Logger.Info($"[AlertDismiss] Dismissed all: {hidden} alert(s) hidden (hoursBack={hoursBack}, server={serverName ?? "all"})");
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions Dashboard/SettingsWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@
Padding="12,4" Margin="0,0,0,4"/>
<TextBlock Text="Create rules to suppress specific recurring alerts while still logging them."
FontSize="11" FontStyle="Italic" Foreground="Gray" TextWrapping="Wrap"/>
<CheckBox x:Name="LogAlertDismissalsCheckBox" Content="Log alert dismiss and mute actions"
Margin="0,8,0,0"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
Expand Down
2 changes: 2 additions & 0 deletions Dashboard/SettingsWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ private void LoadSettings()
"7 days" => 2,
_ => 3
};
LogAlertDismissalsCheckBox.IsChecked = prefs.LogAlertDismissals;

UpdateNotificationCheckboxStates();

Expand Down Expand Up @@ -670,6 +671,7 @@ private async void OkButton_Click(object sender, RoutedEventArgs e)

prefs.MuteRuleDefaultExpiration = (MuteRuleDefaultExpirationCombo.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "24 hours";
MuteRuleDialog.DefaultExpiration = prefs.MuteRuleDefaultExpiration;
prefs.LogAlertDismissals = LogAlertDismissalsCheckBox.IsChecked == true;

// Save SMTP email settings
prefs.SmtpEnabled = SmtpEnabledCheckBox.IsChecked == true;
Expand Down
2 changes: 2 additions & 0 deletions Lite/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public partial class App : Application
public static int AlertCooldownMinutes { get; set; } = 5; // Tray notification cooldown between repeated alerts
public static int EmailCooldownMinutes { get; set; } = 15; // Email cooldown between repeated alerts
public static string MuteRuleDefaultExpiration { get; set; } = "24 hours"; // Default expiration for new mute rules
public static bool LogAlertDismissals { get; set; } = true; // Log alert dismiss/mute actions to file

/* Connection settings */
public static int ConnectionTimeoutSeconds { get; set; } = 5;
Expand Down Expand Up @@ -284,6 +285,7 @@ public static void LoadAlertSettings()
if (exp is "1 hour" or "24 hours" or "7 days" or "Never")
MuteRuleDefaultExpiration = exp;
}
if (root.TryGetProperty("log_alert_dismissals", out v)) LogAlertDismissals = v.GetBoolean();

/* Connection settings */
if (root.TryGetProperty("connection_timeout_seconds", out v))
Expand Down
13 changes: 11 additions & 2 deletions Lite/Controls/AlertsHistoryTab.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ private async System.Threading.Tasks.Task LoadAlertsAsync()
var displayCount = AlertsDataGrid.ItemsSource is ICollection<AlertHistoryRow> coll ? coll.Count : alerts.Count;
NoAlertsMessage.Visibility = displayCount == 0 ? Visibility.Visible : Visibility.Collapsed;
AlertCountIndicator.Text = displayCount > 0 ? $"{displayCount} alert(s)" : "";
AppLogger.Debug("AlertsHistory", $"Loaded {displayCount} alert(s) (query returned {alerts.Count}, hoursBack={hoursBack}, serverId={serverId?.ToString() ?? "all"})");

PopulateServerFilter(alerts);
}
Expand Down Expand Up @@ -232,7 +233,11 @@ private async void DismissSelected_Click(object sender, RoutedEventArgs e)

try
{
await _dataService.DismissAlertsAsync(selected);
var affected = await _dataService.DismissAlertsAsync(selected);
if (affected < selected.Count && App.LogAlertDismissals)
{
AppLogger.Warn("AlertsHistory", $"Dismiss selected: only {affected} of {selected.Count} alert(s) were updated — remaining alerts may have been archived to parquet");
}
await LoadAlertsAsync();
}
catch (Exception ex)
Expand Down Expand Up @@ -260,7 +265,11 @@ private async void DismissAll_Click(object sender, RoutedEventArgs e)
{
var hoursBack = GetSelectedHoursBack();
int? serverId = GetSelectedServerId();
await _dataService.DismissAllVisibleAlertsAsync(hoursBack, serverId);
var affected = await _dataService.DismissAllVisibleAlertsAsync(hoursBack, serverId);
if (affected < displayCount && App.LogAlertDismissals)
{
AppLogger.Warn("AlertsHistory", $"Dismiss all: only {affected} of {displayCount} displayed alert(s) were updated — remaining alerts may have been archived to parquet");
}
await LoadAlertsAsync();
}
catch (Exception ex)
Expand Down
31 changes: 25 additions & 6 deletions Lite/Services/LocalDataService.AlertHistory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,15 @@ ORDER BY alert_time DESC
/// Dismisses specific alerts by marking them as dismissed in DuckDB.
/// Identifies rows by (alert_time, server_id, metric_name) composite key.
/// </summary>
public async Task DismissAlertsAsync(List<AlertHistoryRow> alerts)
public async Task<int> DismissAlertsAsync(List<AlertHistoryRow> alerts)
{
if (alerts.Count == 0) return;
if (alerts.Count == 0) return 0;

if (App.LogAlertDismissals)
AppLogger.Info("AlertDismiss", $"Dismissing {alerts.Count} selected alert(s)");

using var connection = await OpenConnectionAsync();
int totalAffected = 0;

foreach (var alert in alerts)
{
Expand All @@ -115,19 +119,31 @@ UPDATE config_alert_log
SET dismissed = TRUE
WHERE alert_time = $1
AND server_id = $2
AND metric_name = $3";
AND metric_name = $3
AND dismissed = FALSE";
command.Parameters.Add(new DuckDBParameter { Value = alert.AlertTime });
command.Parameters.Add(new DuckDBParameter { Value = alert.ServerId });
command.Parameters.Add(new DuckDBParameter { Value = alert.MetricName });
await command.ExecuteNonQueryAsync();
var affected = await command.ExecuteNonQueryAsync();
totalAffected += affected;

if (affected == 0 && App.LogAlertDismissals)
AppLogger.Warn("AlertDismiss", $"No rows updated for alert: time={alert.AlertTime:O}, server_id={alert.ServerId}, metric={alert.MetricName} — may be archived to parquet");
}

if (App.LogAlertDismissals)
AppLogger.Info("AlertDismiss", $"Dismiss complete: {totalAffected} row(s) updated out of {alerts.Count} selected");
return totalAffected;
}

/// <summary>
/// Dismisses all visible (non-dismissed) alerts matching the current filter criteria.
/// </summary>
public async Task DismissAllVisibleAlertsAsync(int hoursBack, int? serverId = null)
public async Task<int> DismissAllVisibleAlertsAsync(int hoursBack, int? serverId = null)
{
if (App.LogAlertDismissals)
AppLogger.Info("AlertDismiss", $"Dismissing all visible alerts: hoursBack={hoursBack}, serverId={serverId?.ToString() ?? "all"}");

using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

Expand All @@ -154,7 +170,10 @@ UPDATE config_alert_log
command.Parameters.Add(new DuckDBParameter { Value = cutoff });
}

await command.ExecuteNonQueryAsync();
var affected = await command.ExecuteNonQueryAsync();
if (App.LogAlertDismissals)
AppLogger.Info("AlertDismiss", $"Dismiss all complete: {affected} row(s) updated (cutoff={cutoff:O})");
return affected;
}
}

Expand Down
2 changes: 2 additions & 0 deletions Lite/Windows/SettingsWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@
<TextBlock Text="Create rules to suppress specific recurring alerts while still logging them."
FontSize="11" FontStyle="Italic" Foreground="{DynamicResource ForegroundMutedBrush}"
Margin="20,0,0,0" TextWrapping="Wrap"/>
<CheckBox x:Name="LogAlertDismissalsCheckBox" Content="Log alert dismiss and mute actions"
Margin="20,8,0,0" Foreground="{DynamicResource ForegroundBrush}"/>
</StackPanel>
</Border>

Expand Down
3 changes: 3 additions & 0 deletions Lite/Windows/SettingsWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ private void LoadAlertSettings()
"7 days" => 2,
_ => 3
};
LogAlertDismissalsCheckBox.IsChecked = App.LogAlertDismissals;
UpdateAlertControlStates();
}

Expand Down Expand Up @@ -616,6 +617,7 @@ private bool SaveAlertSettings()
else
validationErrors.Add("Email alert cooldown must be between 1 and 120 minutes.");
App.MuteRuleDefaultExpiration = (MuteRuleDefaultExpirationCombo.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "24 hours";
App.LogAlertDismissals = LogAlertDismissalsCheckBox.IsChecked == true;

var settingsPath = Path.Combine(App.ConfigDirectory, "settings.json");
try
Expand Down Expand Up @@ -659,6 +661,7 @@ private bool SaveAlertSettings()
root["alert_cooldown_minutes"] = App.AlertCooldownMinutes;
root["email_cooldown_minutes"] = App.EmailCooldownMinutes;
root["mute_rule_default_expiration"] = App.MuteRuleDefaultExpiration;
root["log_alert_dismissals"] = App.LogAlertDismissals;

var options = new JsonSerializerOptions { WriteIndented = true };
File.WriteAllText(settingsPath, root.ToJsonString(options));
Expand Down
Loading