Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
206ddc8
Add alert muting feature (both editions)
HannahVernon Mar 9, 2026
aae57db
Document alert muting in README
HannahVernon Mar 9, 2026
fb4ec8f
Merged with incoming dev branch.
HannahVernon Mar 9, 2026
ba45359
Add View Details context menu to alert history
HannahVernon Mar 9, 2026
32131ed
Add context-sensitive detail text to alert history
HannahVernon Mar 9, 2026
d4667ff
Document mute options and alert detail window in README
HannahVernon Mar 9, 2026
64e6402
Open alert details on double-click in alert history
HannahVernon Mar 9, 2026
9ea37d8
Fix mute rules checkbox to be interactive
HannahVernon Mar 9, 2026
80f799a
Add Close button to Manage Mute Rules window
HannahVernon Mar 9, 2026
5edacda
Fix mute rule checkbox not persisting across restarts
HannahVernon Mar 9, 2026
f5ba219
Fix checkbox requiring double-click in Manage Mute Rules
HannahVernon Mar 10, 2026
08185a4
Enrich blocking and deadlock alert detail text
HannahVernon Mar 10, 2026
a7658f5
Context-sensitive pattern fields and focus improvements
HannahVernon Mar 10, 2026
8355b1b
Document all 7 alert metrics and poison wait types in README
HannahVernon Mar 10, 2026
d192188
Add Microsoft Docs links for poison wait types
HannahVernon Mar 10, 2026
bc74c5b
Fix alert history selection and double-click in Lite edition
HannahVernon Mar 10, 2026
de42ce1
Fix three issues from Dashboard vs Lite comparative audit
HannahVernon Mar 10, 2026
dcb79c3
Cache JsonSerializerOptions to fix CA1869 warning
HannahVernon Mar 10, 2026
7dc5744
Address PR review feedback from maintainer
HannahVernon Mar 10, 2026
f3e4470
Merge upstream/dev into feature/alert-muting
HannahVernon Mar 11, 2026
be17045
Add test to prevent NOT NULL on ALTER TABLE ADD COLUMN
HannahVernon Mar 11, 2026
2cc6afd
Improve migration guard test to detect multi-line violations
HannahVernon Mar 11, 2026
f462320
Merge remote-tracking branch 'upstream/dev' into feature/alert-muting
HannahVernon Mar 11, 2026
1857fc5
Merge remote-tracking branch 'upstream/dev' into feature/alert-muting
HannahVernon Mar 11, 2026
fe3172a
Merged with upstream dev.
HannahVernon Mar 11, 2026
92b6a1e
Merged with incoming upstreadm dev branch.
HannahVernon Mar 11, 2026
8b2efd3
Fix DynamicResource on Freezable.Color in MutedBanner
HannahVernon Mar 11, 2026
e97f3b4
Merge branch 'feature/alert-muting' of https://github.com/HannahVerno…
HannahVernon Mar 11, 2026
40e5819
Fix CheckBox Enabled toggle persisting wrong value
HannahVernon Mar 11, 2026
8a20b75
Fix Lite MCP get_alert_history to use archive view and filter dismissed
HannahVernon Mar 11, 2026
5cf7fce
Register UserPreferencesService in Dashboard MCP DI container
HannahVernon Mar 11, 2026
5a543cc
Fix 9 bugs: clone-on-edit, mute-similar mode, persist ordering, write…
HannahVernon Mar 11, 2026
0055b94
Fix lock ordering in LogAlertAsync and add Dashboard CPU detailText
HannahVernon Mar 11, 2026
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
99 changes: 99 additions & 0 deletions Dashboard/AlertDetailWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<Window x:Class="PerformanceMonitorDashboard.AlertDetailWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Alert Details"
Width="480" SizeToContent="Height"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
Icon="/EDD.ico"
Background="{DynamicResource BackgroundBrush}">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<!-- Header -->
<TextBlock Grid.Row="0" Text="Alert Details" FontSize="16" FontWeight="SemiBold"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,0,0,16"/>

<!-- Detail fields -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="130"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<TextBlock Grid.Row="0" Grid.Column="0" Text="Time:" FontWeight="SemiBold"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4"/>
<TextBlock Grid.Row="0" Grid.Column="1" x:Name="TimeText"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4" TextWrapping="Wrap"/>

<TextBlock Grid.Row="1" Grid.Column="0" Text="Server:" FontWeight="SemiBold"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4"/>
<TextBlock Grid.Row="1" Grid.Column="1" x:Name="ServerText"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4" TextWrapping="Wrap"/>

<TextBlock Grid.Row="2" Grid.Column="0" Text="Metric:" FontWeight="SemiBold"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4"/>
<TextBlock Grid.Row="2" Grid.Column="1" x:Name="MetricText"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4" TextWrapping="Wrap"/>

