Skip to content

Commit f8d1b63

Browse files
Fix chart zero lines, collection log server name, and overview online status (#85, #91, #93)
#85: Blocking/deadlock trend charts now show a visible flat line at zero instead of empty space. Added negative Y-axis margin so zero-value data renders above the axis border. Reduced fallback Y range from +100 to +1. #91: Added server_name column to collection_log DuckDB table (schema v10) so log entries identify which server produced them without a lookup join. #93: Sidebar status dots now reflect actual connection check results (IsOnline) instead of the IsEnabled user config toggle. Added IsOnline property to ServerConnection model, synced from connection status in RefreshServerList and CheckConnectionsAndNotify. Fixed first-check refresh so dots update as soon as initial connection checks complete. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0b8a840 commit f8d1b63

8 files changed

Lines changed: 74 additions & 17 deletions

File tree

Lite/Controls/ServerTab.xaml.cs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -849,12 +849,19 @@ private void UpdateBlockingTrendChart(List<TrendPoint> data, int hoursBack, Date
849849

850850
if (data.Count == 0)
851851
{
852-
/* Show empty chart with correct time range */
852+
/* No blocking events — show a flat line at zero so the chart looks active */
853+
var zeroLine = BlockingTrendChart.Plot.Add.Scatter(
854+
new[] { rangeStart.ToOADate(), rangeEnd.ToOADate() },
855+
new[] { 0.0, 0.0 });
856+
zeroLine.LegendText = "Blocking Incidents";
857+
zeroLine.Color = ScottPlot.Color.FromHex("#E57373");
858+
zeroLine.MarkerSize = 0;
853859
BlockingTrendChart.Plot.Axes.DateTimeTicksBottom();
854860
BlockingTrendChart.Plot.Axes.SetLimitsX(rangeStart.ToOADate(), rangeEnd.ToOADate());
855-
BlockingTrendChart.Plot.Axes.SetLimitsY(0, 1);
856861
ReapplyAxisColors(BlockingTrendChart);
857862
BlockingTrendChart.Plot.YLabel("Blocking Incidents");
863+
SetChartYLimitsWithLegendPadding(BlockingTrendChart, 0, 1);
864+
ShowChartLegend(BlockingTrendChart);
858865
BlockingTrendChart.Refresh();
859866
return;
860867
}
@@ -919,12 +926,19 @@ private void UpdateDeadlockTrendChart(List<TrendPoint> data, int hoursBack, Date
919926

920927
if (data.Count == 0)
921928
{
922-
/* Show empty chart with correct time range */
929+
/* No deadlocks — show a flat line at zero so the chart looks active */
930+
var zeroLine = DeadlockTrendChart.Plot.Add.Scatter(
931+
new[] { rangeStart.ToOADate(), rangeEnd.ToOADate() },
932+
new[] { 0.0, 0.0 });
933+
zeroLine.LegendText = "Deadlocks";
934+
zeroLine.Color = ScottPlot.Color.FromHex("#FFB74D");
935+
zeroLine.MarkerSize = 0;
923936
DeadlockTrendChart.Plot.Axes.DateTimeTicksBottom();
924937
DeadlockTrendChart.Plot.Axes.SetLimitsX(rangeStart.ToOADate(), rangeEnd.ToOADate());
925-
DeadlockTrendChart.Plot.Axes.SetLimitsY(0, 1);
926938
ReapplyAxisColors(DeadlockTrendChart);
927939
DeadlockTrendChart.Plot.YLabel("Deadlocks");
940+
SetChartYLimitsWithLegendPadding(DeadlockTrendChart, 0, 1);
941+
ShowChartLegend(DeadlockTrendChart);
928942
DeadlockTrendChart.Refresh();
929943
return;
930944
}
@@ -1495,13 +1509,13 @@ private static void SetChartYLimitsWithLegendPadding(ScottPlot.WPF.WpfPlot chart
14951509
dataYMin = limits.Bottom;
14961510
dataYMax = limits.Top;
14971511
}
1498-
if (dataYMax <= dataYMin) dataYMax = dataYMin + 100;
1512+
if (dataYMax <= dataYMin) dataYMax = dataYMin + 1;
14991513

15001514
double range = dataYMax - dataYMin;
15011515
double topPadding = range * 0.05;
15021516

1503-
/* Only add bottom padding if dataYMin is above zero - don't go negative */
1504-
double yMin = dataYMin >= 0 ? 0 : dataYMin - (range * 0.10);
1517+
/* Add small bottom margin when dataYMin is zero so flat lines at Y=0 are visible above the axis */
1518+
double yMin = dataYMin > 0 ? 0 : dataYMin == 0 ? -(range * 0.05) : dataYMin - (range * 0.10);
15051519
double yMax = dataYMax + topPadding;
15061520

15071521
chart.Plot.Axes.SetLimitsY(yMin, yMax);

Lite/Database/DuckDbInitializer.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class DuckDbInitializer
1919
/// <summary>
2020
/// Current schema version. Increment this when schema changes require table rebuilds.
2121
/// </summary>
22-
private const int CurrentSchemaVersion = 9;
22+
private const int CurrentSchemaVersion = 10;
2323

2424
public DuckDbInitializer(string databasePath, ILogger<DuckDbInitializer>? logger = null)
2525
{
@@ -328,6 +328,21 @@ Safe to ALTER because this table uses INSERT (not appender). */
328328
/* Table doesn't exist yet — will be created with correct schema below */
329329
}
330330
}
331+
332+
if (fromVersion < 10)
333+
{
334+
/* v10: Added server_name column to collection_log so log entries
335+
can be identified by server without needing a lookup table. */
336+
_logger?.LogInformation("Running migration to v10: adding server_name column to collection_log");
337+
try
338+
{
339+
await ExecuteNonQueryAsync(connection, "ALTER TABLE collection_log ADD COLUMN IF NOT EXISTS server_name VARCHAR");
340+
}
341+
catch
342+
{
343+
/* Table doesn't exist yet — will be created with correct schema below */
344+
}
345+
}
331346
}
332347

333348
/// <summary>

Lite/Database/Schema.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ modified_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
3838
CREATE TABLE IF NOT EXISTS collection_log (
3939
log_id BIGINT PRIMARY KEY,
4040
server_id INTEGER NOT NULL,
41+
server_name VARCHAR,
4142
collector_name VARCHAR NOT NULL,
4243
collection_time TIMESTAMP NOT NULL,
4344
duration_ms INTEGER,

Lite/MainWindow.xaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@
9797
<Style TargetType="Ellipse">
9898
<Setter Property="Fill" Value="{DynamicResource ForegroundMutedBrush}"/>
9999
<Style.Triggers>
100-
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
100+
<DataTrigger Binding="{Binding IsOnline}" Value="True">
101101
<Setter Property="Fill" Value="{DynamicResource SuccessBrush}"/>
102102
</DataTrigger>
103-
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
103+
<DataTrigger Binding="{Binding IsOnline}" Value="False">
104104
<Setter Property="Fill" Value="{DynamicResource ErrorBrush}"/>
105105
</DataTrigger>
106106
</Style.Triggers>

Lite/MainWindow.xaml.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ private void ServerTabControl_SelectionChanged(object sender, SelectionChangedEv
249249
private void RefreshServerList()
250250
{
251251
var servers = _serverManager.GetAllServers();
252+
foreach (var server in servers)
253+
{
254+
server.IsOnline = _serverManager.GetConnectionStatus(server.Id).IsOnline;
255+
}
252256
ServerListView.ItemsSource = servers;
253257

254258
// Update UI based on server count
@@ -883,9 +887,11 @@ private void CheckConnectionsAndNotify()
883887
try
884888
{
885889
var servers = _serverManager.GetAllServers();
890+
bool needsRefresh = false;
886891
foreach (var server in servers)
887892
{
888893
var status = _serverManager.GetConnectionStatus(server.Id);
894+
server.IsOnline = status?.IsOnline;
889895
if (status?.IsOnline == null) continue;
890896

891897
bool isOnline = status.IsOnline == true;
@@ -912,12 +918,22 @@ private void CheckConnectionsAndNotify()
912918

913919
if (wasOnline != isOnline)
914920
{
915-
RefreshServerList();
921+
needsRefresh = true;
916922
}
917923
}
924+
else
925+
{
926+
/* First time seeing this server's status — need to refresh */
927+
needsRefresh = true;
928+
}
918929

919930
_previousConnectionStates[server.Id] = isOnline;
920931
}
932+
933+
if (needsRefresh)
934+
{
935+
RefreshServerList();
936+
}
921937
}
922938
catch (Exception ex)
923939
{

Lite/Models/ServerConnection.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ public bool UseWindowsAuth
8181
_ => "Windows"
8282
};
8383

84+
/// <summary>
85+
/// Actual connection status from the most recent connection check.
86+
/// null = not checked yet, true = online, false = offline.
87+
/// </summary>
88+
[JsonIgnore]
89+
public bool? IsOnline { get; set; }
90+
8491
/// <summary>
8592
/// Display-only property for showing status in UI.
8693
/// </summary>

Lite/Services/LocalDataService.CollectionHealth.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ public async Task<List<CollectionLogRow>> GetRecentCollectionLogAsync(int server
7979
duckdb_duration_ms,
8080
rows_collected,
8181
status,
82-
error_message
82+
error_message,
83+
server_name
8384
FROM collection_log
8485
WHERE server_id = $1
8586
AND collection_time >= $2
@@ -103,7 +104,8 @@ ORDER BY collection_time DESC
103104
DuckDbDurationMs = reader.IsDBNull(4) ? null : (int?)Convert.ToInt32(reader.GetValue(4)),
104105
RowsCollected = reader.IsDBNull(5) ? null : (int?)Convert.ToInt32(reader.GetValue(5)),
105106
Status = reader.GetString(6),
106-
ErrorMessage = reader.IsDBNull(7) ? null : reader.GetString(7)
107+
ErrorMessage = reader.IsDBNull(7) ? null : reader.GetString(7),
108+
ServerName = reader.IsDBNull(8) ? null : reader.GetString(8)
107109
});
108110
}
109111

@@ -114,6 +116,7 @@ ORDER BY collection_time DESC
114116
public class CollectionLogRow
115117
{
116118
public string CollectorName { get; set; } = "";
119+
public string? ServerName { get; set; }
117120
public DateTime CollectionTime { get; set; }
118121
public int? DurationMs { get; set; }
119122
public int? SqlDurationMs { get; set; }

Lite/Services/RemoteCollectorService.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -363,13 +363,13 @@ public async Task RunCollectorAsync(ServerConnection server, string collectorNam
363363
RecordCollectorResult(GetServerId(server), collectorName, status == "SUCCESS", errorMessage);
364364

365365
// Log the collection attempt
366-
await LogCollectionAsync(GetServerId(server), collectorName, startTime, status, errorMessage, rowsCollected, _lastSqlMs, _lastDuckDbMs);
366+
await LogCollectionAsync(GetServerId(server), server.DisplayName, collectorName, startTime, status, errorMessage, rowsCollected, _lastSqlMs, _lastDuckDbMs);
367367
}
368368

369369
/// <summary>
370370
/// Logs a collection attempt to the collection_log table.
371371
/// </summary>
372-
private async Task LogCollectionAsync(int serverId, string collectorName, DateTime startTime, string status, string? errorMessage, int rowsCollected, long sqlMs = 0, long duckDbMs = 0)
372+
private async Task LogCollectionAsync(int serverId, string serverName, string collectorName, DateTime startTime, string status, string? errorMessage, int rowsCollected, long sqlMs = 0, long duckDbMs = 0)
373373
{
374374
try
375375
{
@@ -380,11 +380,12 @@ private async Task LogCollectionAsync(int serverId, string collectorName, DateTi
380380

381381
using var command = connection.CreateCommand();
382382
command.CommandText = @"
383-
INSERT INTO collection_log (log_id, server_id, collector_name, collection_time, duration_ms, status, error_message, rows_collected, sql_duration_ms, duckdb_duration_ms)
384-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
383+
INSERT INTO collection_log (log_id, server_id, server_name, collector_name, collection_time, duration_ms, status, error_message, rows_collected, sql_duration_ms, duckdb_duration_ms)
384+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)";
385385

386386
command.Parameters.Add(new DuckDBParameter { Value = GenerateCollectionId() });
387387
command.Parameters.Add(new DuckDBParameter { Value = serverId });
388+
command.Parameters.Add(new DuckDBParameter { Value = serverName });
388389
command.Parameters.Add(new DuckDBParameter { Value = collectorName });
389390
command.Parameters.Add(new DuckDBParameter { Value = startTime });
390391
command.Parameters.Add(new DuckDBParameter { Value = durationMs });

0 commit comments

Comments
 (0)