diff --git a/Lite/Controls/FinOpsTab.xaml.cs b/Lite/Controls/FinOpsTab.xaml.cs index 03e41fdc..d0f529fb 100644 --- a/Lite/Controls/FinOpsTab.xaml.cs +++ b/Lite/Controls/FinOpsTab.xaml.cs @@ -87,7 +87,7 @@ private void PopulateServerSelector() private int GetSelectedServerId() { if (ServerSelector.SelectedItem is ServerConnection server) - return RemoteCollectorService.GetDeterministicHashCode(server.ServerName); + return RemoteCollectorService.GetDeterministicHashCode(RemoteCollectorService.GetServerNameForStorage(server)); return 0; } @@ -347,7 +347,7 @@ private async System.Threading.Tasks.Task LoadServerInventoryAsync(bool forceRef // Step 2: Get collected metrics from DuckDB try { - var serverId = RemoteCollectorService.GetDeterministicHashCode(server.ServerName); + var serverId = RemoteCollectorService.GetDeterministicHashCode(RemoteCollectorService.GetServerNameForStorage(server)); var (avgCpu, storageGb, idleDbs, status) = await _dataService!.GetServerMetricsAsync(serverId); if (avgCpu.HasValue) item.AvgCpuPct = avgCpu; if (storageGb.HasValue) item.StorageTotalGb = storageGb; diff --git a/Lite/Controls/ServerTab.xaml.cs b/Lite/Controls/ServerTab.xaml.cs index 5e9a6148..b49c0766 100644 --- a/Lite/Controls/ServerTab.xaml.cs +++ b/Lite/Controls/ServerTab.xaml.cs @@ -36,6 +36,7 @@ public partial class ServerTab : UserControl private readonly LocalDataService _dataService; private readonly int _serverId; public int ServerId => _serverId; + public ServerConnection Server => _server; private readonly CredentialService _credentialService; private readonly DispatcherTimer _refreshTimer; private bool _isRefreshing; @@ -117,13 +118,13 @@ public ServerTab(ServerConnection server, DuckDbInitializer duckDb, CredentialSe _server = server; _dataService = new LocalDataService(duckDb); - _serverId = RemoteCollectorService.GetDeterministicHashCode(server.ServerName); + _serverId = RemoteCollectorService.GetDeterministicHashCode(RemoteCollectorService.GetServerNameForStorage(server)); _credentialService = credentialService; UtcOffsetMinutes = utcOffsetMinutes; ServerTimeHelper.UtcOffsetMinutes = utcOffsetMinutes; - ServerNameText.Text = server.DisplayName; - ConnectionStatusText.Text = server.ServerName; + ServerNameText.Text = server.ReadOnlyIntent ? $"{server.DisplayName} (Read-Only)" : server.DisplayName; + ConnectionStatusText.Text = server.ServerNameDisplay; /* Apply default time range from settings */ TimeRangeCombo.SelectedIndex = App.DefaultTimeRangeHours switch @@ -582,7 +583,7 @@ private async System.Threading.Tasks.Task RefreshAllDataAsync(bool fullRefresh = } var tz = ServerTimeHelper.GetTimezoneLabel(ServerTimeHelper.CurrentDisplayMode); - ConnectionStatusText.Text = $"{_server.ServerName} - Last refresh: {DateTime.Now:HH:mm:ss} ({tz})"; + ConnectionStatusText.Text = $"{_server.ServerNameDisplay} - Last refresh: {DateTime.Now:HH:mm:ss} ({tz})"; } catch (Exception ex) { diff --git a/Lite/MainWindow.xaml b/Lite/MainWindow.xaml index b5aee93c..72b1b361 100644 --- a/Lite/MainWindow.xaml +++ b/Lite/MainWindow.xaml @@ -121,14 +121,14 @@ diff --git a/Lite/MainWindow.xaml.cs b/Lite/MainWindow.xaml.cs index 328c020c..0b1881cc 100644 --- a/Lite/MainWindow.xaml.cs +++ b/Lite/MainWindow.xaml.cs @@ -227,6 +227,7 @@ private void ServerTabControl_SelectionChanged(object sender, SelectionChangedEv if (ServerTabControl.SelectedItem is TabItem { Content: ServerTab serverTab }) { ServerTimeHelper.UtcOffsetMinutes = serverTab.UtcOffsetMinutes; + StatusText.Text = $"Connected to {serverTab.Server.DisplayNameWithIntent}"; } /* Refresh alerts tab when selected */ @@ -416,8 +417,8 @@ private async Task RefreshOverviewAsync() { try { - var serverId = RemoteCollectorService.GetDeterministicHashCode(server.ServerName); - var summary = await _dataService.GetServerSummaryAsync(serverId, server.DisplayName); + var serverId = RemoteCollectorService.GetDeterministicHashCode(RemoteCollectorService.GetServerNameForStorage(server)); + var summary = await _dataService.GetServerSummaryAsync(serverId, server.DisplayNameWithIntent); if (summary != null) { summary.ServerName = server.ServerName; @@ -563,23 +564,23 @@ private async void ConnectToServer(ServerConnection server) // Then collect fresh data and refresh again if (_collectorService != null) { - StatusText.Text = $"Collecting data from {server.DisplayName}..."; + StatusText.Text = $"Collecting data from {server.DisplayNameWithIntent}..."; try { await _collectorService.RunAllCollectorsForServerAsync(server); - StatusText.Text = $"Connected to {server.DisplayName} - Data loaded"; + StatusText.Text = $"Connected to {server.DisplayNameWithIntent} - Data loaded"; serverTab.RefreshData(); UpdateCollectorHealth(); _ = RefreshOverviewAsync(); } catch (Exception ex) { - StatusText.Text = $"Connected to {server.DisplayName} - Collection error: {ex.Message}"; + StatusText.Text = $"Connected to {server.DisplayNameWithIntent} - Collection error: {ex.Message}"; } } else { - StatusText.Text = $"Connected to {server.DisplayName}"; + StatusText.Text = $"Connected to {server.DisplayNameWithIntent}"; } } @@ -587,9 +588,10 @@ private StackPanel CreateTabHeader(ServerConnection server) { var panel = new StackPanel { Orientation = Orientation.Horizontal }; + var tabLabel = server.ReadOnlyIntent ? $"{server.DisplayName} (RO)" : server.DisplayName; panel.Children.Add(new TextBlock { - Text = server.DisplayName, + Text = tabLabel, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 4, 0) }); @@ -795,7 +797,7 @@ private void AddServerButton_Click(object sender, RoutedEventArgs e) if (dialog.ShowDialog() == true && dialog.AddedServer != null) { RefreshServerList(); - StatusText.Text = $"Added server: {dialog.AddedServer.DisplayName}"; + StatusText.Text = $"Added server: {dialog.AddedServer.DisplayNameWithIntent}"; } } @@ -880,7 +882,7 @@ private void ServerContextMenu_Remove_Click(object sender, RoutedEventArgs e) if (server == null) return; var result = MessageBox.Show( - $"Remove server '{server.DisplayName}'?", + $"Remove server '{server.DisplayNameWithIntent}'?", "Remove Server", MessageBoxButton.YesNo, MessageBoxImage.Question); @@ -890,7 +892,7 @@ private void ServerContextMenu_Remove_Click(object sender, RoutedEventArgs e) CloseServerTab(server.Id); _serverManager.DeleteServer(server.Id); RefreshServerList(); - StatusText.Text = $"Removed server: {server.DisplayName}"; + StatusText.Text = $"Removed server: {server.DisplayNameWithIntent}"; } } @@ -961,14 +963,14 @@ private void CheckConnectionsAndNotify() { _trayService?.ShowNotification( "Server Offline", - $"{server.DisplayName} is unreachable: {status.ErrorMessage ?? "unknown error"}", + $"{server.DisplayNameWithIntent} is unreachable: {status.ErrorMessage ?? "unknown error"}", Hardcodet.Wpf.TaskbarNotification.BalloonIcon.Error); } else if (!wasOnline && isOnline) { _trayService?.ShowNotification( "Server Online", - $"{server.DisplayName} is back online", + $"{server.DisplayNameWithIntent} is back online", Hardcodet.Wpf.TaskbarNotification.BalloonIcon.Info); } } diff --git a/Lite/Mcp/McpDiscoveryTools.cs b/Lite/Mcp/McpDiscoveryTools.cs index c9fc54b8..a11b815c 100644 --- a/Lite/Mcp/McpDiscoveryTools.cs +++ b/Lite/Mcp/McpDiscoveryTools.cs @@ -19,9 +19,10 @@ public static async Task ListServers(ServerManager serverManager, LocalD var lines = new List { $"Monitored servers ({servers.Count}):\n" }; foreach (var s in servers) { + var roTag = s.ReadOnlyIntent ? " [Read-Only]" : ""; var display = string.IsNullOrEmpty(s.DisplayName) || s.DisplayName == s.ServerName - ? s.ServerName - : $"{s.DisplayName} ({s.ServerName})"; + ? $"{s.ServerName}{roTag}" + : $"{s.DisplayName} ({s.ServerName}){roTag}"; var status = serverManager.GetConnectionStatus(s.Id); var statusText = status.IsOnline switch @@ -31,8 +32,8 @@ public static async Task ListServers(ServerManager serverManager, LocalD null => "Status not checked" }; - var serverId = RemoteCollectorService.GetDeterministicHashCode(s.ServerName); - var summary = await dataService.GetServerSummaryAsync(serverId, s.DisplayName ?? s.ServerName); + var serverId = RemoteCollectorService.GetDeterministicHashCode(RemoteCollectorService.GetServerNameForStorage(s)); + var summary = await dataService.GetServerSummaryAsync(serverId, s.DisplayNameWithIntent); var lastCollection = summary?.LastCollectionTime?.ToString("o") ?? "No data collected"; lines.Add($"- {display} [{statusText}] (last collection: {lastCollection})"); diff --git a/Lite/Mcp/ServerResolver.cs b/Lite/Mcp/ServerResolver.cs index ae66b719..8cdf9ec8 100644 --- a/Lite/Mcp/ServerResolver.cs +++ b/Lite/Mcp/ServerResolver.cs @@ -24,7 +24,8 @@ public static (int ServerId, string ServerName)? Resolve( if (servers.Count == 1) { var s = servers[0]; - return (RemoteCollectorService.GetDeterministicHashCode(s.ServerName), s.ServerName); + var storageName = RemoteCollectorService.GetServerNameForStorage(s); + return (RemoteCollectorService.GetDeterministicHashCode(storageName), storageName); } return null; @@ -37,7 +38,8 @@ public static (int ServerId, string ServerName)? Resolve( if (exact != null) { - return (RemoteCollectorService.GetDeterministicHashCode(exact.ServerName), exact.ServerName); + var exactName = RemoteCollectorService.GetServerNameForStorage(exact); + return (RemoteCollectorService.GetDeterministicHashCode(exactName), exactName); } /* Partial match */ @@ -47,7 +49,8 @@ public static (int ServerId, string ServerName)? Resolve( if (partial != null) { - return (RemoteCollectorService.GetDeterministicHashCode(partial.ServerName), partial.ServerName); + var partialName = RemoteCollectorService.GetServerNameForStorage(partial); + return (RemoteCollectorService.GetDeterministicHashCode(partialName), partialName); } return null; @@ -62,9 +65,12 @@ public static string ListAvailableServers(ServerManager serverManager) } var lines = servers.Select(s => - string.IsNullOrEmpty(s.DisplayName) || s.DisplayName == s.ServerName - ? s.ServerName - : $"{s.DisplayName} ({s.ServerName})"); + { + var roTag = s.ReadOnlyIntent ? " [Read-Only]" : ""; + return string.IsNullOrEmpty(s.DisplayName) || s.DisplayName == s.ServerName + ? $"{s.ServerName}{roTag}" + : $"{s.DisplayName} ({s.ServerName}){roTag}"; + }); return string.Join("\n", lines); } diff --git a/Lite/Models/ServerConnection.cs b/Lite/Models/ServerConnection.cs index a07c227f..24ca11c5 100644 --- a/Lite/Models/ServerConnection.cs +++ b/Lite/Models/ServerConnection.cs @@ -77,6 +77,20 @@ public bool UseWindowsAuth /// public bool ReadOnlyIntent { get; set; } = false; + /// + /// Server name with "(Read-Only)" suffix when ReadOnlyIntent is enabled. + /// Used for sidebar subtitle and status text. + /// + [JsonIgnore] + public string ServerNameDisplay => ReadOnlyIntent ? $"{ServerName} (Read-Only)" : ServerName; + + /// + /// Display name with "(Read-Only)" suffix when ReadOnlyIntent is enabled. + /// Used for alerts, tray notifications, status bar, and overview cards. + /// + [JsonIgnore] + public string DisplayNameWithIntent => ReadOnlyIntent ? $"{DisplayName} (Read-Only)" : DisplayName; + /// /// Display-only property for showing authentication type in UI. /// diff --git a/Lite/Services/RemoteCollectorService.BlockedProcessReport.cs b/Lite/Services/RemoteCollectorService.BlockedProcessReport.cs index b47a9284..cd5c90a7 100644 --- a/Lite/Services/RemoteCollectorService.BlockedProcessReport.cs +++ b/Lite/Services/RemoteCollectorService.BlockedProcessReport.cs @@ -418,7 +418,7 @@ as it lingers in the ring buffer across collection cycles. */ row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(parsed.EventTime) .AppendValue(parsed.DatabaseName) .AppendValue(parsed.BlockedSpid) diff --git a/Lite/Services/RemoteCollectorService.Cpu.cs b/Lite/Services/RemoteCollectorService.Cpu.cs index 800fb01c..a46c0681 100644 --- a/Lite/Services/RemoteCollectorService.Cpu.cs +++ b/Lite/Services/RemoteCollectorService.Cpu.cs @@ -147,7 +147,7 @@ drs.end_time DESC row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(sampleTime) .AppendValue(reader.IsDBNull(1) ? 0 : reader.GetInt32(1)) .AppendValue(reader.IsDBNull(2) ? 0 : reader.GetInt32(2)) diff --git a/Lite/Services/RemoteCollectorService.DatabaseSize.cs b/Lite/Services/RemoteCollectorService.DatabaseSize.cs index 96583975..7005819f 100644 --- a/Lite/Services/RemoteCollectorService.DatabaseSize.cs +++ b/Lite/Services/RemoteCollectorService.DatabaseSize.cs @@ -231,7 +231,7 @@ ORDER BY row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.DatabaseName) .AppendValue(r.DatabaseId) .AppendValue(r.FileId) diff --git a/Lite/Services/RemoteCollectorService.Deadlocks.cs b/Lite/Services/RemoteCollectorService.Deadlocks.cs index 883341b7..22c8e4df 100644 --- a/Lite/Services/RemoteCollectorService.Deadlocks.cs +++ b/Lite/Services/RemoteCollectorService.Deadlocks.cs @@ -401,7 +401,7 @@ and was previously misattributed as DuckDB time. */ row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(deadlockTime) .AppendValue(victimProcessId) .AppendValue(victimSqlText) diff --git a/Lite/Services/RemoteCollectorService.FileIo.cs b/Lite/Services/RemoteCollectorService.FileIo.cs index b9d280aa..5ce5540b 100644 --- a/Lite/Services/RemoteCollectorService.FileIo.cs +++ b/Lite/Services/RemoteCollectorService.FileIo.cs @@ -152,7 +152,7 @@ AND vfs.database_id < 32761 row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(stat.DatabaseName) .AppendValue(stat.FileName) .AppendValue(stat.FileType) diff --git a/Lite/Services/RemoteCollectorService.Memory.cs b/Lite/Services/RemoteCollectorService.Memory.cs index 55e36219..4af53786 100644 --- a/Lite/Services/RemoteCollectorService.Memory.cs +++ b/Lite/Services/RemoteCollectorService.Memory.cs @@ -175,7 +175,7 @@ FROM sys.dm_os_schedulers row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(totalPhysicalMb) .AppendValue(availablePhysicalMb) .AppendValue(totalPageFileMb) @@ -249,7 +249,7 @@ ORDER BY row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(reader.GetString(0)) .AppendValue(reader.GetDecimal(1)) .EndRow(); diff --git a/Lite/Services/RemoteCollectorService.MemoryGrants.cs b/Lite/Services/RemoteCollectorService.MemoryGrants.cs index a8db4a6e..244e08b2 100644 --- a/Lite/Services/RemoteCollectorService.MemoryGrants.cs +++ b/Lite/Services/RemoteCollectorService.MemoryGrants.cs @@ -99,7 +99,7 @@ WHERE deqrs.max_target_memory_kb IS NOT NULL row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.ResourceSemaphoreId) .AppendValue(r.PoolId) .AppendValue(r.TargetMb) diff --git a/Lite/Services/RemoteCollectorService.Perfmon.cs b/Lite/Services/RemoteCollectorService.Perfmon.cs index 9bfe972f..8e0b1ecb 100644 --- a/Lite/Services/RemoteCollectorService.Perfmon.cs +++ b/Lite/Services/RemoteCollectorService.Perfmon.cs @@ -186,7 +186,7 @@ WHERE pc.counter_name IN ( row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(objectName) .AppendValue(counterName) .AppendValue(instanceName) diff --git a/Lite/Services/RemoteCollectorService.ProcedureStats.cs b/Lite/Services/RemoteCollectorService.ProcedureStats.cs index 0b93c483..e46b71ab 100644 --- a/Lite/Services/RemoteCollectorService.ProcedureStats.cs +++ b/Lite/Services/RemoteCollectorService.ProcedureStats.cs @@ -290,7 +290,7 @@ ORDER BY s.total_elapsed_time DESC row.AppendValue(GenerateCollectionId()) /* collection_id */ .AppendValue(collectionTime) /* collection_time */ .AppendValue(serverId) /* server_id */ - .AppendValue(server.ServerName) /* server_name */ + .AppendValue(GetServerNameForStorage(server)) /* server_name */ .AppendValue(dbName) /* database_name */ .AppendValue(schemaName) /* schema_name */ .AppendValue(objectName) /* object_name */ diff --git a/Lite/Services/RemoteCollectorService.QuerySnapshots.cs b/Lite/Services/RemoteCollectorService.QuerySnapshots.cs index 93f5e363..da031295 100644 --- a/Lite/Services/RemoteCollectorService.QuerySnapshots.cs +++ b/Lite/Services/RemoteCollectorService.QuerySnapshots.cs @@ -137,7 +137,7 @@ private async Task CollectQuerySnapshotsAsync(ServerConnection server, Canc row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(Convert.ToInt32(reader.GetValue(0))) /* session_id */ .AppendValue(reader.IsDBNull(1) ? (string?)null : reader.GetString(1)) /* database_name */ .AppendValue(reader.IsDBNull(2) ? (string?)null : reader.GetString(2)) /* elapsed_time_formatted */ diff --git a/Lite/Services/RemoteCollectorService.QueryStats.cs b/Lite/Services/RemoteCollectorService.QueryStats.cs index 810e5ca6..cb0f535c 100644 --- a/Lite/Services/RemoteCollectorService.QueryStats.cs +++ b/Lite/Services/RemoteCollectorService.QueryStats.cs @@ -253,7 +253,7 @@ qs.total_elapsed_time DESC row.AppendValue(GenerateCollectionId()) /* collection_id */ .AppendValue(collectionTime) /* collection_time */ .AppendValue(serverId) /* server_id */ - .AppendValue(server.ServerName) /* server_name */ + .AppendValue(GetServerNameForStorage(server)) /* server_name */ .AppendValue(reader.IsDBNull(0) ? (string?)null : reader.GetString(0)) /* database_name */ .AppendValue(queryHash) /* query_hash */ .AppendValue(reader.IsDBNull(2) ? (string?)null : reader.GetString(2)) /* query_plan_hash */ diff --git a/Lite/Services/RemoteCollectorService.QueryStore.cs b/Lite/Services/RemoteCollectorService.QueryStore.cs index f8d916a6..612653c3 100644 --- a/Lite/Services/RemoteCollectorService.QueryStore.cs +++ b/Lite/Services/RemoteCollectorService.QueryStore.cs @@ -376,7 +376,7 @@ AND qst.query_sql_text NOT LIKE N''%PerformanceMonitorLite%'' row.AppendValue(GenerateCollectionId()) /* collection_id */ .AppendValue(collectionTime) /* collection_time */ .AppendValue(serverId) /* server_id */ - .AppendValue(server.ServerName) /* server_name */ + .AppendValue(GetServerNameForStorage(server)) /* server_name */ .AppendValue(dbName) /* database_name */ .AppendValue(reader.GetInt64(0)) /* query_id */ .AppendValue(reader.GetInt64(1)) /* plan_id */ diff --git a/Lite/Services/RemoteCollectorService.RunningJobs.cs b/Lite/Services/RemoteCollectorService.RunningJobs.cs index 00d26b87..9b56ca75 100644 --- a/Lite/Services/RemoteCollectorService.RunningJobs.cs +++ b/Lite/Services/RemoteCollectorService.RunningJobs.cs @@ -165,7 +165,7 @@ rj.current_duration_seconds DESC var row = appender.CreateRow(); row.AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.JobName) .AppendValue(r.JobId) .AppendValue(r.JobEnabled) diff --git a/Lite/Services/RemoteCollectorService.ServerConfig.cs b/Lite/Services/RemoteCollectorService.ServerConfig.cs index 6a7ba535..66f4b604 100644 --- a/Lite/Services/RemoteCollectorService.ServerConfig.cs +++ b/Lite/Services/RemoteCollectorService.ServerConfig.cs @@ -76,7 +76,7 @@ ORDER BY c.name row.AppendValue(GenerateCollectionId()) .AppendValue(captureTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.Name) .AppendValue(r.ValueConfigured) .AppendValue(r.ValueInUse) @@ -233,7 +233,7 @@ ORDER BY d.name row.AppendValue(GenerateCollectionId()) .AppendValue(captureTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.DbName) .AppendValue(r.StateDesc) .AppendValue(r.CompatLevel) @@ -443,7 +443,7 @@ ORDER BY dsc.name row.AppendValue(GenerateCollectionId()) .AppendValue(captureTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(dbName) .AppendValue(configName) .AppendValue(value) @@ -535,7 +535,7 @@ ORDER BY tf.trace_flag row.AppendValue(GenerateCollectionId()) .AppendValue(captureTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(r.TraceFlag) .AppendValue(r.Status) .AppendValue(r.IsGlobal) diff --git a/Lite/Services/RemoteCollectorService.ServerProperties.cs b/Lite/Services/RemoteCollectorService.ServerProperties.cs index 519f4deb..40bfd944 100644 --- a/Lite/Services/RemoteCollectorService.ServerProperties.cs +++ b/Lite/Services/RemoteCollectorService.ServerProperties.cs @@ -110,7 +110,7 @@ FROM sys.dm_os_sys_info AS osi row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(serverName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(edition) .AppendValue(productVersion) .AppendValue(productLevel) diff --git a/Lite/Services/RemoteCollectorService.SessionStats.cs b/Lite/Services/RemoteCollectorService.SessionStats.cs index a8281434..3eade6ec 100644 --- a/Lite/Services/RemoteCollectorService.SessionStats.cs +++ b/Lite/Services/RemoteCollectorService.SessionStats.cs @@ -120,7 +120,7 @@ ORDER BY row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(programName) .AppendValue(connectionCount) .AppendValue(runningCount) diff --git a/Lite/Services/RemoteCollectorService.TempDb.cs b/Lite/Services/RemoteCollectorService.TempDb.cs index 1f600121..d36c8da1 100644 --- a/Lite/Services/RemoteCollectorService.TempDb.cs +++ b/Lite/Services/RemoteCollectorService.TempDb.cs @@ -90,7 +90,7 @@ ORDER BY (ssu.user_objects_alloc_page_count + ssu.internal_objects_alloc_page_co row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue(userObjMb) .AppendValue(internalObjMb) .AppendValue(versionStoreMb) diff --git a/Lite/Services/RemoteCollectorService.WaitStats.cs b/Lite/Services/RemoteCollectorService.WaitStats.cs index 0b5dcfbe..35ade9ab 100644 --- a/Lite/Services/RemoteCollectorService.WaitStats.cs +++ b/Lite/Services/RemoteCollectorService.WaitStats.cs @@ -135,7 +135,7 @@ WHERE ws.wait_time_ms > 0 row.AppendValue(GenerateCollectionId()) /* collection_id BIGINT */ .AppendValue(collectionTime) /* collection_time TIMESTAMP */ .AppendValue(serverId) /* server_id INTEGER */ - .AppendValue(server.ServerName) /* server_name VARCHAR */ + .AppendValue(GetServerNameForStorage(server)) /* server_name VARCHAR */ .AppendValue(stat.WaitType) /* wait_type VARCHAR */ .AppendValue(stat.WaitingTasks) /* waiting_tasks_count BIGINT */ .AppendValue(stat.WaitTimeMs) /* wait_time_ms BIGINT */ diff --git a/Lite/Services/RemoteCollectorService.WaitingTasks.cs b/Lite/Services/RemoteCollectorService.WaitingTasks.cs index d68fc1ee..bd9373b1 100644 --- a/Lite/Services/RemoteCollectorService.WaitingTasks.cs +++ b/Lite/Services/RemoteCollectorService.WaitingTasks.cs @@ -80,7 +80,7 @@ AND wt.wait_type IS NOT NULL row.AppendValue(GenerateCollectionId()) .AppendValue(collectionTime) .AppendValue(serverId) - .AppendValue(server.ServerName) + .AppendValue(GetServerNameForStorage(server)) .AppendValue((int)sessionId) .AppendValue(waitType) .AppendValue(waitDurationMs) diff --git a/Lite/Services/RemoteCollectorService.cs b/Lite/Services/RemoteCollectorService.cs index 53fec1c8..3a10c56d 100644 --- a/Lite/Services/RemoteCollectorService.cs +++ b/Lite/Services/RemoteCollectorService.cs @@ -568,12 +568,22 @@ protected static long GenerateCollectionId() return Interlocked.Increment(ref s_idCounter); } + /// + /// Gets the server name used for DuckDB storage and hashing. + /// Appends ":RO" for ReadOnlyIntent connections so they get a + /// different server_id than read-write connections to the same host. + /// + internal static string GetServerNameForStorage(ServerConnection server) + { + return server.ReadOnlyIntent ? server.ServerName + ":RO" : server.ServerName; + } + /// /// Gets the numeric server ID from the server connection. /// protected static int GetServerId(ServerConnection server) { - return GetDeterministicHashCode(server.ServerName); + return GetDeterministicHashCode(GetServerNameForStorage(server)); } /// diff --git a/Lite/Windows/ManageServersWindow.xaml b/Lite/Windows/ManageServersWindow.xaml index 0060bd7f..63ad4208 100644 --- a/Lite/Windows/ManageServersWindow.xaml +++ b/Lite/Windows/ManageServersWindow.xaml @@ -47,8 +47,8 @@ MouseDoubleClick="ServersGrid_MouseDoubleClick" ContextMenu="{StaticResource DataGridContextMenu}"> - - + + diff --git a/Lite/Windows/ManageServersWindow.xaml.cs b/Lite/Windows/ManageServersWindow.xaml.cs index 8ef3447c..03b6b8e8 100644 --- a/Lite/Windows/ManageServersWindow.xaml.cs +++ b/Lite/Windows/ManageServersWindow.xaml.cs @@ -78,7 +78,7 @@ private void DeleteButton_Click(object sender, RoutedEventArgs e) } var result = MessageBox.Show( - $"Delete server '{selected.DisplayName}'?\n\nThis will remove the server and its stored credentials.", + $"Delete server '{selected.DisplayNameWithIntent}'?\n\nThis will remove the server and its stored credentials.", "Delete Server", MessageBoxButton.YesNo, MessageBoxImage.Warning);