diff --git a/Dashboard/Controls/FinOpsContent.xaml b/Dashboard/Controls/FinOpsContent.xaml
index aae149d..0a4af65 100644
--- a/Dashboard/Controls/FinOpsContent.xaml
+++ b/Dashboard/Controls/FinOpsContent.xaml
@@ -82,8 +82,21 @@
RowBackground="Transparent"
AlternatingRowBackground="{DynamicResource DataGridAlternatingRowBrush}">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
@@ -1405,7 +1786,13 @@
RowBackground="Transparent"
AlternatingRowBackground="{DynamicResource DataGridAlternatingRowBrush}">
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
@@ -1514,30 +1962,69 @@
SelectionMode="Extended"
RowStyle="{StaticResource DefaultRowStyle}">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1583,38 +2070,89 @@
SelectionMode="Extended"
RowStyle="{StaticResource DefaultRowStyle}">
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Dashboard/Controls/FinOpsContent.xaml.cs b/Dashboard/Controls/FinOpsContent.xaml.cs
index c9bcaff..76d1e62 100644
--- a/Dashboard/Controls/FinOpsContent.xaml.cs
+++ b/Dashboard/Controls/FinOpsContent.xaml.cs
@@ -15,6 +15,7 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
+using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Microsoft.Win32;
using PerformanceMonitorDashboard.Helpers;
@@ -969,5 +970,160 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
}
}
}
+ // ============================================
+ // Column Filtering
+ // ============================================
+
+ #region Column Filtering
+
+ private Popup? _filterPopup;
+ private ColumnFilterPopup? _filterPopupContent;
+ private DataGrid? _currentFilterGrid;
+ private readonly Dictionary> _gridFilters = new();
+ private readonly Dictionary _gridUnfilteredData = new();
+
+ private void EnsureFinOpsFilterPopup()
+ {
+ if (_filterPopup == null)
+ {
+ _filterPopupContent = new ColumnFilterPopup();
+
+ _filterPopup = new Popup
+ {
+ Child = _filterPopupContent,
+ StaysOpen = false,
+ Placement = PlacementMode.Bottom,
+ AllowsTransparency = true
+ };
+ }
+ }
+
+ private void FinOpsFilter_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is not Button button || button.Tag is not string columnName) return;
+
+ var dataGrid = TabHelpers.FindParent(button);
+ if (dataGrid == null) return;
+
+ EnsureFinOpsFilterPopup();
+
+ // Rewire events — remove then add to avoid double-firing
+ _filterPopupContent!.FilterApplied -= FinOpsFilterPopup_Applied;
+ _filterPopupContent.FilterCleared -= FinOpsFilterPopup_Cleared;
+ _filterPopupContent.FilterApplied += FinOpsFilterPopup_Applied;
+ _filterPopupContent.FilterCleared += FinOpsFilterPopup_Cleared;
+
+ _currentFilterGrid = dataGrid;
+
+ if (!_gridFilters.ContainsKey(dataGrid))
+ _gridFilters[dataGrid] = new Dictionary();
+
+ _gridFilters[dataGrid].TryGetValue(columnName, out var existing);
+ _filterPopupContent.Initialize(columnName, existing);
+
+ _filterPopup!.PlacementTarget = button;
+ _filterPopup.IsOpen = true;
+ }
+
+ private void FinOpsFilterPopup_Applied(object? sender, FilterAppliedEventArgs e)
+ {
+ if (_filterPopup != null)
+ _filterPopup.IsOpen = false;
+
+ if (_currentFilterGrid == null) return;
+
+ if (!_gridFilters.ContainsKey(_currentFilterGrid))
+ _gridFilters[_currentFilterGrid] = new Dictionary();
+
+ if (e.FilterState.IsActive)
+ {
+ _gridFilters[_currentFilterGrid][e.FilterState.ColumnName] = e.FilterState;
+ }
+ else
+ {
+ _gridFilters[_currentFilterGrid].Remove(e.FilterState.ColumnName);
+ }
+
+ ApplyFinOpsFilters(_currentFilterGrid);
+ UpdateFinOpsFilterButtonStyles(_currentFilterGrid);
+ }
+
+ private void FinOpsFilterPopup_Cleared(object? sender, EventArgs e)
+ {
+ if (_filterPopup != null)
+ _filterPopup.IsOpen = false;
+ }
+
+ private void ApplyFinOpsFilters(DataGrid dataGrid)
+ {
+ // Capture unfiltered data on first filter application
+ if (!_gridUnfilteredData.TryGetValue(dataGrid, out var cached) || cached == null)
+ {
+ cached = dataGrid.ItemsSource;
+ _gridUnfilteredData[dataGrid] = cached;
+ }
+
+ var unfilteredData = cached;
+ if (unfilteredData == null) return;
+
+ if (!_gridFilters.TryGetValue(dataGrid, out var filters) || filters.Count == 0)
+ {
+ dataGrid.ItemsSource = unfilteredData;
+ return;
+ }
+
+ // Generic filtering: cast to IEnumerable, filter each item using reflection-based MatchesFilter
+ var sourceList = unfilteredData.Cast
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
-
+
+
+
+
-
-
-
+
+
@@ -1709,48 +1742,32 @@
MaxHeight="350"
RowStyle="{StaticResource DefaultRowStyle}">
-
-
-
-
-
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
+
+
+
+
@@ -1783,32 +1800,43 @@
MaxHeight="250"
RowStyle="{StaticResource DefaultRowStyle}">
-
-
+
+
+
+
+
-
+
+
-
+
+
-
+
+
-
+
+
-
+
+
-
+
+
-
+
+
-
+
+
diff --git a/Lite/Controls/FinOpsTab.xaml.cs b/Lite/Controls/FinOpsTab.xaml.cs
index 93862f8..67505af 100644
--- a/Lite/Controls/FinOpsTab.xaml.cs
+++ b/Lite/Controls/FinOpsTab.xaml.cs
@@ -43,6 +43,11 @@ public partial class FinOpsTab : UserControl
private DataGridFilterManager? _appConnectionsFilterMgr;
private DataGridFilterManager? _serverInventoryFilterMgr;
private DataGridFilterManager? _highImpactFilterMgr;
+ private DataGridFilterManager? _idleDbsFilterMgr;
+ private DataGridFilterManager? _tempdbFilterMgr;
+ private DataGridFilterManager? _waitCategoryFilterMgr;
+ private DataGridFilterManager? _expensiveQueriesFilterMgr;
+ private DataGridFilterManager? _memoryGrantFilterMgr;
public FinOpsTab()
{
@@ -531,7 +536,7 @@ private async System.Threading.Tasks.Task LoadIdleDatabasesAsync(int serverId)
try
{
var data = await _dataService.GetIdleDatabasesAsync(serverId);
- IdleDatabasesDataGrid.ItemsSource = data;
+ _idleDbsFilterMgr!.UpdateData(data);
IdleDatabasesNoDataMessage.Visibility = data.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
IdleDatabasesCountIndicator.Text = data.Count > 0 ? $"{data.Count} idle database(s)" : "";
}
@@ -548,7 +553,7 @@ private async System.Threading.Tasks.Task LoadTempdbSummaryAsync(int serverId)
try
{
var data = await _dataService.GetTempdbSummaryAsync(serverId);
- TempdbPressureDataGrid.ItemsSource = data;
+ _tempdbFilterMgr!.UpdateData(data);
TempdbPressureNoDataMessage.Visibility = data.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
catch (Exception ex)
@@ -635,7 +640,7 @@ private async System.Threading.Tasks.Task LoadWaitCategorySummaryAsync(int serve
}
}
- WaitCategorySummaryDataGrid.ItemsSource = data;
+ _waitCategoryFilterMgr!.UpdateData(data);
WaitCategorySummaryNoDataMessage.Visibility = data.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
catch (Exception ex)
@@ -665,7 +670,7 @@ private async System.Threading.Tasks.Task LoadExpensiveQueriesAsync(int serverId
}
}
- ExpensiveQueriesDataGrid.ItemsSource = data;
+ _expensiveQueriesFilterMgr!.UpdateData(data);
ExpensiveQueriesNoDataMessage.Visibility = data.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
ExpensiveQueriesCountIndicator.Text = data.Count > 0 ? $"{data.Count} query(s)" : "";
}
@@ -682,7 +687,7 @@ private async System.Threading.Tasks.Task LoadMemoryGrantEfficiencyAsync(int ser
try
{
var data = await _dataService.GetMemoryGrantEfficiencyAsync(serverId);
- MemoryGrantEfficiencyDataGrid.ItemsSource = data;
+ _memoryGrantFilterMgr!.UpdateData(data);
MemoryGrantEfficiencyNoDataMessage.Visibility = data.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
catch (Exception ex)
@@ -961,6 +966,11 @@ private void InitializeFilterManagers()
_appConnectionsFilterMgr = new DataGridFilterManager(ApplicationConnectionsDataGrid);
_serverInventoryFilterMgr = new DataGridFilterManager(ServerInventoryDataGrid);
_highImpactFilterMgr = new DataGridFilterManager(HighImpactDataGrid);
+ _idleDbsFilterMgr = new DataGridFilterManager(IdleDatabasesDataGrid);
+ _tempdbFilterMgr = new DataGridFilterManager(TempdbPressureDataGrid);
+ _waitCategoryFilterMgr = new DataGridFilterManager(WaitCategorySummaryDataGrid);
+ _expensiveQueriesFilterMgr = new DataGridFilterManager(ExpensiveQueriesDataGrid);
+ _memoryGrantFilterMgr = new DataGridFilterManager(MemoryGrantEfficiencyDataGrid);
_filterManagers[DatabaseResourcesDataGrid] = _dbResourcesFilterMgr;
_filterManagers[StorageGrowthDataGrid] = _storageGrowthFilterMgr;
@@ -970,6 +980,11 @@ private void InitializeFilterManagers()
_filterManagers[ApplicationConnectionsDataGrid] = _appConnectionsFilterMgr;
_filterManagers[ServerInventoryDataGrid] = _serverInventoryFilterMgr;
_filterManagers[HighImpactDataGrid] = _highImpactFilterMgr;
+ _filterManagers[IdleDatabasesDataGrid] = _idleDbsFilterMgr;
+ _filterManagers[TempdbPressureDataGrid] = _tempdbFilterMgr;
+ _filterManagers[WaitCategorySummaryDataGrid] = _waitCategoryFilterMgr;
+ _filterManagers[ExpensiveQueriesDataGrid] = _expensiveQueriesFilterMgr;
+ _filterManagers[MemoryGrantEfficiencyDataGrid] = _memoryGrantFilterMgr;
}
private void EnsureFilterPopup()