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
4 changes: 2 additions & 2 deletions Dashboard/Controls/AlertsHistoryContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ private void CopyAllRows_Click(object sender, RoutedEventArgs e)
var sb = new StringBuilder();
var headers = dataGrid.Columns
.OfType<DataGridBoundColumn>()
.Select(c => TabHelpers.GetColumnHeader(c))
.Select(c => Helpers.DataGridClipboardBehavior.GetHeaderText(c))
.ToList();
sb.AppendLine(string.Join("\t", headers));

Expand Down Expand Up @@ -406,7 +406,7 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
var sb = new StringBuilder();
var headers = dataGrid.Columns
.OfType<DataGridBoundColumn>()
.Select(c => TabHelpers.EscapeCsvField(TabHelpers.GetColumnHeader(c)))
.Select(c => TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(c)))
.ToList();
sb.AppendLine(string.Join(",", headers));

Expand Down
282 changes: 282 additions & 0 deletions Dashboard/Controls/ConfigChangesContent.xaml

Large diffs are not rendered by default.

384 changes: 384 additions & 0 deletions Dashboard/Controls/ConfigChangesContent.xaml.cs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Dashboard/Controls/CriticalIssuesContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ private void CopyAllRows_Click(object sender, RoutedEventArgs e)
{
if (column is DataGridBoundColumn)
{
headers.Add(TabHelpers.GetColumnHeader(column));
headers.Add(Helpers.DataGridClipboardBehavior.GetHeaderText(column));
}
}
sb.AppendLine(string.Join("\t", headers));
Expand Down Expand Up @@ -351,7 +351,7 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
{
if (column is DataGridBoundColumn)
{
headers.Add(TabHelpers.EscapeCsvField(TabHelpers.GetColumnHeader(column)));
headers.Add(TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(column)));
}
}
sb.AppendLine(string.Join(",", headers));
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/Controls/DailySummaryContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ private void CopyAllRows_Click(object sender, RoutedEventArgs e)
{
if (column is DataGridBoundColumn)
{
headers.Add(TabHelpers.GetColumnHeader(column));
headers.Add(Helpers.DataGridClipboardBehavior.GetHeaderText(column));
}
}
sb.AppendLine(string.Join("\t", headers));
Expand Down Expand Up @@ -428,7 +428,7 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
{
if (column is DataGridBoundColumn)
{
headers.Add(TabHelpers.EscapeCsvField(TabHelpers.GetColumnHeader(column)));
headers.Add(TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(column)));
}
}
sb.AppendLine(string.Join(",", headers));
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/Controls/MemoryContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,7 @@ private void CopyAllRows_Click(object sender, RoutedEventArgs e)
{
if (column is DataGridBoundColumn)
{
headers.Add(TabHelpers.GetColumnHeader(column));
headers.Add(Helpers.DataGridClipboardBehavior.GetHeaderText(column));
}
}
sb.AppendLine(string.Join("\t", headers));
Expand Down Expand Up @@ -974,7 +974,7 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
{
if (column is DataGridBoundColumn)
{
headers.Add(TabHelpers.EscapeCsvField(TabHelpers.GetColumnHeader(column)));
headers.Add(TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(column)));
}
}
sb.AppendLine(string.Join(",", headers));
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/Controls/QueryPerformanceContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1089,7 +1089,7 @@ private void CopyAllRows_Click(object sender, RoutedEventArgs e)
{
if (column is DataGridBoundColumn)
{
headers.Add(TabHelpers.GetColumnHeader(column));
headers.Add(Helpers.DataGridClipboardBehavior.GetHeaderText(column));
}
}
sb.AppendLine(string.Join("\t", headers));
Expand Down Expand Up @@ -1192,7 +1192,7 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
{
if (column is DataGridBoundColumn)
{
headers.Add(TabHelpers.EscapeCsvField(TabHelpers.GetColumnHeader(column)));
headers.Add(TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(column)));
}
}
sb.AppendLine(string.Join(",", headers));
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/Controls/ResourceMetricsContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1289,7 +1289,7 @@ private void CopyAllRows_Click(object sender, RoutedEventArgs e)
{
var sb = new StringBuilder();

var headers = grid.Columns.Select(c => c.Header?.ToString() ?? string.Empty);
var headers = grid.Columns.Select(c => Helpers.DataGridClipboardBehavior.GetHeaderText(c));
sb.AppendLine(string.Join("\t", headers));

foreach (var item in grid.Items)
Expand Down Expand Up @@ -1336,7 +1336,7 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
{
var sb = new StringBuilder();

var headers = grid.Columns.Select(c => TabHelpers.EscapeCsvField(c.Header?.ToString() ?? string.Empty));
var headers = grid.Columns.Select(c => TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(c)));
sb.AppendLine(string.Join(",", headers));

foreach (var item in grid.Items)
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/Controls/SystemEventsContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2120,7 +2120,7 @@ private void CopyAllRows_Click(object sender, RoutedEventArgs e)
var sb = new StringBuilder();

// Header row
var headers = grid.Columns.Select(c => c.Header?.ToString() ?? string.Empty);
var headers = grid.Columns.Select(c => Helpers.DataGridClipboardBehavior.GetHeaderText(c));
sb.AppendLine(string.Join("\t", headers));

// Data rows
Expand Down Expand Up @@ -2169,7 +2169,7 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
var sb = new StringBuilder();

// Header row
var headers = grid.Columns.Select(c => TabHelpers.EscapeCsvField(c.Header?.ToString() ?? string.Empty));
var headers = grid.Columns.Select(c => TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(c)));
sb.AppendLine(string.Join(",", headers));

