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
7 changes: 6 additions & 1 deletion Lite/Controls/ServerTab.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1203,11 +1203,16 @@
<TabItem Header="Running Jobs">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Currently Running SQL Agent Jobs" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,8"/>
<DataGrid Grid.Row="1" x:Name="RunningJobsGrid"
<TextBlock Grid.Row="1" x:Name="RunningJobsMsdbWarning"
Text="Running Jobs requires msdb access. Grant the login access to msdb to enable this feature."
Foreground="#FFD700" FontSize="12" Margin="0,0,0,8" Visibility="Collapsed"
TextWrapping="Wrap"/>
<DataGrid Grid.Row="2" x:Name="RunningJobsGrid"
AutoGenerateColumns="False" IsReadOnly="True"
HeadersVisibility="Column" GridLinesVisibility="Horizontal"
CanUserSortColumns="True">
Expand Down
10 changes: 9 additions & 1 deletion Lite/Controls/ServerTab.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
};

public int UtcOffsetMinutes { get; }
private readonly bool _hasMsdbAccess;

/// <summary>
/// Raised after each data refresh with alert counts for tab badge display.
Expand All @@ -113,7 +114,7 @@
public event Action<int>? ApplyTimeRangeRequested; /* selectedIndex */
public event Func<Task>? ManualRefreshRequested;

public ServerTab(ServerConnection server, DuckDbInitializer duckDb, CredentialService credentialService, int utcOffsetMinutes = 0)
public ServerTab(ServerConnection server, DuckDbInitializer duckDb, CredentialService credentialService, int utcOffsetMinutes = 0, bool hasMsdbAccess = true)
{
InitializeComponent();

Expand All @@ -122,6 +123,7 @@
_serverId = RemoteCollectorService.GetDeterministicHashCode(RemoteCollectorService.GetServerNameForStorage(server));
_credentialService = credentialService;
UtcOffsetMinutes = utcOffsetMinutes;
_hasMsdbAccess = hasMsdbAccess;
ServerTimeHelper.UtcOffsetMinutes = utcOffsetMinutes;

ServerNameText.Text = server.ReadOnlyIntent ? $"{server.DisplayName} (Read-Only)" : server.DisplayName;
Expand Down Expand Up @@ -158,6 +160,12 @@
};
_refreshTimer.Start();

/* Show warning on Running Jobs tab if login lacks msdb access */
if (!_hasMsdbAccess)
{
RunningJobsMsdbWarning.Visibility = System.Windows.Visibility.Visible;
}

/* Initialize time picker ComboBoxes */
InitializeTimeComboBoxes();

Expand Down Expand Up @@ -645,7 +653,7 @@
}

var tz = ServerTimeHelper.GetTimezoneLabel(ServerTimeHelper.CurrentDisplayMode);
ConnectionStatusText.Text = $"{_server.ServerNameDisplay} - Last refresh: {DateTime.Now:HH:mm:ss} ({tz})";

Check warning on line 656 in Lite/Controls/ServerTab.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 656 in Lite/Controls/ServerTab.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 656 in Lite/Controls/ServerTab.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 656 in Lite/Controls/ServerTab.xaml.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
}
catch (Exception ex)
{
Expand Down
2 changes: 1 addition & 1 deletion Lite/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ private async void ConnectToServer(ServerConnection server)
}

