diff --git a/changelog.md b/changelog.md index e179d2a..733b226 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2026-03-23 - 11.10.0 - feat(monitoring) +add backend protocol metrics to network stats and ops dashboard + +- Expose backend protocol, connection, error, and suppression metrics in stats responses. +- Add typed backend info interfaces and app state support for backend metrics. +- Render a new backend protocols table in the ops network view with detail modal and suppression badges. +- Update smartproxy and lik dependencies to support backend protocol metrics collection. + ## 2026-03-21 - 11.9.1 - fix(lifecycle) clean up service subscriptions, proxy retries, and stale runtime state on shutdown diff --git a/package.json b/package.json index 28929c6..42ec6c8 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@apiclient.xyz/cloudflare": "^7.1.0", "@design.estate/dees-catalog": "^3.48.5", "@design.estate/dees-element": "^2.2.3", - "@push.rocks/lik": "^6.3.1", + "@push.rocks/lik": "^6.4.0", "@push.rocks/projectinfo": "^5.0.2", "@push.rocks/qenv": "^6.1.3", "@push.rocks/smartacme": "^9.3.0", @@ -53,7 +53,7 @@ "@push.rocks/smartnetwork": "^4.4.0", "@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpromise": "^4.2.3", - "@push.rocks/smartproxy": "^26.0.0", + "@push.rocks/smartproxy": "^26.1.0", "@push.rocks/smartradius": "^1.1.1", "@push.rocks/smartrequest": "^5.0.1", "@push.rocks/smartrx": "^3.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59f2d08..8cd5496 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,8 +30,8 @@ importers: specifier: ^2.2.3 version: 2.2.3 '@push.rocks/lik': - specifier: ^6.3.1 - version: 6.3.1 + specifier: ^6.4.0 + version: 6.4.0 '@push.rocks/projectinfo': specifier: ^5.0.2 version: 5.0.2 @@ -78,8 +78,8 @@ importers: specifier: ^4.2.3 version: 4.2.3 '@push.rocks/smartproxy': - specifier: ^26.0.0 - version: 26.0.0 + specifier: ^26.1.0 + version: 26.1.0 '@push.rocks/smartradius': specifier: ^1.1.1 version: 1.1.1 @@ -1067,8 +1067,8 @@ packages: '@push.rocks/levelcache@3.2.0': resolution: {integrity: sha512-Ch0Oguta2I0SVi704kHghhBcgfyfS92ua1elRu9d8X1/9LMRYuqvvBAnyXyFxQzI3S8q8QC6EkRdd8CAAYSzRg==} - '@push.rocks/lik@6.3.1': - resolution: {integrity: sha512-UWDwGBaVx5yPtAFXqDDBtQZCzETUOA/7myQIXb+YBsuiIw4yQuhNZ23uY2ChQH2Zn6DLqdNSgQcYC0WywMZBNQ==} + '@push.rocks/lik@6.4.0': + resolution: {integrity: sha512-GCdXyF2a6NP+i0W6Mib1PjtA6JGrl6Ae17SbaQwqTscn4JHNta6xm9r+D8/b83XGZsoU903FlJZli3YqJCxT9Q==} '@push.rocks/mongodump@1.1.0': resolution: {integrity: sha512-kW0ZUGyf1e4nwloVwBQjNId+MzgTcNS834C+RxH21i1NqyOubbpWZtJtPP+K+s35nSJRyCTy3ICfBMdDBTAm2w==} @@ -1259,8 +1259,8 @@ packages: '@push.rocks/smartpromise@4.2.3': resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==} - '@push.rocks/smartproxy@26.0.0': - resolution: {integrity: sha512-fGLSVGCMEnmRFzt1iwiOjaOv6fB94fJgmtU13c9IHrpcuoPL2BhJqY+vj0bEgh2ee1F1fos3oARHKf4dwoeS6w==} + '@push.rocks/smartproxy@26.1.0': + resolution: {integrity: sha512-DhrQopSMFRVxe8RpbNaMDDv1sFiDcjTAcIwDPilXaOZMr9+IJ+td/+GQeg03Ra5Pi9OjpoerwG8M8YHJh2j2tA==} '@push.rocks/smartpuppeteer@2.0.5': resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==} @@ -2047,9 +2047,6 @@ packages: '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} - '@types/symbol-tree@3.2.5': - resolution: {integrity: sha512-zXnnyENt1TYQcS21MkPaJCVjfcPq7p7yc5mo5JACuumXp6sly5jnlS0IokHd+xmmuCbx6V7JqkMBpswR+nZAcw==} - '@types/tar-stream@3.1.4': resolution: {integrity: sha512-921gW0+g29mCJX0fRvqeHzBlE/XclDaAG0Ousy1LCghsOhvaKacDeRGEVzQP9IPfKn8Vysy7FEXAIxycpc/CMg==} @@ -4324,7 +4321,7 @@ snapshots: dependencies: '@api.global/typedrequest-interfaces': 3.0.19 '@push.rocks/isounique': 1.0.5 - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartbuffer': 3.0.5 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartguard': 3.1.0 @@ -4340,7 +4337,7 @@ snapshots: '@cloudflare/workers-types': 4.20260317.1 '@design.estate/dees-catalog': 3.49.0(@tiptap/pm@2.27.2) '@design.estate/dees-comms': 1.0.30 - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartenv': 6.0.0 '@push.rocks/smartfeed': 1.4.0 @@ -4913,7 +4910,7 @@ snapshots: dependencies: '@api.global/typedrequest': 3.3.0 '@design.estate/dees-comms': 1.0.30 - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartjson': 6.0.0 '@push.rocks/smartmarkdown': 3.0.3 @@ -5203,7 +5200,7 @@ snapshots: '@git.zone/tsbundle': 2.9.1 '@git.zone/tsrun': 2.0.1 '@push.rocks/early': 4.0.4 - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/npmextra': 5.3.3 '@push.rocks/smartcli': 4.0.20 '@push.rocks/smartdelay': 3.0.5 @@ -5867,7 +5864,7 @@ snapshots: '@push.rocks/levelcache@3.2.0': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartbucket': 3.3.10 '@push.rocks/smartcache': 1.0.18 '@push.rocks/smartenv': 5.0.13 @@ -5887,20 +5884,18 @@ snapshots: - supports-color - vue - '@push.rocks/lik@6.3.1': + '@push.rocks/lik@6.4.0': dependencies: '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartmatch': 2.0.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrx': 3.0.10 '@push.rocks/smarttime': 4.2.3 - '@types/minimatch': 5.1.2 - '@types/symbol-tree': 3.2.5 symbol-tree: 3.2.4 '@push.rocks/mongodump@1.1.0(socks@2.8.7)': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartfile': 11.2.7 '@push.rocks/smartjson': 5.2.0 '@push.rocks/smartpath': 6.0.0 @@ -5952,7 +5947,7 @@ snapshots: dependencies: '@apiclient.xyz/cloudflare': 7.1.0 '@peculiar/x509': 1.14.3 - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdata': 7.1.0(socks@2.8.7) '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdns': 7.9.0 @@ -6048,7 +6043,7 @@ snapshots: '@push.rocks/smartcli@4.0.20': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartobject': 1.0.12 '@push.rocks/smartpromise': 4.2.3 @@ -6072,7 +6067,7 @@ snapshots: '@push.rocks/smartdata@5.16.7(socks@2.8.7)': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartmongo': 2.2.0(socks@2.8.7) @@ -6102,7 +6097,7 @@ snapshots: '@push.rocks/smartdata@7.1.0(socks@2.8.7)': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartmongo': 2.2.0(socks@2.8.7) @@ -6161,14 +6156,14 @@ snapshots: '@push.rocks/smartexit@1.1.1': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartpromise': 4.2.3 tree-kill: 1.2.2 '@push.rocks/smartexit@2.0.3': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartexpect@2.5.0': @@ -6186,7 +6181,7 @@ snapshots: '@push.rocks/smartfile@10.0.41': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartfile-interfaces': 1.0.7 '@push.rocks/smarthash': 3.2.6 @@ -6205,7 +6200,7 @@ snapshots: '@push.rocks/smartfile@11.2.7': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartfile-interfaces': 1.0.7 '@push.rocks/smarthash': 3.2.6 @@ -6223,7 +6218,7 @@ snapshots: '@push.rocks/smartfile@13.1.2': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartfile-interfaces': 1.0.7 '@push.rocks/smartfs': 1.5.0 @@ -6260,7 +6255,7 @@ snapshots: '@push.rocks/smartinteract@2.0.16': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartobject': 1.0.12 '@push.rocks/smartpromise': 4.2.3 inquirer: 11.1.0 @@ -6545,7 +6540,7 @@ snapshots: '@push.rocks/smartpromise@4.2.3': {} - '@push.rocks/smartproxy@26.0.0': + '@push.rocks/smartproxy@26.1.0': dependencies: '@push.rocks/smartcrypto': 2.0.4 '@push.rocks/smartlog': 3.2.1 @@ -6600,7 +6595,7 @@ snapshots: '@push.rocks/smartrouter@1.3.3': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartrx': 3.0.10 path-to-regexp: 8.3.0 @@ -6617,7 +6612,7 @@ snapshots: dependencies: '@api.global/typedrequest': 3.3.0 '@cfworker/json-schema': 4.1.1 - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartenv': 6.0.0 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartpath': 6.0.0 @@ -6677,7 +6672,7 @@ snapshots: '@push.rocks/smartstream@3.4.0': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartenv': 6.0.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrx': 3.0.10 @@ -6688,7 +6683,7 @@ snapshots: '@push.rocks/smarttime@4.2.3': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartpromise': 4.2.3 croner: 10.0.1 @@ -6712,7 +6707,7 @@ snapshots: '@push.rocks/smartwatch@6.3.0': dependencies: - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartenv': 6.0.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrx': 3.0.10 @@ -6735,7 +6730,7 @@ snapshots: '@push.rocks/taskbuffer@3.5.0': dependencies: '@design.estate/dees-element': 2.2.3 - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartpromise': 4.2.3 @@ -6751,7 +6746,7 @@ snapshots: '@push.rocks/taskbuffer@6.1.2': dependencies: '@design.estate/dees-element': 2.2.3 - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartpromise': 4.2.3 @@ -6767,7 +6762,7 @@ snapshots: '@push.rocks/taskbuffer@8.0.0': dependencies: '@design.estate/dees-element': 2.2.3 - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartpromise': 4.2.3 @@ -6797,7 +6792,7 @@ snapshots: '@push.rocks/webstore@2.0.20': dependencies: '@api.global/typedrequest-interfaces': 3.0.19 - '@push.rocks/lik': 6.3.1 + '@push.rocks/lik': 6.4.0 '@push.rocks/smartenv': 5.0.13 '@push.rocks/smartjson': 5.2.0 '@push.rocks/smartpromise': 4.2.3 @@ -7610,8 +7605,6 @@ snapshots: '@types/semver@7.7.1': {} - '@types/symbol-tree@3.2.5': {} - '@types/tar-stream@3.1.4': dependencies: '@types/node': 25.5.0 diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 35bdd27..d8f61e5 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: '11.9.1', + version: '11.10.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts/monitoring/classes.metricsmanager.ts b/ts/monitoring/classes.metricsmanager.ts index 3bdaa3d..fa029b5 100644 --- a/ts/monitoring/classes.metricsmanager.ts +++ b/ts/monitoring/classes.metricsmanager.ts @@ -558,6 +558,7 @@ export class MetricsManager { throughputByIP: new Map(), requestsPerSecond: 0, requestsTotal: 0, + backends: [] as Array, }; } @@ -590,6 +591,73 @@ export class MetricsManager { const requestsPerSecond = proxyMetrics.requests.perSecond(); const requestsTotal = proxyMetrics.requests.total(); + // Collect backend protocol data + const backendMetrics = proxyMetrics.backends.byBackend(); + const protocolCache = proxyMetrics.backends.detectedProtocols(); + + // Index protocol cache by "host:port" + const cacheByKey = new Map(); + for (const entry of protocolCache) { + cacheByKey.set(`${entry.host}:${entry.port}`, entry); + } + + const backends: Array = []; + const seen = new Set(); + + for (const [key, bm] of backendMetrics) { + seen.add(key); + const cache = cacheByKey.get(key); + backends.push({ + backend: key, + domain: cache?.domain ?? null, + protocol: bm.protocol, + activeConnections: bm.activeConnections, + totalConnections: bm.totalConnections, + connectErrors: bm.connectErrors, + handshakeErrors: bm.handshakeErrors, + requestErrors: bm.requestErrors, + avgConnectTimeMs: Math.round(bm.avgConnectTimeMs * 10) / 10, + poolHitRate: Math.round(bm.poolHitRate * 1000) / 1000, + h2Failures: bm.h2Failures, + h2Suppressed: cache?.h2Suppressed ?? false, + h3Suppressed: cache?.h3Suppressed ?? false, + h2CooldownRemainingSecs: cache?.h2CooldownRemainingSecs ?? null, + h3CooldownRemainingSecs: cache?.h3CooldownRemainingSecs ?? null, + h2ConsecutiveFailures: cache?.h2ConsecutiveFailures ?? null, + h3ConsecutiveFailures: cache?.h3ConsecutiveFailures ?? null, + h3Port: cache?.h3Port ?? null, + cacheAgeSecs: cache?.ageSecs ?? null, + }); + } + + // Include protocol cache entries with no matching backend metric + for (const entry of protocolCache) { + const key = `${entry.host}:${entry.port}`; + if (!seen.has(key)) { + backends.push({ + backend: key, + domain: entry.domain, + protocol: entry.protocol, + activeConnections: 0, + totalConnections: 0, + connectErrors: 0, + handshakeErrors: 0, + requestErrors: 0, + avgConnectTimeMs: 0, + poolHitRate: 0, + h2Failures: 0, + h2Suppressed: entry.h2Suppressed, + h3Suppressed: entry.h3Suppressed, + h2CooldownRemainingSecs: entry.h2CooldownRemainingSecs, + h3CooldownRemainingSecs: entry.h3CooldownRemainingSecs, + h2ConsecutiveFailures: entry.h2ConsecutiveFailures, + h3ConsecutiveFailures: entry.h3ConsecutiveFailures, + h3Port: entry.h3Port, + cacheAgeSecs: entry.ageSecs, + }); + } + } + return { connectionsByIP, throughputRate, @@ -599,6 +667,7 @@ export class MetricsManager { throughputByIP, requestsPerSecond, requestsTotal, + backends, }; }, 1000); // 1s cache — matches typical dashboard poll interval } diff --git a/ts/opsserver/handlers/security.handler.ts b/ts/opsserver/handlers/security.handler.ts index dbb0972..aaa8cdb 100644 --- a/ts/opsserver/handlers/security.handler.ts +++ b/ts/opsserver/handlers/security.handler.ts @@ -101,6 +101,7 @@ export class SecurityHandler { throughputByIP, requestsPerSecond: networkStats.requestsPerSecond || 0, requestsTotal: networkStats.requestsTotal || 0, + backends: networkStats.backends || [], }; } @@ -114,6 +115,7 @@ export class SecurityHandler { throughputByIP: [], requestsPerSecond: 0, requestsTotal: 0, + backends: [], }; } ) diff --git a/ts/opsserver/handlers/stats.handler.ts b/ts/opsserver/handlers/stats.handler.ts index d6a2671..4ca7f46 100644 --- a/ts/opsserver/handlers/stats.handler.ts +++ b/ts/opsserver/handlers/stats.handler.ts @@ -309,6 +309,7 @@ export class StatsHandler { throughputHistory: stats.throughputHistory || [], requestsPerSecond: stats.requestsPerSecond || 0, requestsTotal: stats.requestsTotal || 0, + backends: stats.backends || [], }; })() ); diff --git a/ts_interfaces/data/stats.ts b/ts_interfaces/data/stats.ts index 5e1cfd2..35b4e2d 100644 --- a/ts_interfaces/data/stats.ts +++ b/ts_interfaces/data/stats.ts @@ -165,6 +165,7 @@ export interface INetworkMetrics { throughputHistory?: Array<{ timestamp: number; in: number; out: number }>; requestsPerSecond?: number; requestsTotal?: number; + backends?: IBackendInfo[]; } export interface IConnectionDetails { @@ -174,4 +175,26 @@ export interface IConnectionDetails { startTime: number; bytesIn: number; bytesOut: number; +} + +export interface IBackendInfo { + backend: string; + domain: string | null; + protocol: string; + activeConnections: number; + totalConnections: number; + connectErrors: number; + handshakeErrors: number; + requestErrors: number; + avgConnectTimeMs: number; + poolHitRate: number; + h2Failures: number; + h2Suppressed: boolean; + h3Suppressed: boolean; + h2CooldownRemainingSecs: number | null; + h3CooldownRemainingSecs: number | null; + h2ConsecutiveFailures: number | null; + h3ConsecutiveFailures: number | null; + h3Port: number | null; + cacheAgeSecs: number | null; } \ No newline at end of file diff --git a/ts_interfaces/requests/stats.ts b/ts_interfaces/requests/stats.ts index 5224b57..6af6874 100644 --- a/ts_interfaces/requests/stats.ts +++ b/ts_interfaces/requests/stats.ts @@ -179,5 +179,6 @@ export interface IReq_GetNetworkStats extends plugins.typedrequestInterfaces.imp throughputByIP: Array<{ ip: string; in: number; out: number }>; requestsPerSecond: number; requestsTotal: number; + backends?: statsInterfaces.IBackendInfo[]; }; } \ No newline at end of file diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 35bdd27..d8f61e5 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: '11.9.1', + version: '11.10.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts_web/appstate.ts b/ts_web/appstate.ts index 658b7f5..fd283b6 100644 --- a/ts_web/appstate.ts +++ b/ts_web/appstate.ts @@ -53,6 +53,7 @@ export interface INetworkState { throughputHistory: Array<{ timestamp: number; in: number; out: number }>; requestsPerSecond: number; requestsTotal: number; + backends: interfaces.data.IBackendInfo[]; lastUpdated: number; isLoading: boolean; error: string | null; @@ -148,6 +149,7 @@ export const networkStatePart = await appState.getStatePart( throughputHistory: [], requestsPerSecond: 0, requestsTotal: 0, + backends: [], lastUpdated: 0, isLoading: false, error: null, @@ -503,6 +505,7 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat throughputHistory: networkStatsResponse.throughputHistory || [], requestsPerSecond: networkStatsResponse.requestsPerSecond || 0, requestsTotal: networkStatsResponse.requestsTotal || 0, + backends: networkStatsResponse.backends || [], lastUpdated: Date.now(), isLoading: false, error: null, diff --git a/ts_web/elements/ops-view-network.ts b/ts_web/elements/ops-view-network.ts index 18ffaca..9f071ed 100644 --- a/ts_web/elements/ops-view-network.ts +++ b/ts_web/elements/ops-view-network.ts @@ -1,5 +1,6 @@ import { DeesElement, property, html, customElement, type TemplateResult, css, state, cssManager } from '@design.estate/dees-element'; import * as appstate from '../appstate.js'; +import * as interfaces from '../../dist_ts_interfaces/index.js'; import { viewHostCss } from './shared/css.js'; import { type IStatsTile } from '@design.estate/dees-catalog'; @@ -198,6 +199,38 @@ export class OpsViewNetwork extends DeesElement { color: ${cssManager.bdTheme('#00796b', '#4db6ac')}; } + .protocolBadge.h1 { + background: ${cssManager.bdTheme('#e3f2fd', '#1a2c3a')}; + color: ${cssManager.bdTheme('#1976d2', '#5a9fd4')}; + } + + .protocolBadge.h2 { + background: ${cssManager.bdTheme('#e8f5e9', '#1a3a1a')}; + color: ${cssManager.bdTheme('#388e3c', '#66bb6a')}; + } + + .protocolBadge.h3 { + background: ${cssManager.bdTheme('#f3e5f5', '#2a1a3a')}; + color: ${cssManager.bdTheme('#7b1fa2', '#ba68c8')}; + } + + .protocolBadge.unknown { + background: ${cssManager.bdTheme('#f5f5f5', '#2a2a2a')}; + color: ${cssManager.bdTheme('#757575', '#999999')}; + } + + .suppressionBadge { + display: inline-flex; + align-items: center; + padding: 2px 6px; + border-radius: 3px; + font-size: 11px; + font-weight: 500; + background: ${cssManager.bdTheme('#fff3e0', '#3a2a1a')}; + color: ${cssManager.bdTheme('#f57c00', '#ff9933')}; + margin-left: 4px; + } + .statusBadge { display: inline-flex; align-items: center; @@ -265,6 +298,9 @@ export class OpsViewNetwork extends DeesElement { ${this.renderTopIPs()} + + ${this.renderBackendProtocols()} + { + const totalErrors = item.connectErrors + item.handshakeErrors + item.requestErrors; + const protocolClass = item.protocol.toLowerCase().replace(/[^a-z0-9]/g, ''); + + return { + 'Backend': item.backend, + 'Domain': item.domain || '-', + 'Protocol': html` + ${item.protocol.toUpperCase()} + ${item.h2Suppressed ? html`H2 suppressed` : ''} + ${item.h3Suppressed ? html`H3 suppressed` : ''} + `, + 'Active': item.activeConnections, + 'Total': this.formatNumber(item.totalConnections), + 'Avg Connect': item.avgConnectTimeMs > 0 ? `${item.avgConnectTimeMs.toFixed(1)}ms` : '-', + 'Pool Hit Rate': item.poolHitRate > 0 ? `${(item.poolHitRate * 100).toFixed(1)}%` : '-', + 'Errors': totalErrors > 0 + ? html`${totalErrors}` + : html`0`, + 'Cache Age': item.cacheAgeSecs != null ? `${Math.round(item.cacheAgeSecs)}s` : '-', + }; + }} + .dataActions=${[ + { + name: 'View Details', + iconName: 'lucide:info', + type: ['inRow', 'doubleClick', 'contextmenu'] as any, + actionFunc: async (actionData: any) => { + await this.showBackendDetails(actionData.item); + } + } + ]} + heading1="Backend Protocols" + heading2="Auto-detected backend protocols and connection pool health" + searchable + .pagination=${false} + dataName="backend" + > + `; + } + + private async showBackendDetails(backend: interfaces.data.IBackendInfo) { + const { DeesModal } = await import('@design.estate/dees-catalog'); + + await DeesModal.createAndShow({ + heading: `Backend: ${backend.backend}`, + content: html` +
+ +
+ `, + menuOptions: [ + { + name: 'Copy Backend Key', + iconName: 'lucide:Copy', + action: async () => { + await navigator.clipboard.writeText(backend.backend); + } + } + ] + }); + } + private async updateNetworkData() { // Track requests/sec history for the trend sparkline (moved out of render) const reqPerSec = this.networkState.requestsPerSecond || 0;