From 36a2ebc94eb77541517ec3d7afdaf4aa2deff98d Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Wed, 8 Apr 2026 15:26:12 +0000 Subject: [PATCH] fix(network-ui): enable flashing table updates for network activity, remote ingress, and VPN views --- changelog.md | 7 +++ ts/00_commitinfo_data.ts | 2 +- ts_web/00_commitinfo_data.ts | 2 +- .../network/ops-view-network-activity.ts | 55 ++++++++----------- .../network/ops-view-remoteingress.ts | 2 + ts_web/elements/network/ops-view-vpn.ts | 2 + 6 files changed, 37 insertions(+), 33 deletions(-) diff --git a/changelog.md b/changelog.md index 19bb2ce..3aedccd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # 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) add built-in dcrouter DNS provider support and rename manual domains to dcrouter-hosted/local diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 1c60ccc..0fc8afc 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '13.9.0', + version: '13.9.1', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 1c60ccc..0fc8afc 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '13.9.0', + version: '13.9.1', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts_web/elements/network/ops-view-network-activity.ts b/ts_web/elements/network/ops-view-network-activity.ts index a1fd61b..657dfbe 100644 --- a/ts_web/elements/network/ops-view-network-activity.ts +++ b/ts_web/elements/network/ops-view-network-activity.ts @@ -323,6 +323,8 @@ export class OpsViewNetworkActivity extends DeesElement { ({ Time: new Date(req.timestamp).toLocaleTimeString(), Protocol: html`${req.protocol.toUpperCase()}`, @@ -595,6 +597,8 @@ export class OpsViewNetworkActivity extends DeesElement { return html` { const bw = bandwidthByIP.get(ipData.ip); return { @@ -624,6 +628,8 @@ export class OpsViewNetworkActivity extends DeesElement { return html` { const totalErrors = item.connectErrors + item.handshakeErrors + item.requestErrors; const protocolClass = item.protocol.toLowerCase().replace(/[^a-z0-9]/g, ''); @@ -724,37 +730,24 @@ export class OpsViewNetworkActivity extends DeesElement { this.requestsPerSecHistory.shift(); } - // Only update if connections changed significantly - const newConnectionCount = this.networkState.connections.length; - const oldConnectionCount = this.networkRequests.length; - - // Check if we need to update the network requests array - const shouldUpdate = newConnectionCount !== oldConnectionCount || - newConnectionCount === 0 || - (newConnectionCount > 0 && this.networkRequests.length === 0); - - if (shouldUpdate) { - // Convert connection data to network requests format - if (newConnectionCount > 0) { - this.networkRequests = this.networkState.connections.map((conn, index) => ({ - id: conn.id, - timestamp: conn.startTime, - method: 'GET', // Default method for proxy connections - url: '/', - 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 = []; - } - } + // Reassign unconditionally so dees-table's flash diff can compare per-cell + // values against the previous snapshot. Row identity is preserved via + // rowKey='id', so DOM nodes are reused across ticks. + this.networkRequests = this.networkState.connections.map((conn) => ({ + id: conn.id, + timestamp: conn.startTime, + method: 'GET', // Default method for proxy connections + url: '/', + 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', + })); // Load server-side throughput history into chart (once) if (!this.historyLoaded && this.networkState.throughputHistory && this.networkState.throughputHistory.length > 0) { diff --git a/ts_web/elements/network/ops-view-remoteingress.ts b/ts_web/elements/network/ops-view-remoteingress.ts index 3c51eaf..6a0e720 100644 --- a/ts_web/elements/network/ops-view-remoteingress.ts +++ b/ts_web/elements/network/ops-view-remoteingress.ts @@ -220,6 +220,8 @@ export class OpsViewRemoteIngress extends DeesElement { .heading1=${'Edge Nodes'} .heading2=${'Manage remote ingress edge registrations'} .data=${this.riState.edges} + .rowKey=${'id'} + .highlightUpdates=${'flash'} .showColumnFilters=${true} .displayFunction=${(edge: interfaces.data.IRemoteIngress) => ({ name: edge.name, diff --git a/ts_web/elements/network/ops-view-vpn.ts b/ts_web/elements/network/ops-view-vpn.ts index 4b905d2..ee16a35 100644 --- a/ts_web/elements/network/ops-view-vpn.ts +++ b/ts_web/elements/network/ops-view-vpn.ts @@ -305,6 +305,8 @@ export class OpsViewVpn extends DeesElement { .heading1=${'VPN Clients'} .heading2=${'Manage WireGuard and SmartVPN client registrations'} .data=${clients} + .rowKey=${'clientId'} + .highlightUpdates=${'flash'} .showColumnFilters=${true} .displayFunction=${(client: interfaces.data.IVpnClient) => { const conn = this.getConnectedInfo(client);