var utcOffset = status.UtcOffsetMinutes ?? 0;
var serverTab = new ServerTab(server, _databaseInitializer, _serverManager.CredentialService, utcOffset);
var serverTab = new ServerTab(server, _databaseInitializer, _serverManager.CredentialService, utcOffset, status.HasMsdbAccess);
var tabHeader = CreateTabHeader(server);
var tabItem = new TabItem
{
Expand Down
6 changes: 6 additions & 0 deletions Lite/Models/ServerConnectionStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ public class ServerConnectionStatus
/// </summary>
public bool IsAwsRds { get; set; }

/// <summary>
/// Whether the connected login has access to msdb.
/// Used for gating collectors that query msdb system tables (e.g., running jobs).
/// </summary>
public bool HasMsdbAccess { get; set; } = true;

/// <summary>
/// The server's UTC offset in minutes, queried via DATEDIFF(MINUTE, GETUTCDATE(), GETDATE()).
/// Used to convert UTC collection_time values to server-local time for display.
Expand Down
15 changes: 13 additions & 2 deletions Lite/Services/RemoteCollectorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,9 @@ public async Task RunCollectorAsync(ServerConnection server, string collectorNam
var majorVersion = serverStatus.SqlMajorVersion;
var engineEdition = serverStatus.SqlEngineEdition;
var isAwsRds = serverStatus.IsAwsRds;
var hasMsdbAccess = serverStatus.HasMsdbAccess;

if (!IsCollectorSupported(collectorName, majorVersion, engineEdition, isAwsRds))
if (!IsCollectorSupported(collectorName, majorVersion, engineEdition, isAwsRds, hasMsdbAccess))
{
AppLogger.Info("Collector", $" [{server.DisplayName}] {collectorName} SKIPPED (version {majorVersion}, edition {engineEdition})");
return;
Expand Down Expand Up @@ -740,7 +741,7 @@ internal static int GetDeterministicHashCode(string value)
/// Version 13 = SQL Server 2016, 14 = 2017, 15 = 2019, 16 = 2022, 17 = 2025.
/// Engine edition 5 = Azure SQL DB, 8 = Azure MI.
/// </summary>
private static bool IsCollectorSupported(string collectorName, int majorVersion, int engineEdition, bool isAwsRds = false)
private static bool IsCollectorSupported(string collectorName, int majorVersion, int engineEdition, bool isAwsRds = false, bool hasMsdbAccess = true)
{
bool isAzureSqlDb = engineEdition == 5;
bool isAzureMi = engineEdition == 8;
Expand Down Expand Up @@ -781,6 +782,16 @@ private static bool IsCollectorSupported(string collectorName, int majorVersion,
}
}

/* msdb access gate — login may not have access to msdb on any edition */
if (!hasMsdbAccess)
{
switch (collectorName)
{
case "running_jobs": /* requires msdb.dbo.sysjobs, sysjobactivity, etc. */
return false;
}
}

return true;
}
}
5 changes: 4 additions & 1 deletion Lite/Services/ServerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,8 @@ public async Task<ServerConnectionStatus> CheckConnectionAsync(string serverId,
CONVERT(integer, SERVERPROPERTY('ProductMajorVersion')) AS major_version,
DATEDIFF(MINUTE, GETUTCDATE(), GETDATE()) AS utc_offset_minutes,
CONVERT(integer, SERVERPROPERTY('EngineEdition')) AS engine_edition,
CASE WHEN DB_ID('rdsadmin') IS NOT NULL THEN 1 ELSE 0 END AS is_aws_rds
CASE WHEN DB_ID('rdsadmin') IS NOT NULL THEN 1 ELSE 0 END AS is_aws_rds,
HAS_DBACCESS(N'msdb') AS has_msdb_access
FROM sys.dm_os_sys_info", connection);
command.CommandTimeout = ConnectionCheckTimeoutSeconds;

Expand All @@ -366,6 +367,8 @@ CASE WHEN DB_ID('rdsadmin') IS NOT NULL THEN 1 ELSE 0 END AS is_aws_rds
status.SqlEngineEdition = Convert.ToInt32(reader.GetValue(4));
if (!reader.IsDBNull(5))
status.IsAwsRds = Convert.ToInt32(reader.GetValue(5)) == 1;
if (!reader.IsDBNull(6))
status.HasMsdbAccess = Convert.ToInt32(reader.GetValue(6)) == 1;
}
}
catch (SqlException metaEx)
Expand Down
Loading