<TextBlock Grid.Row="3" Grid.Column="0" Text="Current Value:" FontWeight="SemiBold"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4"/>
<TextBlock Grid.Row="3" Grid.Column="1" x:Name="CurrentValueText"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4" TextWrapping="Wrap"/>

<TextBlock Grid.Row="4" Grid.Column="0" Text="Threshold:" FontWeight="SemiBold"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4"/>
<TextBlock Grid.Row="4" Grid.Column="1" x:Name="ThresholdText"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4" TextWrapping="Wrap"/>

<TextBlock Grid.Row="5" Grid.Column="0" Text="Notification:" FontWeight="SemiBold"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4"/>
<TextBlock Grid.Row="5" Grid.Column="1" x:Name="NotificationText"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4" TextWrapping="Wrap"/>

<TextBlock Grid.Row="6" Grid.Column="0" Text="Status:" FontWeight="SemiBold"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4"/>
<TextBlock Grid.Row="6" Grid.Column="1" x:Name="StatusText"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,4" TextWrapping="Wrap"/>
</Grid>

<!-- Muted indicator -->
<Border Grid.Row="2" x:Name="MutedBanner" Visibility="Collapsed"
CornerRadius="4" Padding="8" Margin="0,12,0,0">
<Border.Background>
<SolidColorBrush Color="{StaticResource WarningColor}" Opacity="0.2"/>
</Border.Background>
<TextBlock Text="🔇 This alert was muted by a mute rule"
Foreground="{DynamicResource ForegroundBrush}" FontStyle="Italic"/>
</Border>

<!-- Context-specific details -->
<Border Grid.Row="3" x:Name="DetailPanel" Visibility="Collapsed"
Background="{DynamicResource BackgroundBrush}" BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="4" Padding="10" Margin="0,12,0,0">
<StackPanel>
<TextBlock Text="Details" FontWeight="SemiBold" FontSize="13"
Foreground="{DynamicResource ForegroundBrush}" Margin="0,0,0,6"/>
<TextBox x:Name="DetailTextBox" IsReadOnly="True" TextWrapping="Wrap"
Background="Transparent" BorderThickness="0"
Foreground="{DynamicResource ForegroundBrush}" FontFamily="Consolas"
FontSize="12" MaxHeight="300"
VerticalScrollBarVisibility="Auto"/>
</StackPanel>
</Border>
</Grid>
</Window>
36 changes: 36 additions & 0 deletions Dashboard/AlertDetailWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Performance Monitor Dashboard
* Copyright (c) 2026 Darling Data, LLC
* Licensed under the MIT License - see LICENSE file for details
*/

using System.Windows;
using PerformanceMonitorDashboard.Controls;

namespace PerformanceMonitorDashboard
{
public partial class AlertDetailWindow : Window
{
public AlertDetailWindow(AlertHistoryDisplayItem item)
{
InitializeComponent();

TimeText.Text = item.TimeLocal;
ServerText.Text = item.ServerName;
MetricText.Text = item.MetricName;
CurrentValueText.Text = item.CurrentValue;
ThresholdText.Text = item.ThresholdValue;
NotificationText.Text = item.NotificationType;
StatusText.Text = item.StatusDisplay;

if (item.Muted)
MutedBanner.Visibility = Visibility.Visible;

if (!string.IsNullOrWhiteSpace(item.DetailText))
{
DetailTextBox.Text = item.DetailText;
DetailPanel.Visibility = Visibility.Visible;
}
}
}
}
16 changes: 16 additions & 0 deletions Dashboard/Controls/AlertsHistoryContent.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
<ResourceDictionary>
<!-- Context Menu for DataGrid Copy/Export -->
<ContextMenu x:Key="DataGridContextMenu">
<MenuItem Header="View Details..." Click="ViewAlertDetails_Click">
<MenuItem.Icon><TextBlock Text="&#x1F50D;"/></MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem Header="Copy Cell" Click="CopyCell_Click">
<MenuItem.Icon><TextBlock Text="&#x1F4CB;"/></MenuItem.Icon>
</MenuItem>
Expand All @@ -22,6 +26,13 @@
<MenuItem Header="Export to CSV..." Click="ExportToCsv_Click">
<MenuItem.Icon><TextBlock Text="&#x1F4CA;"/></MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem Header="Mute This Alert..." Click="MuteThisAlert_Click">
<MenuItem.Icon><TextBlock Text="&#x1F515;"/></MenuItem.Icon>
</MenuItem>
<MenuItem Header="Mute Similar Alerts..." Click="MuteSimilarAlerts_Click">
<MenuItem.Icon><TextBlock Text="&#x1F507;"/></MenuItem.Icon>
</MenuItem>
</ContextMenu>

