fix(network-ui): enable flashing table updates for network activity, remote ingress, and VPN views

This commit is contained in:
2026-04-08 15:26:12 +00:00
parent ed52a3188d
commit 36a2ebc94e
6 changed files with 37 additions and 33 deletions

View File

@@ -1,5 +1,12 @@
# Changelog # Changelog
## 2026-04-08 - 13.9.1 - fix(network-ui)
enable flashing table updates for network activity, remote ingress, and VPN views
- adds stable row keys to dees-table instances so existing rows can be diffed correctly
- enables flash highlighting for changed rows and cells across network activity, top IPs, backends, remote ingress edges, and VPN clients
- updates network activity request data on every refresh so live metrics like duration and byte counts visibly refresh
## 2026-04-08 - 13.9.0 - feat(dns) ## 2026-04-08 - 13.9.0 - feat(dns)
add built-in dcrouter DNS provider support and rename manual domains to dcrouter-hosted/local add built-in dcrouter DNS provider support and rename manual domains to dcrouter-hosted/local

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '13.9.0', version: '13.9.1',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '13.9.0', version: '13.9.1',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -323,6 +323,8 @@ export class OpsViewNetworkActivity extends DeesElement {
<!-- Requests Table --> <!-- Requests Table -->
<dees-table <dees-table
.data=${this.networkRequests} .data=${this.networkRequests}
.rowKey=${'id'}
.highlightUpdates=${'flash'}
.displayFunction=${(req: INetworkRequest) => ({ .displayFunction=${(req: INetworkRequest) => ({
Time: new Date(req.timestamp).toLocaleTimeString(), Time: new Date(req.timestamp).toLocaleTimeString(),
Protocol: html`<span class="protocolBadge ${req.protocol}">${req.protocol.toUpperCase()}</span>`, Protocol: html`<span class="protocolBadge ${req.protocol}">${req.protocol.toUpperCase()}</span>`,
@@ -595,6 +597,8 @@ export class OpsViewNetworkActivity extends DeesElement {
return html` return html`
<dees-table <dees-table
.data=${this.networkState.topIPs} .data=${this.networkState.topIPs}
.rowKey=${'ip'}
.highlightUpdates=${'flash'}
.displayFunction=${(ipData: { ip: string; count: number }) => { .displayFunction=${(ipData: { ip: string; count: number }) => {
const bw = bandwidthByIP.get(ipData.ip); const bw = bandwidthByIP.get(ipData.ip);
return { return {
@@ -624,6 +628,8 @@ export class OpsViewNetworkActivity extends DeesElement {
return html` return html`
<dees-table <dees-table
.data=${backends} .data=${backends}
.rowKey=${'backend'}
.highlightUpdates=${'flash'}
.displayFunction=${(item: interfaces.data.IBackendInfo) => { .displayFunction=${(item: interfaces.data.IBackendInfo) => {
const totalErrors = item.connectErrors + item.handshakeErrors + item.requestErrors; const totalErrors = item.connectErrors + item.handshakeErrors + item.requestErrors;
const protocolClass = item.protocol.toLowerCase().replace(/[^a-z0-9]/g, ''); const protocolClass = item.protocol.toLowerCase().replace(/[^a-z0-9]/g, '');
@@ -724,37 +730,24 @@ export class OpsViewNetworkActivity extends DeesElement {
this.requestsPerSecHistory.shift(); this.requestsPerSecHistory.shift();
} }
// Only update if connections changed significantly // Reassign unconditionally so dees-table's flash diff can compare per-cell
const newConnectionCount = this.networkState.connections.length; // values against the previous snapshot. Row identity is preserved via
const oldConnectionCount = this.networkRequests.length; // rowKey='id', so DOM nodes are reused across ticks.
this.networkRequests = this.networkState.connections.map((conn) => ({
// Check if we need to update the network requests array id: conn.id,
const shouldUpdate = newConnectionCount !== oldConnectionCount || timestamp: conn.startTime,
newConnectionCount === 0 || method: 'GET', // Default method for proxy connections
(newConnectionCount > 0 && this.networkRequests.length === 0); url: '/',
hostname: conn.remoteAddress,
if (shouldUpdate) { port: conn.protocol === 'https' ? 443 : 80,
// Convert connection data to network requests format protocol: conn.protocol === 'https' || conn.protocol === 'http' ? conn.protocol : 'tcp',
if (newConnectionCount > 0) { statusCode: conn.state === 'connected' ? 200 : undefined,
this.networkRequests = this.networkState.connections.map((conn, index) => ({ duration: Date.now() - conn.startTime,
id: conn.id, bytesIn: conn.bytesReceived,
timestamp: conn.startTime, bytesOut: conn.bytesSent,
method: 'GET', // Default method for proxy connections remoteIp: conn.remoteAddress,
url: '/', route: 'proxy',
hostname: conn.remoteAddress, }));
port: conn.protocol === 'https' ? 443 : 80,
protocol: conn.protocol === 'https' || conn.protocol === 'http' ? conn.protocol : 'tcp',
statusCode: conn.state === 'connected' ? 200 : undefined,
duration: Date.now() - conn.startTime,
bytesIn: conn.bytesReceived,
bytesOut: conn.bytesSent,
remoteIp: conn.remoteAddress,
route: 'proxy',
}));
} else {
this.networkRequests = [];
}
}
// Load server-side throughput history into chart (once) // Load server-side throughput history into chart (once)
if (!this.historyLoaded && this.networkState.throughputHistory && this.networkState.throughputHistory.length > 0) { if (!this.historyLoaded && this.networkState.throughputHistory && this.networkState.throughputHistory.length > 0) {

View File

@@ -220,6 +220,8 @@ export class OpsViewRemoteIngress extends DeesElement {
.heading1=${'Edge Nodes'} .heading1=${'Edge Nodes'}
.heading2=${'Manage remote ingress edge registrations'} .heading2=${'Manage remote ingress edge registrations'}
.data=${this.riState.edges} .data=${this.riState.edges}
.rowKey=${'id'}
.highlightUpdates=${'flash'}
.showColumnFilters=${true} .showColumnFilters=${true}
.displayFunction=${(edge: interfaces.data.IRemoteIngress) => ({ .displayFunction=${(edge: interfaces.data.IRemoteIngress) => ({
name: edge.name, name: edge.name,

View File

@@ -305,6 +305,8 @@ export class OpsViewVpn extends DeesElement {
.heading1=${'VPN Clients'} .heading1=${'VPN Clients'}
.heading2=${'Manage WireGuard and SmartVPN client registrations'} .heading2=${'Manage WireGuard and SmartVPN client registrations'}
.data=${clients} .data=${clients}
.rowKey=${'clientId'}
.highlightUpdates=${'flash'}
.showColumnFilters=${true} .showColumnFilters=${true}
.displayFunction=${(client: interfaces.data.IVpnClient) => { .displayFunction=${(client: interfaces.data.IVpnClient) => {
const conn = this.getConnectedInfo(client); const conn = this.getConnectedInfo(client);