|
10 | 10 | <div> |
11 | 11 | <p class="text-sm font-black uppercase tracking-[0.28em] text-es-green">Observability</p> |
12 | 12 | <h1 class="mt-4 text-5xl font-black tracking-tight text-es-ink sm:text-6xl">A map of the signals the node already emits.</h1> |
13 | | - <p class="mt-5 text-lg leading-8 text-es-muted">Inspect live queue pressure, processing throughput, health, metrics, replication, TCP, and grouped runtime statistics.</p> |
| 13 | + <p class="mt-5 text-lg leading-8 text-es-muted">Inspect live queue pressure, processing throughput, health, metrics, replication, and grouped runtime statistics.</p> |
14 | 14 | </div> |
15 | 15 | <div class="grid gap-3 rounded-[2rem] border border-white/80 bg-white/85 p-6 shadow-[0_20px_70px_rgba(23,32,51,0.09)] sm:grid-cols-2"> |
16 | 16 | <MetricCard Label="Queues" Value="@(Page?.QueueCountLabel ?? "-")" QueueMetric="queue-count" /> |
|
20 | 20 | </div> |
21 | 21 | </section> |
22 | 22 |
|
| 23 | +<section class="mt-8"> |
| 24 | + <div class="mb-4 flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between"> |
| 25 | + <div> |
| 26 | + <p class="text-xs font-black uppercase tracking-[0.22em] text-es-green">Network boundary</p> |
| 27 | + <h2 class="mt-2 text-3xl font-black tracking-tight text-es-ink">Shared HTTP and gRPC connections</h2> |
| 28 | + <p class="mt-2 text-sm text-es-muted">Active connections accepted by the node endpoint, including per-second traffic rates and pending bytes.</p> |
| 29 | + </div> |
| 30 | + <div class="flex flex-wrap gap-2"> |
| 31 | + <span class="rounded-full border border-es-green/20 bg-es-green/10 px-4 py-2 text-sm font-bold text-es-forest" data-network-status>@NetworkStatusLabel</span> |
| 32 | + <span class="rounded-full border border-es-ink/10 bg-white px-4 py-2 text-sm font-bold text-es-muted" data-network-page-status>@NetworkPageStatusLabel</span> |
| 33 | + <button type="button" class="rounded-full border border-es-ink/10 bg-white px-4 py-2 text-sm font-bold text-es-ink transition hover:border-es-green/30 hover:text-es-green disabled:cursor-not-allowed disabled:opacity-40" data-network-page="previous" disabled>Previous</button> |
| 34 | + <button type="button" class="rounded-full border border-es-ink/10 bg-white px-4 py-2 text-sm font-bold text-es-ink transition hover:border-es-green/30 hover:text-es-green disabled:cursor-not-allowed disabled:opacity-40" data-network-page="next" disabled>Next</button> |
| 35 | + </div> |
| 36 | + </div> |
| 37 | + |
| 38 | + <div class="overflow-hidden rounded-[2rem] border border-white/80 bg-white/85 shadow-[0_20px_70px_rgba(23,32,51,0.09)]"> |
| 39 | + <div class="overflow-x-auto"> |
| 40 | + <table class="min-w-full divide-y divide-es-ink/10 text-left text-sm"> |
| 41 | + <thead class="bg-es-ink/5 text-xs font-black uppercase tracking-[0.18em] text-es-muted"> |
| 42 | + <tr> |
| 43 | + <th class="px-5 py-4">Connection</th> |
| 44 | + <th class="px-5 py-4">Client</th> |
| 45 | + <th class="px-5 py-4">Type</th> |
| 46 | + <th class="px-5 py-4">Remote endpoint</th> |
| 47 | + <th class="px-5 py-4 text-right">Sent rate</th> |
| 48 | + <th class="px-5 py-4 text-right">Sent current</th> |
| 49 | + <th class="px-5 py-4 text-right">Sent pending</th> |
| 50 | + <th class="px-5 py-4 text-right">Received rate</th> |
| 51 | + <th class="px-5 py-4 text-right">Received current</th> |
| 52 | + <th class="px-5 py-4 text-right">Received pending</th> |
| 53 | + </tr> |
| 54 | + </thead> |
| 55 | + <tbody class="divide-y divide-es-ink/10" data-network-table-body> |
| 56 | + @if (NetworkRows.Count == 0) |
| 57 | + { |
| 58 | + <tr><td class="px-5 py-4 text-es-muted" colspan="10">No active shared-endpoint connections.</td></tr> |
| 59 | + } |
| 60 | + else |
| 61 | + { |
| 62 | + foreach (var connection in NetworkRows.Take(5)) |
| 63 | + { |
| 64 | + <tr> |
| 65 | + <td class="px-5 py-4 font-mono text-xs text-es-muted">@connection.ConnectionId</td> |
| 66 | + <td class="px-5 py-4 font-bold text-es-ink">@Display(connection.ClientName)</td> |
| 67 | + <td class="px-5 py-4 text-es-muted">@connection.Application · @Display(connection.Protocol) · @(connection.IsTls ? "TLS" : "Cleartext")</td> |
| 68 | + <td class="px-5 py-4 font-mono text-xs text-es-muted" title="@ConnectionDetails(connection)">@connection.RemoteEndPoint</td> |
| 69 | + <td class="px-5 py-4 text-right font-mono text-es-muted">Waiting</td> |
| 70 | + <td class="px-5 py-4 text-right font-mono text-es-ink">@FormatBytes(connection.TotalBytesSent)</td> |
| 71 | + <td class="px-5 py-4 text-right font-mono text-es-ink">@FormatBytes(connection.PendingSendBytes)</td> |
| 72 | + <td class="px-5 py-4 text-right font-mono text-es-muted">Waiting</td> |
| 73 | + <td class="px-5 py-4 text-right font-mono text-es-ink">@FormatBytes(connection.TotalBytesReceived)</td> |
| 74 | + <td class="px-5 py-4 text-right font-mono text-es-ink">@FormatBytes(connection.PendingReceivedBytes)</td> |
| 75 | + </tr> |
| 76 | + } |
| 77 | + } |
| 78 | + </tbody> |
| 79 | + </table> |
| 80 | + </div> |
| 81 | + </div> |
| 82 | +</section> |
| 83 | + |
| 84 | +<section class="mt-8"> |
| 85 | + <div class="mb-4"> |
| 86 | + <p class="text-xs font-black uppercase tracking-[0.22em] text-es-green">Cluster transport</p> |
| 87 | + <h2 class="mt-2 text-3xl font-black tracking-tight text-es-ink">gRPC replication connections</h2> |
| 88 | + <p class="mt-2 text-sm text-es-muted">Live database replication sessions on the shared HTTP/2 endpoint.</p> |
| 89 | + </div> |
| 90 | + |
| 91 | + <div class="overflow-hidden rounded-[2rem] border border-white/80 bg-white/85 shadow-[0_20px_70px_rgba(23,32,51,0.09)]"> |
| 92 | + <div class="overflow-x-auto"> |
| 93 | + <table class="min-w-full divide-y divide-es-ink/10 text-left text-sm"> |
| 94 | + <thead class="bg-es-ink/5 text-xs font-black uppercase tracking-[0.18em] text-es-muted"> |
| 95 | + <tr> |
| 96 | + <th class="px-5 py-4">Endpoint</th> |
| 97 | + <th class="px-5 py-4">Connection</th> |
| 98 | + <th class="px-5 py-4 text-right">Sent</th> |
| 99 | + <th class="px-5 py-4 text-right">Received</th> |
| 100 | + <th class="px-5 py-4 text-right">Pending</th> |
| 101 | + <th class="px-5 py-4 text-right">Send queue</th> |
| 102 | + </tr> |
| 103 | + </thead> |
| 104 | + <tbody class="divide-y divide-es-ink/10" data-replication-table-body> |
| 105 | + @if (Page is not null && Page.ReplicationConnections.Count > 0) |
| 106 | + { |
| 107 | + @foreach (var connection in Page.ReplicationConnections) |
| 108 | + { |
| 109 | + <tr> |
| 110 | + <td class="px-5 py-4 font-bold text-es-ink">@connection.Endpoint</td> |
| 111 | + <td class="px-5 py-4 font-mono text-xs text-es-muted" title="Subscription @connection.SubscriptionId">@connection.ConnectionId</td> |
| 112 | + <td class="px-5 py-4 text-right font-mono text-es-ink">@FormatBytes(connection.TotalBytesSent)</td> |
| 113 | + <td class="px-5 py-4 text-right font-mono text-es-ink">@FormatBytes(connection.TotalBytesReceived)</td> |
| 114 | + <td class="px-5 py-4 text-right font-mono text-es-ink">@FormatBytes(connection.PendingSendBytes + connection.PendingReceivedBytes)</td> |
| 115 | + <td class="px-5 py-4 text-right font-mono text-es-ink">@connection.SendQueueSize.ToString("N0", CultureInfo.InvariantCulture)</td> |
| 116 | + </tr> |
| 117 | + } |
| 118 | + } |
| 119 | + else |
| 120 | + { |
| 121 | + <tr><td class="px-5 py-4 text-es-muted" colspan="6">No active gRPC replication connections.</td></tr> |
| 122 | + } |
| 123 | + </tbody> |
| 124 | + </table> |
| 125 | + </div> |
| 126 | + </div> |
| 127 | +</section> |
| 128 | + |
23 | 129 | <section class="mt-8"> |
24 | 130 | <details id="dashboard-snapshot" class="rounded-[2rem] border border-white/80 bg-white/85 p-6 shadow-[0_20px_70px_rgba(23,32,51,0.09)]"> |
25 | 131 | <summary class="cursor-pointer text-sm font-black uppercase tracking-[0.22em] text-es-green">Dashboard snapshot</summary> |
|
87 | 193 | } |
88 | 194 | </section> |
89 | 195 |
|
90 | | -<section class="mt-8"> |
91 | | - <div class="mb-4 flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between"> |
92 | | - <div> |
93 | | - <p class="text-xs font-black uppercase tracking-[0.22em] text-es-green">TCP dashboard</p> |
94 | | - <h2 class="mt-2 text-3xl font-black tracking-tight text-es-ink">Realtime connections</h2> |
95 | | - <p class="mt-2 text-sm text-es-muted">Shows active connections with per-second sent and received byte rates.</p> |
96 | | - </div> |
97 | | - <div class="flex flex-wrap gap-2"> |
98 | | - <span class="rounded-full border border-es-green/20 bg-es-green/10 px-4 py-2 text-sm font-bold text-es-forest" data-tcp-status>@TcpStatusLabel</span> |
99 | | - <span class="rounded-full border border-es-ink/10 bg-white px-4 py-2 text-sm font-bold text-es-muted" data-tcp-page-status>@TcpPageStatusLabel</span> |
100 | | - <button type="button" class="rounded-full border border-es-ink/10 bg-white px-4 py-2 text-sm font-bold text-es-ink transition hover:border-es-green/30 hover:text-es-green disabled:cursor-not-allowed disabled:opacity-40" data-tcp-page="previous" disabled>Previous</button> |
101 | | - <button type="button" class="rounded-full border border-es-ink/10 bg-white px-4 py-2 text-sm font-bold text-es-ink transition hover:border-es-green/30 hover:text-es-green disabled:cursor-not-allowed disabled:opacity-40" data-tcp-page="next" disabled>Next</button> |
102 | | - </div> |
103 | | - </div> |
104 | | - |
105 | | - <div class="overflow-hidden rounded-[2rem] border border-white/80 bg-white/85 shadow-[0_20px_70px_rgba(23,32,51,0.09)]"> |
106 | | - <div class="overflow-x-auto"> |
107 | | - <table class="min-w-full divide-y divide-es-ink/10 text-left text-sm"> |
108 | | - <thead class="bg-es-ink/5 text-xs font-black uppercase tracking-[0.18em] text-es-muted"> |
109 | | - <tr> |
110 | | - <th class="px-5 py-4">Connection</th> |
111 | | - <th class="px-5 py-4">Client</th> |
112 | | - <th class="px-5 py-4">Type</th> |
113 | | - <th class="px-5 py-4">IP Address</th> |
114 | | - <th class="px-5 py-4 text-right">Sent rate</th> |
115 | | - <th class="px-5 py-4 text-right">Sent current</th> |
116 | | - <th class="px-5 py-4 text-right">Sent pending</th> |
117 | | - <th class="px-5 py-4 text-right">Received rate</th> |
118 | | - <th class="px-5 py-4 text-right">Received current</th> |
119 | | - <th class="px-5 py-4 text-right">Received pending</th> |
120 | | - </tr> |
121 | | - </thead> |
122 | | - <tbody class="divide-y divide-es-ink/10" data-tcp-table-body> |
123 | | - @if (TcpRows.Count == 0) |
124 | | - { |
125 | | - <tr> |
126 | | - <td class="px-5 py-4 text-es-muted" colspan="10">@TcpEmptyMessage</td> |
127 | | - </tr> |
128 | | - } |
129 | | - else |
130 | | - { |
131 | | - foreach (var connection in TcpRows.Take(5)) |
132 | | - { |
133 | | - @RenderTcpRow(connection) |
134 | | - } |
135 | | - } |
136 | | - </tbody> |
137 | | - </table> |
138 | | - </div> |
139 | | - </div> |
140 | | -</section> |
141 | | - |
142 | 196 | </div> |
143 | 197 |
|
144 | 198 | @code { |
|
148 | 202 | private string Expanded { get; set; } = ""; |
149 | 203 |
|
150 | 204 | private string DashboardPayloadJson => Page?.ClientPayloadJson ?? "{}"; |
151 | | - private IReadOnlyList<TcpConnectionRow> TcpRows => Page?.TcpConnections ?? Array.Empty<TcpConnectionRow>(); |
152 | | - private string TcpErrorMessage => !string.IsNullOrWhiteSpace(Page?.TcpMessage) |
153 | | - ? Page.TcpMessage |
154 | | - : Page?.Message ?? ""; |
155 | | - private string TcpStatusLabel { |
156 | | - get { |
157 | | - if (Page is null) |
158 | | - return "Connecting TCP stats..."; |
159 | | - |
160 | | - if (!string.IsNullOrWhiteSpace(TcpErrorMessage)) |
161 | | - return "TCP unavailable"; |
162 | | - |
163 | | - return TcpRows.Count == 0 |
164 | | - ? "TCP live" |
165 | | - : string.Create(CultureInfo.InvariantCulture, $"TCP live · {TcpRows.Count} connection{(TcpRows.Count == 1 ? "" : "s")}"); |
166 | | - } |
167 | | - } |
168 | | - private string TcpPageStatusLabel => TcpRows.Count == 0 |
| 205 | + private IReadOnlyList<NodeConnectionSnapshot> NetworkRows => |
| 206 | + Page?.NodeConnections ?? Array.Empty<NodeConnectionSnapshot>(); |
| 207 | + private string NetworkStatusLabel => NetworkRows.Count == 0 |
| 208 | + ? "Network live" |
| 209 | + : string.Create(CultureInfo.InvariantCulture, $"Network live · {NetworkRows.Count} connection{(NetworkRows.Count == 1 ? "" : "s")}"); |
| 210 | + private string NetworkPageStatusLabel => NetworkRows.Count == 0 |
169 | 211 | ? "No pages" |
170 | | - : string.Create(CultureInfo.InvariantCulture, $"Page 1 of {Math.Max(1, (int)Math.Ceiling(TcpRows.Count / 5.0))}"); |
171 | | - private string TcpEmptyMessage => string.IsNullOrWhiteSpace(TcpErrorMessage) |
172 | | - ? "No TCP connections are currently reported." |
173 | | - : TcpErrorMessage; |
| 212 | + : string.Create(CultureInfo.InvariantCulture, $"Page 1 of {Math.Max(1, (int)Math.Ceiling(NetworkRows.Count / 5.0))}"); |
| 213 | + |
| 214 | + private static string FormatBytes(long value) => |
| 215 | + value.ToString("N0", CultureInfo.InvariantCulture) + " B"; |
| 216 | + private static string Display(string value) => string.IsNullOrWhiteSpace(value) ? "<none>" : value; |
| 217 | + private static string ConnectionDetails(NodeConnectionSnapshot connection) => |
| 218 | + string.Create(CultureInfo.InvariantCulture, |
| 219 | + $"Local {connection.LocalEndPoint}, connected {connection.ConnectedAt:u}"); |
174 | 220 |
|
175 | 221 | protected override async Task OnParametersSetAsync() { |
176 | 222 | Page = null; |
|
216 | 262 | <td class="px-5 py-4 font-mono text-xs text-es-muted">@row.CurrentLastMessageLabel</td> |
217 | 263 | </tr>; |
218 | 264 |
|
219 | | - private RenderFragment RenderTcpRow(TcpConnectionRow connection) => @<tr class="bg-white/70 text-es-ink"> |
220 | | - <td class="max-w-[14rem] truncate px-5 py-4 font-mono text-xs text-es-muted">@connection.IdLabel</td> |
221 | | - <td class="px-5 py-4 font-bold text-es-ink">@connection.ClientLabel</td> |
222 | | - <td class="px-5 py-4 text-es-muted">@connection.TypeLabel</td> |
223 | | - <td class="px-5 py-4 font-mono text-xs text-es-muted">@connection.RemoteEndPointLabel</td> |
224 | | - <td class="px-5 py-4 text-right font-mono text-es-ink">@connection.SentRateLabel</td> |
225 | | - <td class="px-5 py-4 text-right font-mono text-es-ink">@connection.TotalBytesSentLabel</td> |
226 | | - <td class="px-5 py-4 text-right font-mono text-es-ink">@connection.PendingSendBytesLabel</td> |
227 | | - <td class="px-5 py-4 text-right font-mono text-es-ink">@connection.ReceivedRateLabel</td> |
228 | | - <td class="px-5 py-4 text-right font-mono text-es-ink">@connection.TotalBytesReceivedLabel</td> |
229 | | - <td class="px-5 py-4 text-right font-mono text-es-ink">@connection.PendingReceivedBytesLabel</td> |
230 | | - </tr>; |
231 | | - |
232 | 265 | private bool IsExpanded(string groupName) => |
233 | 266 | ParseExpandedGroups().Contains(groupName); |
234 | 267 |
|
|
0 commit comments