<Style x:Key="DefaultRowStyle" TargetType="DataGridRow">
Expand All @@ -39,6 +50,10 @@
<DataTrigger Binding="{Binding IsResolved}" Value="True">
<Setter Property="Background" Value="#3322C55E"/>
</DataTrigger>
<DataTrigger Binding="{Binding Muted}" Value="True">
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
Expand Down Expand Up @@ -89,6 +104,7 @@
CanUserResizeColumns="True"
SelectionMode="Extended"
SelectionChanged="AlertsDataGrid_SelectionChanged"
MouseDoubleClick="AlertsDataGrid_MouseDoubleClick"
RowStyle="{StaticResource AlertRowStyle}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding TimeLocal}" Width="150">
Expand Down
88 changes: 87 additions & 1 deletion Dashboard/Controls/AlertsHistoryContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public partial class AlertsHistoryContent : UserControl
{
public event EventHandler? AlertsDismissed;

public MuteRuleService? MuteRuleService { get; set; }

private List<AlertHistoryDisplayItem> _allAlerts = new();

/* Column filter state */
Expand Down Expand Up @@ -71,7 +73,9 @@ private void LoadAlerts()
IsResolved = e.MetricName.Contains("Cleared") || e.MetricName.Contains("Resolved"),
IsCritical = e.MetricName.Contains("Deadlock") || e.MetricName.Contains("Poison"),
IsWarning = !e.MetricName.Contains("Cleared") && !e.MetricName.Contains("Resolved")
&& !e.MetricName.Contains("Deadlock") && !e.MetricName.Contains("Poison")
&& !e.MetricName.Contains("Deadlock") && !e.MetricName.Contains("Poison"),
Muted = e.Muted,
DetailText = e.DetailText
}).ToList();

ApplyFilters();
Expand Down Expand Up @@ -432,6 +436,86 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
}

#endregion

#region Mute Handlers

private void AlertsDataGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (sender is not DataGrid) return;

// Walk up the visual tree from the click target to find the DataGridRow
var source = e.OriginalSource as DependencyObject;
while (source != null && source is not DataGridRow && source is not DataGridColumnHeader)
source = System.Windows.Media.VisualTreeHelper.GetParent(source);

// Ignore clicks on column headers or outside rows
if (source is not DataGridRow row) return;
if (row.DataContext is not AlertHistoryDisplayItem item) return;

var owner = Window.GetWindow(this);
var detailWindow = new AlertDetailWindow(item);
if (owner != null) detailWindow.Owner = owner;
detailWindow.ShowDialog();
}

private void ViewAlertDetails_Click(object sender, RoutedEventArgs e)
{
if (sender is not MenuItem menuItem) return;
var contextMenu = menuItem.Parent as ContextMenu;
if (contextMenu == null) return;
var dataGrid = TabHelpers.FindDataGridFromContextMenu(contextMenu);
if (dataGrid?.SelectedItem is not AlertHistoryDisplayItem item) return;

var detailWindow = new AlertDetailWindow(item) { Owner = Window.GetWindow(this) };
detailWindow.ShowDialog();
}

private void MuteThisAlert_Click(object sender, RoutedEventArgs e)
{
if (MuteRuleService == null) return;
if (sender is not MenuItem menuItem) return;
var contextMenu = menuItem.Parent as ContextMenu;
if (contextMenu == null) return;
var dataGrid = TabHelpers.FindDataGridFromContextMenu(contextMenu);
if (dataGrid?.SelectedItem is not AlertHistoryDisplayItem item) return;

var context = new AlertMuteContext
{
ServerName = item.ServerName,
MetricName = item.MetricName
};

var dialog = new MuteRuleDialog(context) { Owner = Window.GetWindow(this) };
if (dialog.ShowDialog() == true)
{
MuteRuleService.AddRule(dialog.Rule);
LoadAlerts();
}
}

private void MuteSimilarAlerts_Click(object sender, RoutedEventArgs e)
{
if (MuteRuleService == null) return;
if (sender is not MenuItem menuItem) return;
var contextMenu = menuItem.Parent as ContextMenu;
if (contextMenu == null) return;
var dataGrid = TabHelpers.FindDataGridFromContextMenu(contextMenu);
if (dataGrid?.SelectedItem is not AlertHistoryDisplayItem item) return;

var context = new AlertMuteContext
{
MetricName = item.MetricName
};

var dialog = new MuteRuleDialog(context) { Owner = Window.GetWindow(this) };
if (dialog.ShowDialog() == true)
{
MuteRuleService.AddRule(dialog.Rule);
LoadAlerts();
}
}

#endregion
}

public class AlertHistoryDisplayItem
Expand All @@ -447,5 +531,7 @@ public class AlertHistoryDisplayItem
public bool IsResolved { get; set; }
public bool IsCritical { get; set; }
public bool IsWarning { get; set; }
public bool Muted { get; set; }
public string? DetailText { get; set; }
}
}
Loading
Loading