// Data rows
Expand Down
74 changes: 74 additions & 0 deletions Dashboard/Helpers/DataGridClipboardBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2026 Erik Darling, Darling Data LLC
*
* This file is part of the SQL Server Performance Monitor.
*
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
*/

using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace PerformanceMonitorDashboard.Helpers;

/// <summary>
/// Attached behavior that fixes DataGrid clipboard copy when column headers
/// use StackPanel (e.g. filter button + text). Without this, copied headers
/// show "System.Windows.Controls.StackPanel" instead of the column name.
/// </summary>
public static class DataGridClipboardBehavior
{
public static readonly DependencyProperty FixHeaderCopyProperty =
DependencyProperty.RegisterAttached(
"FixHeaderCopy",
typeof(bool),
typeof(DataGridClipboardBehavior),
new PropertyMetadata(false, OnFixHeaderCopyChanged));

public static bool GetFixHeaderCopy(DependencyObject obj) => (bool)obj.GetValue(FixHeaderCopyProperty);
public static void SetFixHeaderCopy(DependencyObject obj, bool value) => obj.SetValue(FixHeaderCopyProperty, value);

private static void OnFixHeaderCopyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is DataGrid grid && (bool)e.NewValue)
{
grid.CopyingRowClipboardContent += FixHeaderCopy;
}
}

/// <summary>
/// Extracts the display text from a DataGrid column header.
/// Handles StackPanel headers (filter button + TextBlock) by returning the TextBlock text.
/// </summary>
public static string GetHeaderText(DataGridColumn column)
{
if (column.Header is StackPanel sp)
{
var tb = sp.Children.OfType<TextBlock>().FirstOrDefault();
if (tb != null) return tb.Text;
}
return column.Header?.ToString() ?? "";
}

/// <summary>
/// Event handler that can be wired directly to DataGrid.CopyingRowClipboardContent.
/// </summary>
public static void FixHeaderCopy(object? sender, DataGridRowClipboardEventArgs e)
{
if (!e.IsColumnHeadersRow) return;

for (int i = 0; i < e.ClipboardRowContent.Count; i++)
{
var cell = e.ClipboardRowContent[i];
if (cell.Column?.Header is StackPanel sp)
{
var tb = sp.Children.OfType<TextBlock>().FirstOrDefault();
if (tb != null)
{
e.ClipboardRowContent[i] = new DataGridClipboardCellContent(cell.Item, cell.Column, tb.Text);
}
}
}
}
}
32 changes: 23 additions & 9 deletions Dashboard/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -772,20 +772,34 @@ private void EditServer_Click(object sender, RoutedEventArgs e)
}
}

private void RemoveServer_Click(object sender, RoutedEventArgs e)
private async void RemoveServer_Click(object sender, RoutedEventArgs e)
{
if (ServerListView.SelectedItem is ServerListItem item)
{
var server = item.Server;
var result = MessageBox.Show(
$"Are you sure you want to remove server '{server.DisplayName}'?\n\nThis action cannot be undone.",
"Confirm Remove Server",
MessageBoxButton.YesNo,
MessageBoxImage.Warning
);

if (result == MessageBoxResult.Yes)
var dialog = new RemoveServerDialog(server.DisplayName);
dialog.Owner = this;

if (dialog.ShowDialog() == true)
{
// Drop the database first if requested (before we delete credentials)
if (dialog.DropDatabase)
{
try
{
await _serverManager.DropMonitorDatabaseAsync(server);
}
catch (Exception ex)
{
MessageBox.Show(
$"Could not drop the PerformanceMonitor database on '{server.DisplayName}':\n\n{ex.Message}\n\nThe server will still be removed from the Dashboard.",
"Database Drop Failed",
MessageBoxButton.OK,
MessageBoxImage.Warning
);
}
}

if (_openTabs.TryGetValue(server.Id, out var tabItem))
{
_openTabs.Remove(server.Id);
Expand Down
29 changes: 21 additions & 8 deletions Dashboard/ManageServersWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,19 +139,32 @@ private void ToggleFavorite_Click(object sender, RoutedEventArgs e)
}
}

private void RemoveServer_Click(object sender, RoutedEventArgs e)
private async void RemoveServer_Click(object sender, RoutedEventArgs e)
{
if (ServersDataGrid.SelectedItem is ServerConnection server)
{
var result = MessageBox.Show(
$"Are you sure you want to remove server '{server.DisplayName}'?\n\nThis action cannot be undone.",
"Confirm Remove Server",
MessageBoxButton.YesNo,
MessageBoxImage.Warning
);
var dialog = new RemoveServerDialog(server.DisplayName);
dialog.Owner = this;

if (result == MessageBoxResult.Yes)
if (dialog.ShowDialog() == true)
{
if (dialog.DropDatabase)
{
try
{
await _serverManager.DropMonitorDatabaseAsync(server);
}
catch (Exception ex)
{
MessageBox.Show(
$"Could not drop the PerformanceMonitor database on '{server.DisplayName}':\n\n{ex.Message}\n\nThe server will still be removed from the Dashboard.",
"Database Drop Failed",
MessageBoxButton.OK,
MessageBoxImage.Warning
);
}
}

_serverManager.DeleteServer(server.Id);
LoadServers();
ServersModified = true;
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/ProcedureHistoryWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ private void CopyAllRows_Click(object sender, RoutedEventArgs e)
foreach (var column in dataGrid.Columns)
{
if (column is DataGridBoundColumn)
headers.Add(TabHelpers.GetColumnHeader(column));
headers.Add(Helpers.DataGridClipboardBehavior.GetHeaderText(column));
}
sb.AppendLine(string.Join("\t", headers));
foreach (var item in dataGrid.Items)
Expand Down Expand Up @@ -402,7 +402,7 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
foreach (var column in dataGrid.Columns)
{
if (column is DataGridBoundColumn)
headers.Add(TabHelpers.EscapeCsvField(TabHelpers.GetColumnHeader(column)));
headers.Add(TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(column)));
}
sb.AppendLine(string.Join(",", headers));
foreach (var item in dataGrid.Items)
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/QueryExecutionHistoryWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ private void CopyAllRows_Click(object sender, RoutedEventArgs e)
foreach (var column in dataGrid.Columns)
{
if (column is DataGridBoundColumn)
headers.Add(TabHelpers.GetColumnHeader(column));
headers.Add(Helpers.DataGridClipboardBehavior.GetHeaderText(column));
}
sb.AppendLine(string.Join("\t", headers));
foreach (var item in dataGrid.Items)
Expand Down Expand Up @@ -454,7 +454,7 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
foreach (var column in dataGrid.Columns)
{
if (column is DataGridBoundColumn)
headers.Add(TabHelpers.EscapeCsvField(TabHelpers.GetColumnHeader(column)));
headers.Add(TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(column)));
}
sb.AppendLine(string.Join(",", headers));
foreach (var item in dataGrid.Items)
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/QueryStatsHistoryWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ private void CopyAllRows_Click(object sender, RoutedEventArgs e)
foreach (var column in dataGrid.Columns)
{
if (column is DataGridBoundColumn)
headers.Add(TabHelpers.GetColumnHeader(column));
headers.Add(Helpers.DataGridClipboardBehavior.GetHeaderText(column));
}
sb.AppendLine(string.Join("\t", headers));
foreach (var item in dataGrid.Items)
Expand Down Expand Up @@ -399,7 +399,7 @@ private void ExportToCsv_Click(object sender, RoutedEventArgs e)
foreach (var column in dataGrid.Columns)
{
if (column is DataGridBoundColumn)
headers.Add(TabHelpers.EscapeCsvField(TabHelpers.GetColumnHeader(column)));
headers.Add(TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(column)));
}
sb.AppendLine(string.Join(",", headers));
foreach (var item in dataGrid.Items)
Expand Down
39 changes: 39 additions & 0 deletions Dashboard/RemoveServerDialog.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<Window x:Class="PerformanceMonitorDashboard.RemoveServerDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Confirm Remove Server"
Height="230" Width="450"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
Background="{DynamicResource BackgroundBrush}">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<!-- Warning message -->
<TextBlock Grid.Row="0" x:Name="WarningText" TextWrapping="Wrap" FontSize="13" Margin="0,0,0,15"/>

<!-- Drop database checkbox -->
<CheckBox Grid.Row="1" x:Name="DropDatabaseCheckBox" Margin="0,0,0,5"
Foreground="{DynamicResource ForegroundBrush}">
<TextBlock Text="Also drop the PerformanceMonitor database on this server" TextWrapping="Wrap"/>
</CheckBox>
<TextBlock Grid.Row="2" Text="Warning: all collected monitoring data on the server will be permanently deleted."
FontSize="11" Foreground="{DynamicResource ErrorBrush}" Margin="20,0,0,15"
TextWrapping="Wrap" Visibility="{Binding ElementName=DropDatabaseCheckBox, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}}"/>

<!-- Buttons -->
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="Remove" Width="80" Height="28" Margin="0,0,8,0" Click="Remove_Click"
Foreground="{DynamicResource ErrorBrush}"/>
<Button Content="Cancel" Width="80" Height="28" Click="Cancel_Click" IsCancel="True"/>
</StackPanel>
</Grid>
</Window>
Loading
Loading