signalk-edge-link 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/delta-sanitizer.js +86 -0
- package/lib/instance.js +284 -8
- package/lib/metadata.js +467 -0
- package/lib/metrics.js +22 -1
- package/lib/packet.js +51 -14
- package/lib/pathDictionary.js +20 -1
- package/lib/pipeline-v2-client.js +177 -12
- package/lib/pipeline-v2-server.js +236 -2
- package/lib/pipeline.js +221 -1
- package/lib/prometheus.js +11 -0
- package/lib/routes/config-validation.js +49 -0
- package/lib/routes/metrics.js +1 -0
- package/lib/routes.js +25 -4
- package/package.json +1 -1
- package/public/{982.b207a377ed6542e2fb4a.js → 982.63949a2b2f6c5854e034.js} +2 -2
- package/public/982.63949a2b2f6c5854e034.js.map +1 -0
- package/public/index.html +1 -1
- package/public/main.0b6f5e3267731da945f0.js +2 -0
- package/public/main.0b6f5e3267731da945f0.js.map +1 -0
- package/public/main.js +467 -0
- package/public/remoteEntry.js +1 -1
- package/public/remoteEntry.js.map +1 -1
- package/public/982.b207a377ed6542e2fb4a.js.map +0 -1
- package/public/main.f1780db6593b0c07a48c.js +0 -2
- package/public/main.f1780db6593b0c07a48c.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"main.f1780db6593b0c07a48c.js","mappings":"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","sources":["webpack://signalk-edge-link/webpack/runtime/load script","webpack://signalk-edge-link/./src/webapp/utils/apiFetch.ts","webpack://signalk-edge-link/./src/webapp/index.ts","webpack://signalk-edge-link/webpack/bootstrap","webpack://signalk-edge-link/webpack/runtime/ensure chunk","webpack://signalk-edge-link/webpack/runtime/get javascript chunk filename","webpack://signalk-edge-link/webpack/runtime/get mini-css chunk filename","webpack://signalk-edge-link/webpack/runtime/global","webpack://signalk-edge-link/webpack/runtime/hasOwnProperty shorthand","webpack://signalk-edge-link/webpack/runtime/sharing","webpack://signalk-edge-link/webpack/runtime/publicPath","webpack://signalk-edge-link/webpack/runtime/jsonp chunk loading","webpack://signalk-edge-link/webpack/startup"],"sourcesContent":["var inProgress = {};\nvar dataWebpackPrefix = \"signalk-edge-link:\";\n// loadScript function to load a script via script tag\n__webpack_require__.l = (url, done, key, chunkId) => {\n\tif(inProgress[url]) { inProgress[url].push(done); return; }\n\tvar script, needAttach;\n\tif(key !== undefined) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tfor(var i = 0; i < scripts.length; i++) {\n\t\t\tvar s = scripts[i];\n\t\t\tif(s.getAttribute(\"src\") == url || s.getAttribute(\"data-webpack\") == dataWebpackPrefix + key) { script = s; break; }\n\t\t}\n\t}\n\tif(!script) {\n\t\tneedAttach = true;\n\t\tscript = document.createElement('script');\n\n\t\tscript.charset = 'utf-8';\n\t\tif (__webpack_require__.nc) {\n\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n\t\t}\n\t\tscript.setAttribute(\"data-webpack\", dataWebpackPrefix + key);\n\n\t\tscript.src = url;\n\t}\n\tinProgress[url] = [done];\n\tvar onScriptComplete = (prev, event) => {\n\t\t// avoid mem leaks in IE.\n\t\tscript.onerror = script.onload = null;\n\t\tclearTimeout(timeout);\n\t\tvar doneFns = inProgress[url];\n\t\tdelete inProgress[url];\n\t\tscript.parentNode && script.parentNode.removeChild(script);\n\t\tdoneFns && doneFns.forEach((fn) => (fn(event)));\n\t\tif(prev) return prev(event);\n\t}\n\tvar timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);\n\tscript.onerror = onScriptComplete.bind(null, script.onerror);\n\tscript.onload = onScriptComplete.bind(null, script.onload);\n\tneedAttach && document.head.appendChild(script);\n};","/// <reference lib=\"dom\" />\r\n\r\nexport const MANAGEMENT_TOKEN_ERROR_MESSAGE = \"Management token required/invalid.\";\r\n\r\ninterface AuthConfig {\r\n token: string | null;\r\n localStorageKey: string;\r\n queryParam: string;\r\n includeTokenInQuery: boolean;\r\n headerMode: string;\r\n}\r\n\r\ndeclare global {\r\n interface Window {\r\n __EDGE_LINK_AUTH__?: Partial<AuthConfig>;\r\n }\r\n}\r\n\r\nconst DEFAULT_AUTH_CONFIG: AuthConfig = {\r\n token: null,\r\n localStorageKey: \"signalkEdgeLinkManagementToken\",\r\n queryParam: \"edgeLinkToken\",\r\n // Default to false: query-parameter tokens leak into browser history, server\r\n // access logs, and Referer headers. Set includeTokenInQuery: true in\r\n // window.__EDGE_LINK_AUTH__ only when you explicitly need URL-based auth.\r\n includeTokenInQuery: false,\r\n headerMode: \"both\"\r\n};\r\n\r\nfunction readRuntimeAuthConfig(): AuthConfig {\r\n if (typeof window === \"undefined\") {\r\n return DEFAULT_AUTH_CONFIG;\r\n }\r\n\r\n const runtime = window.__EDGE_LINK_AUTH__;\r\n if (!runtime || typeof runtime !== \"object\") {\r\n return DEFAULT_AUTH_CONFIG;\r\n }\r\n\r\n return { ...DEFAULT_AUTH_CONFIG, ...runtime };\r\n}\r\n\r\nfunction resolveToken(config: AuthConfig): string {\r\n if (config.token) {\r\n return String(config.token).trim();\r\n }\r\n\r\n if (typeof window === \"undefined\") {\r\n return \"\";\r\n }\r\n\r\n // SECURITY NOTE: Query parameter tokens can leak into browser history, server\r\n // access logs, and Referer headers. Prefer localStorage or\r\n // window.__EDGE_LINK_AUTH__.token for production deployments. Set\r\n // includeTokenInQuery: false in __EDGE_LINK_AUTH__ to disable this path.\r\n if (config.includeTokenInQuery && config.queryParam) {\r\n const tokenFromQuery = new URLSearchParams(window.location.search).get(config.queryParam);\r\n if (tokenFromQuery) {\r\n return tokenFromQuery.trim();\r\n }\r\n }\r\n\r\n if (config.localStorageKey && window.localStorage) {\r\n const tokenFromStorage = window.localStorage.getItem(config.localStorageKey);\r\n if (tokenFromStorage) {\r\n return tokenFromStorage.trim();\r\n }\r\n }\r\n\r\n return \"\";\r\n}\r\n\r\nfunction attachAuthHeaders(headers: Headers, token: string, headerMode: string): Headers {\r\n if (!token) {\r\n return headers;\r\n }\r\n\r\n const normalizedMode = (headerMode || \"both\").toLowerCase();\r\n if (\r\n normalizedMode === \"x-edge-link-token\" ||\r\n normalizedMode === \"token\" ||\r\n normalizedMode === \"both\"\r\n ) {\r\n headers.set(\"X-Edge-Link-Token\", token);\r\n }\r\n if (\r\n normalizedMode === \"authorization\" ||\r\n normalizedMode === \"bearer\" ||\r\n normalizedMode === \"both\"\r\n ) {\r\n headers.set(\"Authorization\", `Bearer ${token}`);\r\n }\r\n return headers;\r\n}\r\n\r\nexport function getAuthToken(): string {\r\n const config = readRuntimeAuthConfig();\r\n return resolveToken(config);\r\n}\r\n\r\nexport function getTokenHelpText(): string {\r\n const config = readRuntimeAuthConfig();\r\n const modeText =\r\n config.headerMode && String(config.headerMode).toLowerCase() === \"authorization\"\r\n ? \"Authorization: Bearer <token>\"\r\n : config.headerMode && String(config.headerMode).toLowerCase() === \"x-edge-link-token\"\r\n ? \"X-Edge-Link-Token\"\r\n : \"X-Edge-Link-Token and Authorization: Bearer <token>\";\r\n\r\n return `The server-side token is configured in plugin settings (managementApiToken) or via the SIGNALK_EDGE_LINK_MANAGEMENT_TOKEN environment variable. To authenticate from the browser, provide the token using window.__EDGE_LINK_AUTH__.token, query parameter \"${config.queryParam}\", or localStorage key \"${config.localStorageKey}\". Requests send ${modeText} when a token is available.`;\r\n}\r\n\r\nexport function apiFetch(input: string | Request, init: RequestInit = {}): Promise<Response> {\r\n const config = readRuntimeAuthConfig();\r\n const token = resolveToken(config);\r\n const headers = new Headers(init.headers || {});\r\n attachAuthHeaders(headers, token, config.headerMode);\r\n\r\n return fetch(input, {\r\n ...init,\r\n headers\r\n });\r\n}\r\n","import \"./styles.css\";\r\nimport { apiFetch, getTokenHelpText, MANAGEMENT_TOKEN_ERROR_MESSAGE } from \"./utils/apiFetch\";\r\n\r\n// Constants\r\nconst API_BASE_PATH = \"/plugins/signalk-edge-link\";\r\nconst DELTA_TIMER_MIN = 100;\r\nconst DELTA_TIMER_MAX = 10000;\r\nconst NOTIFICATION_TIMEOUT = 4000;\r\nconst METRICS_REFRESH_INTERVAL = 15000;\r\nconst JSON_SYNC_DEBOUNCE = 300;\r\n\r\n// ── Types ─────────────────────────────────────────────────────────────────────\r\n\r\ninterface ConnectionInfo {\r\n id: string;\r\n name?: string;\r\n type: string;\r\n healthy?: boolean;\r\n readyToSend?: boolean;\r\n}\r\n\r\ninterface AuthenticatedError extends Error {\r\n isUnauthorized?: boolean;\r\n}\r\n\r\n// Escape HTML special characters to prevent XSS when inserting dynamic values into innerHTML\r\nfunction escapeHtml(str: string | number): string {\r\n return String(str)\r\n .replace(/&/g, \"&\")\r\n .replace(/</g, \"<\")\r\n .replace(/>/g, \">\")\r\n .replace(/\"/g, \""\")\r\n .replace(/'/g, \"'\");\r\n}\r\n\r\n// HTML Template Helpers\r\nconst renderCard = (\r\n title: string,\r\n subtitle: string | null,\r\n contentId: string,\r\n contentClass = \"\"\r\n) => `\r\n <div class=\"config-section\">\r\n <div class=\"card\">\r\n <div class=\"card-header\">\r\n <h2>${escapeHtml(title)}</h2>\r\n ${subtitle ? `<p class=\"subtitle\">${escapeHtml(subtitle)}</p>` : \"\"}\r\n </div>\r\n <div class=\"card-content\">\r\n <div id=\"${contentId}\" class=\"${contentClass || contentId + \"-info\"}\">\r\n <p>Loading ${escapeHtml(title.toLowerCase())}...</p>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n`;\r\n\r\nconst renderStatItem = (label: string, value: string | number, hasError = false) => `\r\n <div class=\"stat-item${hasError ? \" error\" : \"\"}\">\r\n <span class=\"stat-label\">${escapeHtml(label)}:</span>\r\n <span class=\"stat-value\">${escapeHtml(value)}</span>\r\n </div>\r\n`;\r\n\r\nconst renderMetricItem = (label: string, value: string | number, statusClass = \"\") => `\r\n <div class=\"metric-item${statusClass ? \" \" + statusClass : \"\"}\">\r\n <div class=\"metric-label\">${escapeHtml(label)}</div>\r\n <div class=\"metric-value\">${escapeHtml(value)}</div>\r\n </div>\r\n`;\r\n\r\n// Variants that accept a pre-sanitized HTML string as the value (label is still escaped)\r\nconst renderMetricItemHtml = (label: string, htmlValue: string, statusClass = \"\") => `\r\n <div class=\"metric-item${statusClass ? \" \" + statusClass : \"\"}\">\r\n <div class=\"metric-label\">${escapeHtml(label)}</div>\r\n <div class=\"metric-value\">${htmlValue}</div>\r\n </div>\r\n`;\r\n\r\nconst renderStatItemHtml = (label: string, htmlValue: string, hasError = false) => `\r\n <div class=\"stat-item${hasError ? \" error\" : \"\"}\">\r\n <span class=\"stat-label\">${escapeHtml(label)}:</span>\r\n <span class=\"stat-value\">${htmlValue}</span>\r\n </div>\r\n`;\r\n\r\nconst renderBwStat = (\r\n label: string,\r\n value: string | number,\r\n isHighlight = false,\r\n isSuccess = false\r\n) => `\r\n <div class=\"bw-stat${isHighlight ? \" highlight\" : \"\"}\">\r\n <span class=\"bw-label\">${escapeHtml(label)}:</span>\r\n <span class=\"bw-value${isSuccess ? \" success-text\" : \"\"}\">${escapeHtml(value)}</span>\r\n </div>\r\n`;\r\n\r\nconst renderSectionGroup = (\r\n title: string,\r\n description: string | null,\r\n content: string,\r\n id = \"\"\r\n) => `\r\n <section class=\"page-group\"${id ? ` id=\"${id}\"` : \"\"}>\r\n <div class=\"page-group-header\">\r\n <h2>${escapeHtml(title)}</h2>\r\n ${description ? `<p>${escapeHtml(description)}</p>` : \"\"}\r\n </div>\r\n <div class=\"page-group-content\">\r\n ${content}\r\n </div>\r\n </section>\r\n`;\r\n\r\nclass DataConnectorConfig {\r\n connections: ConnectionInfo[];\r\n activeConnectionId: string | null;\r\n pluginConfig: Record<string, unknown> | null;\r\n pluginSchema: Record<string, unknown> | null;\r\n schemaCurrentMode: string | null;\r\n metricsInterval: ReturnType<typeof setInterval> | null;\r\n syncTimeout: ReturnType<typeof setTimeout> | null;\r\n tokenHelpText: string;\r\n deltaTimerConfig: Record<string, unknown> | null;\r\n subscriptionConfig: Record<string, unknown> | null;\r\n sentenceFilterConfig: Record<string, unknown> | null;\r\n protocolVersion: number;\r\n isServerMode: boolean;\r\n _refreshInFlight: boolean;\r\n _notificationTimer: ReturnType<typeof setTimeout> | null;\r\n\r\n constructor() {\r\n this.connections = [];\r\n this.activeConnectionId = null;\r\n this.pluginConfig = null;\r\n this.pluginSchema = null;\r\n this.schemaCurrentMode = null;\r\n this.metricsInterval = null;\r\n this.syncTimeout = null;\r\n this.tokenHelpText = getTokenHelpText();\r\n\r\n // Per-connection state (loaded for the active tab)\r\n this.deltaTimerConfig = null;\r\n this.subscriptionConfig = null;\r\n this.sentenceFilterConfig = null;\r\n this.protocolVersion = 1;\r\n this.isServerMode = false;\r\n this._refreshInFlight = false;\r\n this._notificationTimer = null;\r\n\r\n this.init();\r\n }\r\n\r\n async init() {\r\n try {\r\n await this.loadPluginConfiguration(false);\r\n await this.fetchConnections();\r\n this.renderPage();\r\n this.startMetricsRefresh();\r\n } catch (error: unknown) {\r\n console.error(\"Initialization error:\", error);\r\n this.showNotification(\r\n \"Failed to initialize application: \" +\r\n (error instanceof Error ? error.message : String(error)),\r\n \"error\"\r\n );\r\n }\r\n }\r\n\r\n // ── Connections list ───────────────────────────────────────────────────────\r\n\r\n async fetchConnections() {\r\n try {\r\n const res = await this.request(`${API_BASE_PATH}/connections`);\r\n if (res.ok) {\r\n this.connections = await res.json();\r\n }\r\n } catch (_e) {\r\n // /connections not available – fall back to legacy single-instance detection\r\n }\r\n\r\n if (!this.connections || this.connections.length === 0) {\r\n // Fallback: detect mode from plugin config and treat as single connection\r\n const type = this.detectModeFromConfig();\r\n this.connections = [{ id: \"_legacy\", name: \"Default\", type }];\r\n }\r\n\r\n if (!this.activeConnectionId) {\r\n this.activeConnectionId = this.connections[0].id;\r\n }\r\n }\r\n\r\n detectModeFromConfig(): string {\r\n if (this.pluginConfig) {\r\n const st = this.normalizeServerType(this.pluginConfig.serverType);\r\n if (st) {\r\n return st;\r\n }\r\n }\r\n if (this.schemaCurrentMode) {\r\n return this.schemaCurrentMode;\r\n }\r\n return \"client\";\r\n }\r\n\r\n getActiveConnection(): ConnectionInfo {\r\n return this.connections.find((c) => c.id === this.activeConnectionId) || this.connections[0];\r\n }\r\n\r\n isLegacyMode(): boolean {\r\n return this.connections.length === 1 && this.connections[0].id === \"_legacy\";\r\n }\r\n\r\n // ── API path helpers ───────────────────────────────────────────────────────\r\n\r\n metricsPath(connId: string): string {\r\n if (connId === \"_legacy\") {\r\n return `${API_BASE_PATH}/metrics`;\r\n }\r\n return `${API_BASE_PATH}/connections/${encodeURIComponent(connId)}/metrics`;\r\n }\r\n\r\n configPath(connId: string, filename: string): string {\r\n if (connId === \"_legacy\") {\r\n return `${API_BASE_PATH}/config/${filename}`;\r\n }\r\n return `${API_BASE_PATH}/connections/${encodeURIComponent(connId)}/config/${filename}`;\r\n }\r\n\r\n monitoringPath(connId: string, sub: string): string {\r\n if (connId === \"_legacy\") {\r\n return `${API_BASE_PATH}/monitoring/${sub}`;\r\n }\r\n return `${API_BASE_PATH}/connections/${encodeURIComponent(connId)}/monitoring/${sub}`;\r\n }\r\n\r\n congestionPath(connId: string): string {\r\n if (connId === \"_legacy\") {\r\n return `${API_BASE_PATH}/congestion`;\r\n }\r\n return `${API_BASE_PATH}/connections/${encodeURIComponent(connId)}/congestion`;\r\n }\r\n\r\n bondingPath(connId: string): string {\r\n if (connId === \"_legacy\") {\r\n return `${API_BASE_PATH}/bonding`;\r\n }\r\n return `${API_BASE_PATH}/connections/${encodeURIComponent(connId)}/bonding`;\r\n }\r\n\r\n bondingFailoverPath(connId: string): string {\r\n if (connId === \"_legacy\") {\r\n return `${API_BASE_PATH}/bonding/failover`;\r\n }\r\n return `${API_BASE_PATH}/connections/${encodeURIComponent(connId)}/bonding/failover`;\r\n }\r\n\r\n async request(input: RequestInfo, init: RequestInit = {}): Promise<Response> {\r\n const response = await apiFetch(input, init);\r\n if (response.status === 401) {\r\n const error: AuthenticatedError = new Error(MANAGEMENT_TOKEN_ERROR_MESSAGE);\r\n error.isUnauthorized = true;\r\n throw error;\r\n }\r\n return response;\r\n }\r\n\r\n authFailureMessage(context: string): string {\r\n return `${MANAGEMENT_TOKEN_ERROR_MESSAGE} Failed while ${context}. ${this.tokenHelpText}`;\r\n }\r\n\r\n // ── Page rendering ─────────────────────────────────────────────────────────\r\n\r\n renderPage() {\r\n this.renderTabs();\r\n this.renderConnectionContent();\r\n }\r\n\r\n renderTabs() {\r\n const tabsDiv = document.getElementById(\"connectionTabs\");\r\n if (!tabsDiv) {\r\n return;\r\n }\r\n\r\n if (this.connections.length <= 1) {\r\n tabsDiv.innerHTML = \"\";\r\n tabsDiv.style.display = \"none\";\r\n return;\r\n }\r\n\r\n tabsDiv.style.display = \"\";\r\n tabsDiv.innerHTML = `<div class=\"tabs-container\">${this.connections\r\n .map((c) => {\r\n const icon = c.type === \"server\" ? \"🖥\" : \"📱\";\r\n const statusDot =\r\n c.healthy === false\r\n ? '<span class=\"tab-status-dot error\"></span>'\r\n : c.readyToSend || c.type === \"server\"\r\n ? '<span class=\"tab-status-dot ok\"></span>'\r\n : '<span class=\"tab-status-dot warning\"></span>';\r\n return `<button class=\"connection-tab${c.id === this.activeConnectionId ? \" active\" : \"\"}\"\r\n data-connection-id=\"${this.escapeHtml(c.id)}\">\r\n ${statusDot}\r\n <span class=\"tab-icon\">${icon}</span>\r\n <span class=\"tab-name\">${this.escapeHtml(c.name || c.id)}</span>\r\n <span class=\"tab-type\">${this.escapeHtml(c.type)}</span>\r\n </button>`;\r\n })\r\n .join(\"\")}</div>`;\r\n\r\n // Attach tab click listeners\r\n tabsDiv.querySelectorAll(\".connection-tab\").forEach((btn) => {\r\n btn.addEventListener(\"click\", () => {\r\n const id = (btn as HTMLElement).dataset.connectionId;\r\n if (id !== this.activeConnectionId) {\r\n this.activeConnectionId = id!;\r\n this.renderPage();\r\n this.loadConnectionData();\r\n }\r\n });\r\n });\r\n }\r\n\r\n renderConnectionContent() {\r\n const contentDiv = document.getElementById(\"connectionContent\");\r\n if (!contentDiv) {\r\n return;\r\n }\r\n\r\n const conn = this.getActiveConnection();\r\n this.isServerMode = conn.type === \"server\";\r\n\r\n if (this.isServerMode) {\r\n this.renderServerContent(contentDiv);\r\n } else {\r\n this.renderClientContent(contentDiv);\r\n }\r\n\r\n this.setupEventListeners();\r\n this.loadConnectionData();\r\n }\r\n\r\n renderServerContent(container: HTMLElement) {\r\n const telemetryAndHealth =\r\n renderCard(\r\n \"Performance Metrics\",\r\n \"Real-time reception statistics (auto-refreshes every 15 seconds)\",\r\n \"metrics\"\r\n ) +\r\n '<div id=\"monitoringSection\" style=\"display:none;\">' +\r\n renderCard(\r\n \"Network Quality\",\r\n \"Link quality score and network health indicators\",\r\n \"networkQuality\"\r\n ) +\r\n renderCard(\"Bandwidth Monitor\", \"Network reception statistics\", \"bandwidth\") +\r\n renderCard(\"Path Analytics\", \"Incoming data volume by SignalK path\", \"pathAnalytics\") +\r\n renderCard(\r\n \"Monitoring & Alerts\",\r\n \"Packet loss, retransmission tracking, and alert thresholds\",\r\n \"monitoringAlerts\"\r\n ) +\r\n \"</div>\";\r\n\r\n container.innerHTML =\r\n renderSectionGroup(\r\n \"Operations & Monitoring\",\r\n \"Track reception quality, throughput, and runtime behavior.\",\r\n telemetryAndHealth,\r\n \"operationsGroup\"\r\n ) +\r\n renderSectionGroup(\r\n \"Advanced\",\r\n \"Full plugin configurator (JSON editor).\",\r\n this.renderPluginConfigurationCard(),\r\n \"advancedGroup\"\r\n );\r\n }\r\n\r\n renderClientContent(container: HTMLElement) {\r\n const configuration =\r\n this.renderDeltaTimerCard() + this.renderSubscriptionCard() + this.renderSentenceFilterCard();\r\n\r\n const telemetryAndHealth =\r\n renderCard(\r\n \"Performance Metrics\",\r\n \"Real-time transmission statistics (auto-refreshes every 15 seconds)\",\r\n \"metrics\"\r\n ) +\r\n '<div id=\"congestionSection\" class=\"config-section\" style=\"display:none;\">' +\r\n renderCard(\r\n \"Network Quality\",\r\n \"Link quality score and network health indicators\",\r\n \"networkQuality\"\r\n ) +\r\n renderCard(\"Bandwidth Monitor\", \"Real-time data transmission statistics\", \"bandwidth\") +\r\n renderCard(\"Path Analytics\", \"Data volume by subscription path\", \"pathAnalytics\") +\r\n renderCard(\r\n \"Congestion Control\",\r\n \"AIMD congestion control state and delta timer auto-adjustment\",\r\n \"congestionControl\"\r\n ) +\r\n \"</div>\" +\r\n '<div id=\"bondingSection\" class=\"config-section\" style=\"display:none;\">' +\r\n renderCard(\r\n \"Connection Bonding\",\r\n \"Multi-link bonding status and failover control\",\r\n \"bondingStatus\"\r\n ) +\r\n \"</div>\" +\r\n '<div id=\"monitoringSection\" style=\"display:none;\">' +\r\n renderCard(\r\n \"Monitoring & Alerts\",\r\n \"Packet loss, retransmission tracking, and alert thresholds\",\r\n \"monitoringAlerts\"\r\n ) +\r\n \"</div>\" +\r\n renderCard(\"Status\", null, \"status\", \"status-info\");\r\n\r\n container.innerHTML =\r\n renderSectionGroup(\r\n \"Configuration\",\r\n \"Set up transmission behavior and plugin-level parameters.\",\r\n configuration,\r\n \"configurationGroup\"\r\n ) +\r\n renderSectionGroup(\r\n \"Operations & Monitoring\",\r\n \"Track transmission quality, reliability, and runtime performance.\",\r\n telemetryAndHealth,\r\n \"operationsGroup\"\r\n ) +\r\n renderSectionGroup(\r\n \"Advanced\",\r\n \"Full plugin configurator (JSON editor).\",\r\n this.renderPluginConfigurationCard(),\r\n \"advancedGroup\"\r\n );\r\n }\r\n\r\n renderDeltaTimerCard(): string {\r\n return `\r\n <div class=\"config-section\">\r\n <div class=\"card\">\r\n <div class=\"card-header\">\r\n <h2>Delta Timer Configuration</h2>\r\n <p>Controls how often deltas are collected and sent (in milliseconds)</p>\r\n </div>\r\n <div class=\"card-content\">\r\n <div class=\"form-group\">\r\n <label for=\"deltaTimer\">Delta Timer (ms):</label>\r\n <input type=\"number\" id=\"deltaTimer\" min=\"100\" max=\"10000\" step=\"100\" placeholder=\"1000\" />\r\n <small class=\"help-text\">\r\n Lower values = more frequent updates, higher bandwidth usage<br>\r\n Higher values = better compression ratio, lower bandwidth usage\r\n </small>\r\n </div>\r\n <button id=\"saveDeltaTimer\" class=\"btn btn-primary\">Save Delta Timer</button>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n renderSubscriptionCard(): string {\r\n return `\r\n <div class=\"config-section\">\r\n <div class=\"card\">\r\n <div class=\"card-header\">\r\n <h2>Subscription Configuration</h2>\r\n <p>Define which SignalK data paths to subscribe to</p>\r\n </div>\r\n <div class=\"card-content\">\r\n <div class=\"form-group\">\r\n <label for=\"context\">Context:</label>\r\n <input type=\"text\" id=\"context\" placeholder=\"*\" />\r\n <small class=\"help-text\">\r\n Context for the subscription (e.g., \"vessels.self\", \"*\" for all)\r\n </small>\r\n </div>\r\n <div class=\"subscription-paths\">\r\n <h3>Subscription Paths</h3>\r\n <div id=\"pathsList\" class=\"paths-list\"></div>\r\n <button id=\"addPath\" class=\"btn btn-secondary\">Add Path</button>\r\n </div>\r\n <div class=\"json-editor\">\r\n <h3>JSON Editor</h3>\r\n <textarea id=\"subscriptionJson\" rows=\"10\" placeholder='{\"context\": \"*\", \"subscribe\": [{\"path\": \"*\"}]}'></textarea>\r\n <small class=\"help-text\">Advanced: Edit the raw JSON configuration</small>\r\n </div>\r\n <button id=\"saveSubscription\" class=\"btn btn-primary\">Save Subscription</button>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n renderSentenceFilterCard(): string {\r\n return `\r\n <div class=\"config-section\">\r\n <div class=\"card\">\r\n <div class=\"card-header\">\r\n <h2>Sentence Filter</h2>\r\n <p>Exclude NMEA sentences from transmission (reduces bandwidth)</p>\r\n </div>\r\n <div class=\"card-content\">\r\n <div class=\"form-group\">\r\n <label for=\"sentenceFilter\">Excluded Sentences:</label>\r\n <input type=\"text\" id=\"sentenceFilter\" placeholder=\"\" autocomplete=\"off\" />\r\n <small class=\"help-text\">\r\n Comma-separated list of NMEA sentence types to exclude.\r\n </small>\r\n </div>\r\n <button id=\"saveSentenceFilter\" class=\"btn btn-primary\">Save Sentence Filter</button>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n renderPluginConfigurationCard(): string {\r\n return `\r\n <div class=\"config-section\">\r\n <div class=\"card\">\r\n <div class=\"card-header\">\r\n <h2>Full Plugin Configuration</h2>\r\n <p>All parameters from <code>/plugin-config</code> (advanced JSON editor)</p>\r\n </div>\r\n <div class=\"card-content\">\r\n <div id=\"pluginConfigSummary\" class=\"plugin-config-summary\">\r\n <p>Loading plugin configuration...</p>\r\n </div>\r\n <div class=\"json-editor plugin-config-editor\">\r\n <h3>Plugin Config JSON</h3>\r\n <textarea\r\n id=\"pluginConfigJson\"\r\n rows=\"20\"\r\n placeholder='{\"serverType\":\"client\",\"udpPort\":4446,\"secretKey\":\"...\"}'\r\n ></textarea>\r\n <small class=\"help-text\">\r\n This editor exposes all available plugin fields. Save triggers plugin restart when supported.\r\n </small>\r\n <small class=\"help-text\">${this.escapeHtml(this.tokenHelpText)}</small>\r\n </div>\r\n <div class=\"plugin-config-actions\">\r\n <button id=\"savePluginConfig\" class=\"btn btn-primary\">Save Full Plugin Config</button>\r\n <button id=\"reloadPluginConfig\" class=\"btn btn-secondary\">Reload From Server</button>\r\n <button id=\"loadDefaultPluginConfig\" class=\"btn btn-secondary\">Load Schema Defaults</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n // ── Data loading ───────────────────────────────────────────────────────────\r\n\r\n async loadConnectionData() {\r\n const conn = this.getActiveConnection();\r\n const connId = conn.id;\r\n\r\n if (!this.isServerMode) {\r\n await this.loadConfigurations(connId);\r\n this.updateUI();\r\n this.updateStatus();\r\n } else {\r\n this.updatePluginConfigUI();\r\n }\r\n\r\n await this.loadMetrics(connId);\r\n }\r\n\r\n async loadPluginConfiguration(showErrors = true): Promise<boolean> {\r\n try {\r\n const [configResponse, schemaResponse] = await Promise.all([\r\n this.request(`${API_BASE_PATH}/plugin-config`),\r\n this.request(`${API_BASE_PATH}/plugin-schema`)\r\n ]);\r\n\r\n if (!configResponse.ok) {\r\n throw new Error(`Failed to load plugin configuration (${configResponse.status})`);\r\n }\r\n\r\n const configData = await configResponse.json();\r\n const configuration =\r\n configData &&\r\n configData.configuration &&\r\n typeof configData.configuration === \"object\" &&\r\n !Array.isArray(configData.configuration)\r\n ? configData.configuration\r\n : {};\r\n\r\n if (schemaResponse.ok) {\r\n const schemaData = await schemaResponse.json();\r\n if (schemaData && schemaData.schema && typeof schemaData.schema === \"object\") {\r\n this.pluginSchema = schemaData.schema;\r\n }\r\n if (\r\n schemaData &&\r\n (schemaData.currentMode === \"server\" || schemaData.currentMode === \"client\")\r\n ) {\r\n this.schemaCurrentMode = schemaData.currentMode;\r\n }\r\n }\r\n\r\n this.pluginConfig = this.buildCompletePluginConfig(configuration);\r\n return true;\r\n } catch (error: unknown) {\r\n if (showErrors) {\r\n const err = error as AuthenticatedError;\r\n this.showNotification(\r\n err.isUnauthorized\r\n ? this.authFailureMessage(\"loading plugin config\")\r\n : \"Error loading plugin config: \" + err.message,\r\n \"warning\"\r\n );\r\n }\r\n return false;\r\n }\r\n }\r\n\r\n async loadConfigurations(connId: string) {\r\n try {\r\n const [deltaResponse, subResponse, filterResponse] = await Promise.all([\r\n this.request(this.configPath(connId, \"delta_timer.json\")),\r\n this.request(this.configPath(connId, \"subscription.json\")),\r\n this.request(this.configPath(connId, \"sentence_filter.json\"))\r\n ]);\r\n\r\n this.deltaTimerConfig = deltaResponse.ok ? await deltaResponse.json() : null;\r\n this.subscriptionConfig = subResponse.ok ? await subResponse.json() : null;\r\n this.sentenceFilterConfig = filterResponse.ok ? await filterResponse.json() : null;\r\n } catch (error: unknown) {\r\n const err = error as AuthenticatedError;\r\n this.showNotification(\r\n err.isUnauthorized\r\n ? this.authFailureMessage(\"loading connection configuration\")\r\n : \"Error loading configurations: \" + err.message,\r\n \"error\"\r\n );\r\n }\r\n }\r\n\r\n async loadMetrics(connId?: string) {\r\n if (!connId) {\r\n connId = this.activeConnectionId!;\r\n }\r\n try {\r\n const response = await this.request(this.metricsPath(connId));\r\n if (response.ok) {\r\n const metrics = await response.json();\r\n this.protocolVersion = metrics.protocolVersion || 1;\r\n this.updateMetricsDisplay(metrics);\r\n\r\n if (this.protocolVersion >= 2) {\r\n this.loadV2Data(connId);\r\n }\r\n }\r\n } catch (error: unknown) {\r\n console.error(\"Error loading metrics:\", (error as Error).message);\r\n }\r\n }\r\n\r\n async loadV2Data(connId: string) {\r\n const isClient = !this.isServerMode;\r\n\r\n const fetches: Promise<Response | null>[] = [\r\n this.request(this.monitoringPath(connId, \"alerts\")).catch(() => null),\r\n this.request(this.monitoringPath(connId, \"packet-loss\")).catch(() => null),\r\n this.request(this.monitoringPath(connId, \"retransmissions\")).catch(() => null)\r\n ];\r\n\r\n if (isClient) {\r\n fetches.push(\r\n this.request(this.congestionPath(connId)).catch(() => null),\r\n this.request(this.bondingPath(connId)).catch(() => null)\r\n );\r\n }\r\n\r\n const results = await Promise.all(fetches);\r\n const [alertsRes, packetLossRes, retransmissionsRes, congestionRes, bondingRes] = results;\r\n\r\n const monitoringData: Record<string, unknown> = {};\r\n if (alertsRes && alertsRes.ok) {\r\n monitoringData.alerts = await alertsRes.json();\r\n }\r\n if (packetLossRes && packetLossRes.ok) {\r\n monitoringData.packetLoss = await packetLossRes.json();\r\n }\r\n if (retransmissionsRes && retransmissionsRes.ok) {\r\n monitoringData.retransmissions = await retransmissionsRes.json();\r\n }\r\n this.updateMonitoringDisplay(monitoringData);\r\n\r\n if (isClient && congestionRes && congestionRes.ok) {\r\n const congestionData = await congestionRes.json();\r\n this.updateCongestionDisplay(congestionData);\r\n }\r\n\r\n if (isClient && bondingRes && bondingRes.ok) {\r\n const bondingData = await bondingRes.json();\r\n this.updateBondingDisplay(bondingData);\r\n }\r\n }\r\n\r\n startMetricsRefresh() {\r\n if (this.metricsInterval) {\r\n clearInterval(this.metricsInterval);\r\n }\r\n\r\n this.metricsInterval = setInterval(() => {\r\n if (this._refreshInFlight) {\r\n return;\r\n }\r\n this._refreshInFlight = true;\r\n this.refreshActiveTab().finally(() => {\r\n this._refreshInFlight = false;\r\n });\r\n }, METRICS_REFRESH_INTERVAL);\r\n }\r\n\r\n async refreshActiveTab() {\r\n // Refresh the connections list to update tab badges\r\n try {\r\n const res = await this.request(`${API_BASE_PATH}/connections`);\r\n if (res.ok) {\r\n const updated = await res.json();\r\n if (updated.length > 0) {\r\n this.connections = updated;\r\n this.renderTabs();\r\n }\r\n }\r\n } catch (_e) {\r\n /* ignore */\r\n }\r\n\r\n await this.loadMetrics(this.activeConnectionId!);\r\n }\r\n\r\n // ── Event listeners ────────────────────────────────────────────────────────\r\n\r\n setupEventListeners() {\r\n const saveDeltaTimerBtn = document.getElementById(\"saveDeltaTimer\");\r\n if (saveDeltaTimerBtn) {\r\n saveDeltaTimerBtn.addEventListener(\"click\", () => this.saveDeltaTimer());\r\n }\r\n\r\n const saveSubscriptionBtn = document.getElementById(\"saveSubscription\");\r\n if (saveSubscriptionBtn) {\r\n saveSubscriptionBtn.addEventListener(\"click\", () => this.saveSubscription());\r\n }\r\n\r\n const saveSentenceFilterBtn = document.getElementById(\"saveSentenceFilter\");\r\n if (saveSentenceFilterBtn) {\r\n saveSentenceFilterBtn.addEventListener(\"click\", () => this.saveSentenceFilter());\r\n }\r\n\r\n const addPathBtn = document.getElementById(\"addPath\");\r\n if (addPathBtn) {\r\n addPathBtn.addEventListener(\"click\", () => this.addPathItem());\r\n }\r\n\r\n const subscriptionJsonEditor = document.getElementById(\"subscriptionJson\");\r\n if (subscriptionJsonEditor) {\r\n subscriptionJsonEditor.addEventListener(\"input\", () => {\r\n if (this.syncTimeout) {\r\n clearTimeout(this.syncTimeout);\r\n }\r\n this.syncTimeout = setTimeout(() => this.syncFromJson(), JSON_SYNC_DEBOUNCE);\r\n });\r\n }\r\n\r\n const contextInput = document.getElementById(\"context\");\r\n if (contextInput) {\r\n contextInput.addEventListener(\"input\", () => this.updateJsonFromForm());\r\n }\r\n\r\n const savePluginConfigBtn = document.getElementById(\"savePluginConfig\");\r\n if (savePluginConfigBtn) {\r\n savePluginConfigBtn.addEventListener(\"click\", () => this.savePluginConfig());\r\n }\r\n\r\n const reloadPluginConfigBtn = document.getElementById(\"reloadPluginConfig\");\r\n if (reloadPluginConfigBtn) {\r\n reloadPluginConfigBtn.addEventListener(\"click\", () => this.reloadPluginConfiguration());\r\n }\r\n\r\n const loadDefaultPluginConfigBtn = document.getElementById(\"loadDefaultPluginConfig\");\r\n if (loadDefaultPluginConfigBtn) {\r\n loadDefaultPluginConfigBtn.addEventListener(\"click\", () =>\r\n this.loadDefaultPluginConfiguration()\r\n );\r\n }\r\n }\r\n\r\n // ── UI updates ─────────────────────────────────────────────────────────────\r\n\r\n updateUI() {\r\n this.updatePluginConfigUI();\r\n\r\n if (this.deltaTimerConfig && (this.deltaTimerConfig as Record<string, unknown>).deltaTimer) {\r\n const el = document.getElementById(\"deltaTimer\") as HTMLInputElement | null;\r\n if (el) {\r\n el.value = String((this.deltaTimerConfig as Record<string, unknown>).deltaTimer);\r\n }\r\n }\r\n\r\n if (this.subscriptionConfig) {\r\n const cfg = this.subscriptionConfig as Record<string, unknown>;\r\n const ctxEl = document.getElementById(\"context\") as HTMLInputElement | null;\r\n if (ctxEl) {\r\n ctxEl.value = (cfg.context as string) || \"*\";\r\n }\r\n\r\n const pathsList = document.getElementById(\"pathsList\");\r\n if (pathsList) {\r\n pathsList.innerHTML = \"\";\r\n if (cfg.subscribe && Array.isArray(cfg.subscribe)) {\r\n (cfg.subscribe as Array<{ path: string }>).forEach((sub) => this.addPathItem(sub.path));\r\n }\r\n }\r\n\r\n const jsonEl = document.getElementById(\"subscriptionJson\") as HTMLTextAreaElement | null;\r\n if (jsonEl) {\r\n jsonEl.value = JSON.stringify(this.subscriptionConfig, null, 2);\r\n }\r\n }\r\n\r\n if (\r\n this.sentenceFilterConfig &&\r\n Array.isArray((this.sentenceFilterConfig as Record<string, unknown>).excludedSentences)\r\n ) {\r\n const el = document.getElementById(\"sentenceFilter\") as HTMLInputElement | null;\r\n if (el) {\r\n el.value = (\r\n (this.sentenceFilterConfig as Record<string, unknown>).excludedSentences as string[]\r\n ).join(\", \");\r\n }\r\n }\r\n }\r\n\r\n updatePluginConfigUI() {\r\n const editor = document.getElementById(\"pluginConfigJson\") as HTMLTextAreaElement | null;\r\n const summary = document.getElementById(\"pluginConfigSummary\");\r\n\r\n if (!editor) {\r\n return;\r\n }\r\n\r\n if (!this.pluginConfig) {\r\n editor.value = \"{}\";\r\n if (summary) {\r\n summary.innerHTML = \"<p>Plugin config unavailable.</p>\";\r\n }\r\n return;\r\n }\r\n\r\n editor.value = JSON.stringify(this.pluginConfig, null, 2);\r\n\r\n if (summary) {\r\n const hasConnections =\r\n Array.isArray(this.pluginConfig.connections) &&\r\n (this.pluginConfig.connections as unknown[]).length > 0;\r\n\r\n let summaryScope = \"Top-level\";\r\n let keyLabel = \"Top-Level Fields\";\r\n let summaryConfig: Record<string, unknown> = this.pluginConfig;\r\n\r\n if (hasConnections) {\r\n const totalConnections = (this.pluginConfig.connections as unknown[]).length;\r\n const runtimeIndex = this.connections.findIndex((c) => c.id === this.activeConnectionId);\r\n const summaryIndex =\r\n runtimeIndex >= 0 && runtimeIndex < totalConnections ? runtimeIndex : 0;\r\n const candidate = (this.pluginConfig.connections as Record<string, unknown>[])[\r\n summaryIndex\r\n ];\r\n summaryConfig =\r\n candidate && typeof candidate === \"object\" && !Array.isArray(candidate) ? candidate : {};\r\n summaryScope = `Connection ${summaryIndex + 1}/${totalConnections}`;\r\n keyLabel = \"Connection Fields\";\r\n }\r\n\r\n const mode = this.normalizeServerType(summaryConfig.serverType) || \"client\";\r\n const protocol =\r\n Number(summaryConfig.protocolVersion) >= 2 ? Number(summaryConfig.protocolVersion) : 1;\r\n const keyCount = Object.keys(summaryConfig).length;\r\n summary.innerHTML = `\r\n <div class=\"plugin-summary-grid\">\r\n ${renderStatItem(\"Scope\", summaryScope)}\r\n ${renderStatItem(\"Mode\", mode.toUpperCase())}\r\n ${renderStatItem(\"Protocol\", \"v\" + String(protocol))}\r\n ${renderStatItem(keyLabel, String(keyCount))}\r\n </div>\r\n `;\r\n }\r\n }\r\n\r\n // ── Form helpers (subscription paths) ──────────────────────────────────────\r\n\r\n addPathItem(path = \"\") {\r\n const pathsList = document.getElementById(\"pathsList\");\r\n if (!pathsList) {\r\n return;\r\n }\r\n\r\n const pathItem = document.createElement(\"div\");\r\n pathItem.className = \"path-item\";\r\n\r\n const input = document.createElement(\"input\");\r\n input.type = \"text\";\r\n input.value = path;\r\n input.placeholder = \"navigation.position\";\r\n input.className = \"path-input\";\r\n\r\n const button = document.createElement(\"button\");\r\n button.type = \"button\";\r\n button.className = \"btn btn-danger\";\r\n button.textContent = \"Remove\";\r\n\r\n input.addEventListener(\"input\", () => this.updateJsonFromForm());\r\n button.addEventListener(\"click\", () => {\r\n pathItem.remove();\r\n this.updateJsonFromForm();\r\n });\r\n\r\n pathItem.appendChild(input);\r\n pathItem.appendChild(button);\r\n pathsList.appendChild(pathItem);\r\n this.updateJsonFromForm();\r\n }\r\n\r\n updateJsonFromForm() {\r\n const contextEl = document.getElementById(\"context\") as HTMLInputElement | null;\r\n const context = contextEl ? contextEl.value || \"*\" : \"*\";\r\n const pathInputs = document.querySelectorAll(\".path-input\") as NodeListOf<HTMLInputElement>;\r\n const subscribe = Array.from(pathInputs)\r\n .map((input) => ({ path: input.value }))\r\n .filter((sub) => sub.path.trim() !== \"\");\r\n\r\n const config = { context, subscribe };\r\n const jsonEl = document.getElementById(\"subscriptionJson\") as HTMLTextAreaElement | null;\r\n if (jsonEl) {\r\n jsonEl.value = JSON.stringify(config, null, 2);\r\n }\r\n }\r\n\r\n syncFromJson() {\r\n try {\r\n const jsonEl = document.getElementById(\"subscriptionJson\") as HTMLTextAreaElement | null;\r\n if (!jsonEl) {\r\n return;\r\n }\r\n const config = JSON.parse(jsonEl.value);\r\n\r\n const ctxEl = document.getElementById(\"context\") as HTMLInputElement | null;\r\n if (ctxEl) {\r\n ctxEl.value = config.context || \"*\";\r\n }\r\n\r\n const pathsList = document.getElementById(\"pathsList\");\r\n if (pathsList) {\r\n pathsList.innerHTML = \"\";\r\n if (config.subscribe && Array.isArray(config.subscribe)) {\r\n config.subscribe.forEach((sub: { path?: string }) => this.addPathItem(sub.path || \"\"));\r\n }\r\n }\r\n } catch (error: unknown) {\r\n console.warn(\"Invalid JSON in editor:\", (error as Error).message);\r\n }\r\n }\r\n\r\n // ── Save operations ────────────────────────────────────────────────────────\r\n\r\n async saveConfig(filename: string, config: unknown, configKey: string, label: string) {\r\n const connId = this.activeConnectionId!;\r\n try {\r\n const response = await this.request(this.configPath(connId, filename), {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify(config)\r\n });\r\n\r\n if (response.ok) {\r\n (this as Record<string, unknown>)[configKey] = config;\r\n this.showNotification(`${label} saved successfully!`, \"success\");\r\n this.updateStatus();\r\n } else {\r\n throw new Error(\"Failed to save configuration\");\r\n }\r\n } catch (error: unknown) {\r\n const err = error as AuthenticatedError;\r\n this.showNotification(\r\n err.isUnauthorized\r\n ? this.authFailureMessage(`saving ${label.toLowerCase()}`)\r\n : `Error saving ${label.toLowerCase()}: ` + err.message,\r\n \"error\"\r\n );\r\n }\r\n }\r\n\r\n async saveDeltaTimer() {\r\n const deltaTimer = parseInt((document.getElementById(\"deltaTimer\") as HTMLInputElement).value);\r\n\r\n if (isNaN(deltaTimer) || deltaTimer < DELTA_TIMER_MIN || deltaTimer > DELTA_TIMER_MAX) {\r\n this.showNotification(\r\n `Delta timer must be between ${DELTA_TIMER_MIN} and ${DELTA_TIMER_MAX} milliseconds`,\r\n \"error\"\r\n );\r\n return;\r\n }\r\n\r\n await this.saveConfig(\r\n \"delta_timer.json\",\r\n { deltaTimer },\r\n \"deltaTimerConfig\",\r\n \"Delta timer configuration\"\r\n );\r\n }\r\n\r\n async saveSubscription() {\r\n try {\r\n const jsonText = (document.getElementById(\"subscriptionJson\") as HTMLTextAreaElement).value;\r\n const config = JSON.parse(jsonText);\r\n\r\n if (!config.context) {\r\n throw new Error(\"Context is required\");\r\n }\r\n if (!config.subscribe || !Array.isArray(config.subscribe)) {\r\n throw new Error(\"Subscribe array is required\");\r\n }\r\n\r\n await this.saveConfig(\r\n \"subscription.json\",\r\n config,\r\n \"subscriptionConfig\",\r\n \"Subscription configuration\"\r\n );\r\n } catch (error: unknown) {\r\n this.showNotification(\"Error saving subscription: \" + (error as Error).message, \"error\");\r\n }\r\n }\r\n\r\n async saveSentenceFilter() {\r\n const filterInput = (document.getElementById(\"sentenceFilter\") as HTMLInputElement).value;\r\n const excludedSentences = filterInput\r\n .split(\",\")\r\n .map((s) => s.trim().toUpperCase())\r\n .filter((s) => s.length > 0);\r\n\r\n await this.saveConfig(\r\n \"sentence_filter.json\",\r\n { excludedSentences },\r\n \"sentenceFilterConfig\",\r\n \"Sentence filter\"\r\n );\r\n }\r\n\r\n async savePluginConfig() {\r\n const editor = document.getElementById(\"pluginConfigJson\") as HTMLTextAreaElement | null;\r\n if (!editor) {\r\n return;\r\n }\r\n\r\n try {\r\n const parsedConfig = JSON.parse(editor.value);\r\n if (!parsedConfig || typeof parsedConfig !== \"object\" || Array.isArray(parsedConfig)) {\r\n throw new Error(\"Plugin configuration must be a JSON object\");\r\n }\r\n\r\n const requestConfig = this.deepClone(parsedConfig) as Record<string, unknown>;\r\n const normalizedServerType = this.normalizeServerType(requestConfig.serverType);\r\n if (normalizedServerType) {\r\n requestConfig.serverType = normalizedServerType;\r\n }\r\n\r\n const response = await this.request(`${API_BASE_PATH}/plugin-config`, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify(requestConfig)\r\n });\r\n\r\n const result = await response.json().catch(() => ({}) as Record<string, unknown>);\r\n if (!response.ok || !result.success) {\r\n throw new Error(result.error || `Failed to save plugin configuration (${response.status})`);\r\n }\r\n\r\n this.pluginConfig = this.buildCompletePluginConfig(requestConfig);\r\n this.updatePluginConfigUI();\r\n this.showNotification(\r\n result.message || \"Plugin configuration saved. Refresh to apply changes.\",\r\n \"success\"\r\n );\r\n } catch (error: unknown) {\r\n const err = error as AuthenticatedError;\r\n this.showNotification(\r\n err.isUnauthorized\r\n ? this.authFailureMessage(\"saving full plugin config\")\r\n : \"Error saving full plugin config: \" + err.message,\r\n \"error\"\r\n );\r\n }\r\n }\r\n\r\n async reloadPluginConfiguration() {\r\n const loaded = await this.loadPluginConfiguration(true);\r\n if (loaded) {\r\n this.updatePluginConfigUI();\r\n this.showNotification(\"Plugin configuration reloaded.\", \"success\");\r\n }\r\n }\r\n\r\n loadDefaultPluginConfiguration() {\r\n this.pluginConfig = this.buildCompletePluginConfig({});\r\n this.updatePluginConfigUI();\r\n this.showNotification(\"Loaded schema defaults into editor. Save to apply.\", \"warning\");\r\n }\r\n\r\n // ── Metrics display ────────────────────────────────────────────────────────\r\n\r\n updateMetricsDisplay(metrics: Record<string, any>) {\r\n this.updateNetworkQualityDisplay(metrics);\r\n this.updateBandwidthDisplay(metrics);\r\n this.updatePathAnalyticsDisplay(metrics);\r\n\r\n const metricsDiv = document.getElementById(\"metrics\");\r\n if (!metricsDiv) {\r\n return;\r\n }\r\n\r\n const isClient = metrics.mode === \"client\";\r\n const { stats, status, uptime } = metrics;\r\n\r\n const cryptoErrors = (stats.errorCounts && stats.errorCounts.crypto) || 0;\r\n const malformedPackets = stats.malformedPackets || 0;\r\n\r\n const hasErrors =\r\n stats.udpSendErrors > 0 ||\r\n stats.compressionErrors > 0 ||\r\n stats.encryptionErrors > 0 ||\r\n stats.subscriptionErrors > 0 ||\r\n cryptoErrors > 0 ||\r\n malformedPackets > 0;\r\n\r\n const protocolVersion = metrics.protocolVersion || 1;\r\n const protocolLabel = protocolVersion >= 2 ? `v${protocolVersion}` : \"v1\";\r\n\r\n const metricsGridItems = [\r\n renderMetricItem(\"Uptime\", uptime.formatted),\r\n renderMetricItem(\"Mode\", isClient ? \"Client\" : \"Server\"),\r\n renderMetricItemHtml(\r\n \"Protocol\",\r\n `<span class=\"protocol-badge protocol-${escapeHtml(protocolLabel)}\">${escapeHtml(protocolLabel.toUpperCase())}</span>`\r\n ),\r\n renderMetricItem(\r\n \"Status\",\r\n status.readyToSend ? \"Ready\" : \"Not Ready\",\r\n status.readyToSend ? \"success\" : \"error\"\r\n ),\r\n isClient ? renderMetricItem(\"Buffered Deltas\", status.deltasBuffered) : \"\"\r\n ].join(\"\");\r\n\r\n const subErrors = stats.subscriptionErrors;\r\n const subscriptionErrorStat = isClient\r\n ? renderStatItem(\"Subscription Errors\", subErrors, subErrors > 0)\r\n : \"\";\r\n\r\n const statsItems = [\r\n isClient\r\n ? renderStatItem(\"Deltas Sent\", stats.deltasSent.toLocaleString())\r\n : renderStatItem(\"Deltas Received\", stats.deltasReceived.toLocaleString()),\r\n isClient\r\n ? renderStatItem(\"UDP Send Errors\", stats.udpSendErrors, stats.udpSendErrors > 0)\r\n : \"\",\r\n isClient ? renderStatItem(\"UDP Retries\", stats.udpRetries) : \"\",\r\n renderStatItem(\"Compression Errors\", stats.compressionErrors, stats.compressionErrors > 0),\r\n renderStatItem(\"Encryption Errors\", stats.encryptionErrors, stats.encryptionErrors > 0),\r\n subscriptionErrorStat,\r\n !isClient && stats.duplicatePackets > 0\r\n ? renderStatItem(\"Duplicate Packets\", stats.duplicatePackets.toLocaleString())\r\n : \"\",\r\n protocolVersion >= 3\r\n ? renderStatItem(\"Auth Failures (V3)\", cryptoErrors, cryptoErrors > 0)\r\n : \"\",\r\n renderStatItem(\"Malformed Packets\", malformedPackets, malformedPackets > 0)\r\n ].join(\"\");\r\n\r\n let metricsHtml = `\r\n <h4>Performance Metrics</h4>\r\n <div class=\"metrics-grid\">${metricsGridItems}</div>\r\n <div class=\"metrics-stats\">\r\n <h5>Transmission Statistics</h5>\r\n <div class=\"stats-grid\">${statsItems}</div>\r\n </div>\r\n `;\r\n\r\n if (isClient && metrics.smartBatching) {\r\n const sb = metrics.smartBatching;\r\n const totalSends = sb.earlySends + sb.timerSends;\r\n const earlyPercent = totalSends > 0 ? Math.round((sb.earlySends / totalSends) * 100) : 0;\r\n\r\n metricsHtml += `\r\n <div class=\"metrics-stats\">\r\n <h5>Smart Batching</h5>\r\n <div class=\"stats-grid\">\r\n ${renderStatItem(\"Avg Bytes/Delta\", sb.avgBytesPerDelta + \" bytes\")}\r\n ${renderStatItem(\"Max Deltas/Batch\", sb.maxDeltasPerBatch)}\r\n ${renderStatItem(\"Early Sends\", sb.earlySends.toLocaleString() + \" (\" + earlyPercent + \"%)\")}\r\n ${renderStatItem(\"Timer Sends\", sb.timerSends.toLocaleString())}\r\n ${renderStatItem(\"Oversized Packets\", sb.oversizedPackets, sb.oversizedPackets > 0)}\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n if (Array.isArray(metrics.recentErrors) && metrics.recentErrors.length > 0) {\r\n const errorItems = [...metrics.recentErrors]\r\n .reverse()\r\n .map((err) => {\r\n const timeAgo = Date.now() - err.timestamp;\r\n const timeStr =\r\n timeAgo < 60000\r\n ? `${Math.floor(timeAgo / 1000)}s ago`\r\n : `${Math.floor(timeAgo / 60000)}m ago`;\r\n return `\r\n <div class=\"recent-error-item\">\r\n <span class=\"error-category-badge\">${this.escapeHtml(err.category)}</span>\r\n <span class=\"recent-error-msg\">${this.escapeHtml(err.message)}</span>\r\n <span class=\"recent-error-time\">${this.escapeHtml(timeStr)}</span>\r\n </div>\r\n `;\r\n })\r\n .join(\"\");\r\n metricsHtml += `\r\n <div class=\"metrics-error\">\r\n <h5>Recent Errors (${metrics.recentErrors.length})</h5>\r\n <div class=\"recent-errors-list\">${errorItems}</div>\r\n </div>\r\n `;\r\n } else if (metrics.lastError) {\r\n const timeAgo = metrics.lastError.timeAgo;\r\n const timeAgoStr =\r\n timeAgo < 60000\r\n ? `${Math.floor(timeAgo / 1000)}s ago`\r\n : `${Math.floor(timeAgo / 60000)}m ago`;\r\n\r\n metricsHtml += `\r\n <div class=\"metrics-error\">\r\n <h5>Last Error</h5>\r\n <div class=\"error-message\">${this.escapeHtml(metrics.lastError.message)}</div>\r\n <div class=\"error-time\">Occurred ${timeAgoStr}</div>\r\n </div>\r\n `;\r\n } else if (!hasErrors) {\r\n metricsHtml += `\r\n <div class=\"metrics-success\">\r\n <div class=\"success-message\">No errors detected</div>\r\n </div>\r\n `;\r\n }\r\n\r\n metricsDiv.innerHTML = metricsHtml;\r\n }\r\n\r\n updateNetworkQualityDisplay(metrics: Record<string, any>) {\r\n const nqDiv = document.getElementById(\"networkQuality\");\r\n if (!nqDiv || !metrics.networkQuality) {\r\n return;\r\n }\r\n\r\n const nq = metrics.networkQuality;\r\n const isClient = metrics.mode === \"client\";\r\n\r\n let qualityLabel = \"N/A\";\r\n let qualityColor = \"#9E9E9E\";\r\n if (nq.linkQuality !== undefined) {\r\n if (nq.linkQuality >= 90) {\r\n qualityLabel = \"Excellent\";\r\n qualityColor = \"#4CAF50\";\r\n } else if (nq.linkQuality >= 70) {\r\n qualityLabel = \"Good\";\r\n qualityColor = \"#FFC107\";\r\n } else if (nq.linkQuality >= 50) {\r\n qualityLabel = \"Fair\";\r\n qualityColor = \"#FF9800\";\r\n } else {\r\n qualityLabel = \"Poor\";\r\n qualityColor = \"#F44336\";\r\n }\r\n }\r\n\r\n const qualityPct = nq.linkQuality !== undefined ? nq.linkQuality : 0;\r\n const gaugeAngle = (qualityPct / 100) * 180;\r\n const radStart = Math.PI;\r\n const radEnd = radStart + (gaugeAngle * Math.PI) / 180;\r\n const cx = 50,\r\n cy = 50,\r\n r = 40;\r\n const x1 = cx + r * Math.cos(radStart);\r\n const y1 = cy + r * Math.sin(radStart);\r\n const x2 = cx + r * Math.cos(radEnd);\r\n const y2 = cy + r * Math.sin(radEnd);\r\n const largeArc = gaugeAngle > 180 ? 1 : 0;\r\n\r\n const gaugeArcPath =\r\n qualityPct > 0\r\n ? `<path d=\"M ${x1} ${y1} A ${r} ${r} 0 ${largeArc} 1 ${x2} ${y2}\"\r\n fill=\"none\" stroke=\"${qualityColor}\" stroke-width=\"8\" stroke-linecap=\"round\"/>`\r\n : \"\";\r\n\r\n const gaugeSvg = `\r\n <svg viewBox=\"0 0 100 55\" class=\"quality-gauge\" preserveAspectRatio=\"xMidYMid meet\">\r\n <path d=\"M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}\"\r\n fill=\"none\" stroke=\"#E0E0E0\" stroke-width=\"8\" stroke-linecap=\"round\"/>\r\n ${gaugeArcPath}\r\n <text x=\"${cx}\" y=\"${cy - 5}\" text-anchor=\"middle\" font-size=\"16\" font-weight=\"bold\" fill=\"${qualityColor}\">\r\n ${qualityPct}\r\n </text>\r\n <text x=\"${cx}\" y=\"${cy + 8}\" text-anchor=\"middle\" font-size=\"7\" fill=\"#666\">\r\n ${qualityLabel}\r\n </text>\r\n </svg>\r\n `;\r\n\r\n const rttDisplay = nq.rtt !== undefined ? nq.rtt + \" ms\" : \"N/A\";\r\n const jitterDisplay = nq.jitter !== undefined ? nq.jitter + \" ms\" : \"N/A\";\r\n\r\n let nqHtml = `\r\n <div class=\"network-quality-dashboard\">\r\n <div class=\"nq-hero\">\r\n <div class=\"nq-gauge-container\">\r\n ${gaugeSvg}\r\n <div class=\"nq-gauge-label\">Link Quality</div>\r\n </div>\r\n <div class=\"nq-key-metrics\">\r\n ${renderMetricItem(\"RTT\", rttDisplay, nq.rtt > 500 ? \"error\" : nq.rtt > 200 ? \"warning\" : \"\")}\r\n ${renderMetricItem(\"Jitter\", jitterDisplay, nq.jitter > 100 ? \"error\" : nq.jitter > 50 ? \"warning\" : \"\")}\r\n </div>\r\n </div>\r\n\r\n <div class=\"nq-details\">\r\n <h5>Reliability Statistics</h5>\r\n <div class=\"stats-grid\">\r\n `;\r\n\r\n if (isClient) {\r\n nqHtml += `\r\n ${renderStatItem(\"Retransmissions\", (nq.retransmissions || 0).toLocaleString(), nq.retransmissions > 0)}\r\n ${renderStatItem(\"Queue Depth\", (nq.queueDepth || 0).toLocaleString(), nq.queueDepth > 100)}\r\n `;\r\n } else {\r\n nqHtml += `\r\n ${renderStatItem(\"ACKs Sent\", (nq.acksSent || 0).toLocaleString())}\r\n ${renderStatItem(\"NAKs Sent\", (nq.naksSent || 0).toLocaleString(), nq.naksSent > 0)}\r\n `;\r\n }\r\n\r\n nqHtml += `\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n\r\n nqDiv.innerHTML = nqHtml;\r\n }\r\n\r\n updateBandwidthDisplay(metrics: Record<string, any>) {\r\n const bandwidthDiv = document.getElementById(\"bandwidth\");\r\n if (!bandwidthDiv || !metrics.bandwidth) {\r\n return;\r\n }\r\n\r\n const bw = metrics.bandwidth;\r\n const isClient = metrics.mode === \"client\";\r\n\r\n const savedBytes = isClient ? bw.bytesOutRaw - bw.bytesOut : bw.bytesInRaw - bw.bytesIn;\r\n const savedFormatted = this.formatBytes(savedBytes > 0 ? savedBytes : 0);\r\n\r\n let bandwidthStats: string[];\r\n if (isClient) {\r\n bandwidthStats = [\r\n renderBwStat(\"Total Sent (Compressed)\", bw.bytesOutFormatted),\r\n renderBwStat(\"Total Raw (Before Compression)\", bw.bytesOutRawFormatted),\r\n renderBwStat(\"Bandwidth Saved\", savedFormatted, true, true),\r\n renderBwStat(\"Packets Sent\", bw.packetsOut.toLocaleString())\r\n ];\r\n } else {\r\n bandwidthStats = [\r\n renderBwStat(\"Total Received (Compressed)\", bw.bytesInFormatted),\r\n renderBwStat(\"Total Raw (After Decompression)\", this.formatBytes(bw.bytesInRaw || 0)),\r\n renderBwStat(\"Bandwidth Saved\", savedFormatted, true, true),\r\n renderBwStat(\"Packets Received\", bw.packetsIn.toLocaleString())\r\n ];\r\n }\r\n\r\n const bandwidthHtml = `\r\n <div class=\"bandwidth-dashboard\">\r\n <div class=\"bandwidth-hero\">\r\n <div class=\"hero-stat ${isClient ? \"primary\" : \"secondary\"}\">\r\n <div class=\"hero-value\">${isClient ? bw.rateOutFormatted : bw.rateInFormatted}</div>\r\n <div class=\"hero-label\">${isClient ? \"Upload Rate\" : \"Download Rate\"}</div>\r\n </div>\r\n <div class=\"hero-stat success\">\r\n <div class=\"hero-value\">${bw.compressionRatio}%</div>\r\n <div class=\"hero-label\">Compression Ratio</div>\r\n </div>\r\n <div class=\"hero-stat\">\r\n <div class=\"hero-value\">${bw.avgPacketSizeFormatted}</div>\r\n <div class=\"hero-label\">Avg Packet Size</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"bandwidth-details\">\r\n <h5>Bandwidth Details</h5>\r\n <div class=\"bandwidth-grid\">${bandwidthStats.join(\"\")}</div>\r\n </div>\r\n\r\n ${this.renderBandwidthChart(bw.history, isClient)}\r\n </div>\r\n `;\r\n\r\n bandwidthDiv.innerHTML = bandwidthHtml;\r\n }\r\n\r\n renderBandwidthChart(\r\n history: Array<{ rateOut: number; rateIn: number }> | undefined,\r\n isClient: boolean\r\n ): string {\r\n if (!history || history.length < 2) {\r\n return `\r\n <div class=\"bandwidth-chart-placeholder\">\r\n <p>Collecting data for chart... (${history ? history.length : 0}/2 points)</p>\r\n </div>\r\n `;\r\n }\r\n\r\n const width = 100;\r\n const height = 40;\r\n const maxRate = Math.max(...history.map((h) => (isClient ? h.rateOut : h.rateIn)), 1);\r\n const points = history\r\n .map((h, i) => {\r\n const x = (i / (history.length - 1)) * width;\r\n const y = height - ((isClient ? h.rateOut : h.rateIn) / maxRate) * height;\r\n return `${x},${y}`;\r\n })\r\n .join(\" \");\r\n\r\n const maxRateFormatted = this.formatBytes(maxRate);\r\n const intervalSeconds = METRICS_REFRESH_INTERVAL / 1000;\r\n\r\n return `\r\n <div class=\"bandwidth-chart\">\r\n <h5>Rate History (Last ${history.length * intervalSeconds}s)</h5>\r\n <div class=\"chart-container\">\r\n <svg viewBox=\"0 0 ${width} ${height}\" class=\"sparkline\" preserveAspectRatio=\"none\">\r\n <polyline\r\n fill=\"none\"\r\n stroke=\"var(--primary-color)\"\r\n stroke-width=\"1.5\"\r\n points=\"${points}\"\r\n />\r\n </svg>\r\n <div class=\"chart-labels\">\r\n <span class=\"chart-max\">${maxRateFormatted}/s</span>\r\n <span class=\"chart-min\">0</span>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n updatePathAnalyticsDisplay(metrics: Record<string, any>) {\r\n const pathDiv = document.getElementById(\"pathAnalytics\");\r\n if (!pathDiv || !metrics.pathStats) {\r\n return;\r\n }\r\n\r\n const paths: Array<{\r\n path: string;\r\n updatesPerMinute: number;\r\n bytesFormatted: string;\r\n percentage: number;\r\n }> = metrics.pathStats;\r\n\r\n if (paths.length === 0) {\r\n pathDiv.innerHTML = `\r\n <div class=\"path-analytics-empty\">\r\n <p>No path data collected yet. Data will appear once deltas are transmitted.</p>\r\n </div>\r\n `;\r\n return;\r\n }\r\n\r\n const categoryCount = new Set(paths.map((p) => p.path.split(\".\")[0])).size;\r\n\r\n let pathHtml = `\r\n <div class=\"path-analytics-dashboard\">\r\n <div class=\"path-summary\">\r\n <div class=\"summary-stat\">\r\n <span class=\"summary-value\">${paths.length}</span>\r\n <span class=\"summary-label\">Active Paths</span>\r\n </div>\r\n <div class=\"summary-stat\">\r\n <span class=\"summary-value\">${categoryCount}</span>\r\n <span class=\"summary-label\">Categories</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"path-table-container\">\r\n <table class=\"path-table\">\r\n <thead>\r\n <tr>\r\n <th>Path</th>\r\n <th>Updates/min</th>\r\n <th>Data Volume</th>\r\n <th>% of Total</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n `;\r\n\r\n paths.slice(0, 15).forEach((p) => {\r\n const barWidth = Math.max(p.percentage, 2);\r\n pathHtml += `\r\n <tr>\r\n <td class=\"path-name\" title=\"${this.escapeHtml(p.path)}\">${this.escapeHtml(p.path)}</td>\r\n <td class=\"path-rate\">${p.updatesPerMinute}</td>\r\n <td class=\"path-bytes\">${p.bytesFormatted}</td>\r\n <td class=\"path-percentage\">\r\n <div class=\"percentage-bar-container\">\r\n <div class=\"percentage-bar\" style=\"width: ${barWidth}%\"></div>\r\n <span class=\"percentage-text\">${p.percentage}%</span>\r\n </div>\r\n </td>\r\n </tr>\r\n `;\r\n });\r\n\r\n pathHtml += `\r\n </tbody>\r\n </table>\r\n </div>\r\n `;\r\n\r\n if (paths.length > 15) {\r\n pathHtml += `\r\n <div class=\"path-more\">\r\n <p>Showing top 15 of ${paths.length} paths</p>\r\n </div>\r\n `;\r\n }\r\n\r\n pathHtml += \"</div>\";\r\n pathDiv.innerHTML = pathHtml;\r\n }\r\n\r\n updateCongestionDisplay(data: Record<string, any>) {\r\n const section = document.getElementById(\"congestionSection\");\r\n const div = document.getElementById(\"congestionControl\");\r\n if (!section || !div) {\r\n return;\r\n }\r\n\r\n section.style.display = \"\";\r\n\r\n const enabled = !!data.enabled;\r\n const manualMode = !!data.manualMode;\r\n const stateLabel = enabled ? (manualMode ? \"manual\" : \"active\") : \"disabled\";\r\n const stateClass = enabled ? (manualMode ? \"warning\" : \"success\") : \"error\";\r\n const modeLabel = manualMode ? \"Manual Override\" : \"Automatic\";\r\n const currentDeltaTimer = data.currentDeltaTimer || 0;\r\n const nominalDeltaTimer = data.nominalDeltaTimer || 0;\r\n\r\n const html = `\r\n <div class=\"v2-dashboard\">\r\n <div class=\"metrics-grid\">\r\n ${renderMetricItemHtml(\"State\", `<span class=\"congestion-state ${stateClass}\">${stateLabel}</span>`)}\r\n ${renderMetricItem(\"Mode\", modeLabel)}\r\n ${renderMetricItem(\"Current Timer\", currentDeltaTimer + \" ms\")}\r\n ${renderMetricItem(\"Nominal Timer\", nominalDeltaTimer + \" ms\")}\r\n </div>\r\n <div class=\"metrics-stats\">\r\n <h5>Congestion Details</h5>\r\n <div class=\"stats-grid\">\r\n ${renderStatItem(\"Min Delta Timer\", (data.minDeltaTimer || 0) + \" ms\")}\r\n ${renderStatItem(\"Max Delta Timer\", (data.maxDeltaTimer || 0) + \" ms\")}\r\n ${renderStatItem(\"Target RTT\", (data.targetRTT || 0) + \" ms\")}\r\n ${renderStatItem(\"Avg RTT\", (data.avgRTT !== undefined ? Math.round(data.avgRTT) : 0) + \" ms\")}\r\n ${renderStatItem(\"Avg Packet Loss\", (data.avgLoss !== undefined ? (data.avgLoss * 100).toFixed(1) : 0) + \"%\", data.avgLoss > 0.05)}\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n\r\n div.innerHTML = html;\r\n }\r\n\r\n updateBondingDisplay(data: Record<string, any>) {\r\n const section = document.getElementById(\"bondingSection\");\r\n const div = document.getElementById(\"bondingStatus\");\r\n if (!section || !div) {\r\n return;\r\n }\r\n\r\n if (!data.enabled) {\r\n section.style.display = \"none\";\r\n return;\r\n }\r\n\r\n section.style.display = \"\";\r\n\r\n const modeLabel = (data.mode || \"main-backup\").replace(/-/g, \" \");\r\n const activeLink = data.activeLink || \"primary\";\r\n\r\n let linksHtml = \"\";\r\n if (data.links) {\r\n const linkEntries = Object.entries(data.links) as Array<[string, Record<string, any>]>;\r\n linksHtml = linkEntries\r\n .map(([name, link]) => {\r\n const isActive = name === activeLink;\r\n const status = (link.status || \"unknown\").toLowerCase();\r\n const isUp = status !== \"down\";\r\n const aliveClass = isUp ? \"success\" : \"error\";\r\n return `\r\n <div class=\"bonding-link ${isActive ? \"active\" : \"\"}\">\r\n <div class=\"link-header\">\r\n <span class=\"link-name\">${this.escapeHtml(name)}</span>\r\n ${isActive ? '<span class=\"link-badge active-badge\">ACTIVE</span>' : \"\"}\r\n <span class=\"link-badge ${aliveClass}\">${this.escapeHtml(status.toUpperCase())}</span>\r\n </div>\r\n <div class=\"link-stats\">\r\n ${renderStatItem(\"RTT\", (link.rtt || 0) + \" ms\")}\r\n ${renderStatItem(\"Packet Loss\", ((link.loss || 0) * 100).toFixed(1) + \"%\")}\r\n </div>\r\n </div>\r\n `;\r\n })\r\n .join(\"\");\r\n }\r\n\r\n const html = `\r\n <div class=\"v2-dashboard\">\r\n <div class=\"metrics-grid\">\r\n ${renderMetricItem(\"Mode\", modeLabel)}\r\n ${renderMetricItem(\"Active Link\", activeLink)}\r\n </div>\r\n <div class=\"bonding-links\">${linksHtml}</div>\r\n <div style=\"margin-top: 1rem;\">\r\n <button id=\"failoverBtn\" class=\"btn btn-secondary\">Force Failover</button>\r\n </div>\r\n </div>\r\n `;\r\n\r\n div.innerHTML = html;\r\n\r\n const failoverBtn = document.getElementById(\"failoverBtn\");\r\n if (failoverBtn) {\r\n failoverBtn.addEventListener(\"click\", () => this.triggerFailover());\r\n }\r\n }\r\n\r\n async triggerFailover() {\r\n const connId = this.activeConnectionId!;\r\n const btn = document.getElementById(\"failoverBtn\") as HTMLButtonElement | null;\r\n if (btn) {\r\n btn.disabled = true;\r\n }\r\n try {\r\n const response = await this.request(this.bondingFailoverPath(connId), { method: \"POST\" });\r\n if (response.ok) {\r\n const result = await response.json();\r\n this.showNotification(`Failover complete. Active link: ${result.activeLink}`, \"success\");\r\n this.loadMetrics(connId);\r\n } else {\r\n const err = await response.json();\r\n this.showNotification(\r\n response.status === 401\r\n ? this.authFailureMessage(\"triggering failover\")\r\n : \"Failover failed: \" + (err.error || \"Unknown error\"),\r\n \"error\"\r\n );\r\n }\r\n } catch (error: unknown) {\r\n const err = error as AuthenticatedError;\r\n this.showNotification(\r\n err.isUnauthorized\r\n ? this.authFailureMessage(\"triggering failover\")\r\n : \"Failover failed: \" + err.message,\r\n \"error\"\r\n );\r\n } finally {\r\n if (btn) {\r\n btn.disabled = false;\r\n }\r\n }\r\n }\r\n\r\n updateMonitoringDisplay(data: Record<string, any>) {\r\n const section = document.getElementById(\"monitoringSection\");\r\n const div = document.getElementById(\"monitoringAlerts\");\r\n if (!section || !div) {\r\n return;\r\n }\r\n\r\n const hasData = data.alerts || data.packetLoss || data.retransmissions;\r\n if (!hasData) {\r\n section.style.display = \"none\";\r\n return;\r\n }\r\n\r\n section.style.display = \"\";\r\n\r\n let html = '<div class=\"v2-dashboard\">';\r\n\r\n if (data.alerts) {\r\n const activeAlerts: Record<string, any> = data.alerts.activeAlerts || {};\r\n const alertEntries = Object.entries(activeAlerts).map(([metric, alert]: [string, any]) => {\r\n let level = \"warning\";\r\n if (typeof alert === \"string\") {\r\n level = alert.toLowerCase();\r\n } else if (alert && typeof alert === \"object\" && alert.level) {\r\n level = String(alert.level).toLowerCase();\r\n }\r\n\r\n if (level === \"warn\") {\r\n level = \"warning\";\r\n }\r\n if (level === \"alert\") {\r\n level = \"critical\";\r\n }\r\n if (level !== \"warning\" && level !== \"critical\") {\r\n level = \"warning\";\r\n }\r\n\r\n return {\r\n metric,\r\n level,\r\n value: alert && typeof alert === \"object\" ? alert.value : undefined\r\n };\r\n });\r\n const alertCount = alertEntries.length;\r\n\r\n const alertItems = alertEntries\r\n .map((entry) => {\r\n const alertValue =\r\n entry.value !== undefined ? ` (${this.escapeHtml(String(entry.value))})` : \"\";\r\n return renderStatItemHtml(\r\n entry.metric,\r\n `<span class=\"alert-level alert-${entry.level}\">${this.escapeHtml(entry.level.toUpperCase())}${alertValue}</span>`,\r\n entry.level === \"critical\"\r\n );\r\n })\r\n .join(\"\");\r\n\r\n const alertsContent =\r\n alertCount === 0\r\n ? '<div class=\"metrics-success\"><div class=\"success-message\">No active alerts</div></div>'\r\n : `<div class=\"stats-grid\">${alertItems}</div>`;\r\n\r\n html += `\r\n <div class=\"monitoring-subsection\">\r\n <h5>Active Alerts</h5>\r\n ${alertsContent}\r\n </div>\r\n `;\r\n }\r\n\r\n if (data.packetLoss && data.packetLoss.summary) {\r\n const summary = data.packetLoss.summary;\r\n html += `\r\n <div class=\"monitoring-subsection\">\r\n <h5>Packet Loss</h5>\r\n <div class=\"stats-grid\">\r\n ${renderStatItem(\"Overall Loss Rate\", (summary.overallLossRate * 100).toFixed(2) + \"%\", summary.overallLossRate > 0.05)}\r\n ${renderStatItem(\"Max Loss Rate\", (summary.maxLossRate * 100).toFixed(2) + \"%\", summary.maxLossRate > 0.1)}\r\n ${renderStatItem(\"Trend\", summary.trend || \"stable\")}\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n if (data.retransmissions && data.retransmissions.summary) {\r\n const summary = data.retransmissions.summary;\r\n html += `\r\n <div class=\"monitoring-subsection\">\r\n <h5>Retransmission Rates</h5>\r\n <div class=\"stats-grid\">\r\n ${renderStatItem(\"Current Rate\", (summary.currentRate * 100).toFixed(2) + \"%\", summary.currentRate > 0.05)}\r\n ${renderStatItem(\"Average Rate\", (summary.avgRate * 100).toFixed(2) + \"%\")}\r\n ${renderStatItem(\"Max Rate\", (summary.maxRate * 100).toFixed(2) + \"%\", summary.maxRate > 0.1)}\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n html += \"</div>\";\r\n div.innerHTML = html;\r\n }\r\n\r\n // ── Status display ─────────────────────────────────────────────────────────\r\n\r\n updateStatus() {\r\n const statusDiv = document.getElementById(\"status\");\r\n if (!statusDiv) {\r\n return;\r\n }\r\n\r\n let statusHtml = \"<h4>Configuration Status</h4>\";\r\n\r\n if (this.deltaTimerConfig) {\r\n statusHtml += `\r\n <div class=\"status-item\">\r\n <strong>Delta Timer:</strong> ${this.escapeHtml(String((this.deltaTimerConfig as Record<string, unknown>).deltaTimer))}ms\r\n <span class=\"status-indicator success\">Configured</span>\r\n </div>\r\n `;\r\n } else {\r\n statusHtml += `\r\n <div class=\"status-item\">\r\n <strong>Delta Timer:</strong>\r\n <span class=\"status-indicator warning\">Not configured</span>\r\n </div>\r\n `;\r\n }\r\n\r\n if (this.subscriptionConfig && (this.subscriptionConfig as Record<string, unknown>).subscribe) {\r\n const cfg = this.subscriptionConfig as Record<string, unknown>;\r\n const pathCount = (cfg.subscribe as unknown[]).length;\r\n const escapedContext = this.escapeHtml((cfg.context as string) || \"\");\r\n const escapedPaths = (cfg.subscribe as Array<{ path: string }>)\r\n .map((s) => this.escapeHtml(s.path))\r\n .join(\", \");\r\n statusHtml += `\r\n <div class=\"status-item\">\r\n <strong>Subscriptions:</strong> ${pathCount} path(s) configured\r\n <span class=\"status-indicator success\">Configured</span>\r\n </div>\r\n <div class=\"status-details\">\r\n <strong>Context:</strong> ${escapedContext}<br>\r\n <strong>Paths:</strong> ${escapedPaths}\r\n </div>\r\n `;\r\n } else {\r\n statusHtml += `\r\n <div class=\"status-item\">\r\n <strong>Subscriptions:</strong>\r\n <span class=\"status-indicator warning\">Not configured</span>\r\n </div>\r\n `;\r\n }\r\n\r\n if (\r\n this.sentenceFilterConfig &&\r\n (this.sentenceFilterConfig as Record<string, unknown>).excludedSentences &&\r\n ((this.sentenceFilterConfig as Record<string, unknown>).excludedSentences as string[])\r\n .length > 0\r\n ) {\r\n const filterCount = (\r\n (this.sentenceFilterConfig as Record<string, unknown>).excludedSentences as string[]\r\n ).length;\r\n const escapedFilters = (\r\n (this.sentenceFilterConfig as Record<string, unknown>).excludedSentences as string[]\r\n )\r\n .map((s) => this.escapeHtml(s))\r\n .join(\", \");\r\n statusHtml += `\r\n <div class=\"status-item\">\r\n <strong>Sentence Filter:</strong> ${filterCount} sentence(s) excluded\r\n <span class=\"status-indicator success\">Configured</span>\r\n </div>\r\n <div class=\"status-details\">\r\n <strong>Excluded:</strong> ${escapedFilters}\r\n </div>\r\n `;\r\n }\r\n\r\n statusDiv.innerHTML = statusHtml;\r\n }\r\n\r\n // ── Utility methods ────────────────────────────────────────────────────────\r\n\r\n buildCompletePluginConfig(currentConfig: Record<string, unknown>): Record<string, unknown> {\r\n const defaults = this.extractSchemaDefaults(this.pluginSchema);\r\n const merged = this.deepMerge(defaults || {}, currentConfig || {}) as Record<string, unknown>;\r\n if (Array.isArray(merged.connections)) {\r\n return merged;\r\n }\r\n const normalizedServerType = this.normalizeServerType(merged.serverType);\r\n merged.serverType = normalizedServerType || \"client\";\r\n return merged;\r\n }\r\n\r\n normalizeServerType(value: unknown): string | undefined {\r\n if (value === true || value === \"server\") {\r\n return \"server\";\r\n }\r\n if (value === false || value === \"client\") {\r\n return \"client\";\r\n }\r\n return undefined;\r\n }\r\n\r\n extractSchemaDefaults(\r\n schemaNode: Record<string, any> | null\r\n ): Record<string, unknown> | undefined {\r\n if (!schemaNode || typeof schemaNode !== \"object\") {\r\n return undefined;\r\n }\r\n\r\n const isObjectNode = schemaNode.type === \"object\" || !!schemaNode.properties;\r\n const merged: Record<string, unknown> = {};\r\n let hasData = false;\r\n\r\n if (isObjectNode && schemaNode.default && this.isPlainObject(schemaNode.default)) {\r\n Object.assign(merged, this.deepClone(schemaNode.default));\r\n hasData = true;\r\n }\r\n\r\n if (schemaNode.properties && this.isPlainObject(schemaNode.properties)) {\r\n for (const [key, value] of Object.entries(schemaNode.properties)) {\r\n const childDefaults = this.extractSchemaDefaults(value as Record<string, any>);\r\n if (childDefaults !== undefined) {\r\n merged[key] = childDefaults;\r\n hasData = true;\r\n } else if (value && (value as Record<string, any>).type === \"object\") {\r\n merged[key] = {};\r\n hasData = true;\r\n } else if (value && (value as Record<string, any>).type === \"string\") {\r\n merged[key] = \"\";\r\n hasData = true;\r\n }\r\n }\r\n }\r\n\r\n if (schemaNode.dependencies && this.isPlainObject(schemaNode.dependencies)) {\r\n for (const dependencyValue of Object.values(schemaNode.dependencies)) {\r\n const dependencyDefaults = this.extractSchemaDefaults(\r\n dependencyValue as Record<string, any>\r\n );\r\n if (dependencyDefaults && this.isPlainObject(dependencyDefaults)) {\r\n Object.assign(merged, this.deepMerge(merged, dependencyDefaults));\r\n hasData = true;\r\n }\r\n }\r\n }\r\n\r\n for (const compositeKey of [\"oneOf\", \"anyOf\", \"allOf\"]) {\r\n const composite = schemaNode[compositeKey];\r\n if (!Array.isArray(composite)) {\r\n continue;\r\n }\r\n\r\n composite.forEach((item: Record<string, any>) => {\r\n const itemDefaults = this.extractSchemaDefaults(item);\r\n if (itemDefaults === undefined) {\r\n return;\r\n }\r\n\r\n if (this.isPlainObject(itemDefaults)) {\r\n Object.assign(merged, this.deepMerge(merged, itemDefaults));\r\n hasData = true;\r\n return;\r\n }\r\n\r\n if (!hasData && schemaNode.default === undefined) {\r\n return;\r\n }\r\n\r\n hasData = true;\r\n });\r\n }\r\n\r\n if (hasData) {\r\n return merged;\r\n }\r\n\r\n if (schemaNode.default !== undefined) {\r\n return this.deepClone(schemaNode.default) as Record<string, unknown>;\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n isPlainObject(value: unknown): value is Record<string, unknown> {\r\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\r\n }\r\n\r\n deepClone(value: unknown): unknown {\r\n if (Array.isArray(value)) {\r\n return value.map((item) => this.deepClone(item));\r\n }\r\n if (this.isPlainObject(value)) {\r\n const clone: Record<string, unknown> = {};\r\n for (const [key, childValue] of Object.entries(value)) {\r\n clone[key] = this.deepClone(childValue);\r\n }\r\n return clone;\r\n }\r\n return value;\r\n }\r\n\r\n deepMerge(baseValue: unknown, overrideValue: unknown): unknown {\r\n if (overrideValue === undefined) {\r\n return this.deepClone(baseValue);\r\n }\r\n if (Array.isArray(overrideValue)) {\r\n return this.deepClone(overrideValue);\r\n }\r\n\r\n if (this.isPlainObject(baseValue) && this.isPlainObject(overrideValue)) {\r\n const merged = this.deepClone(baseValue) as Record<string, unknown>;\r\n for (const [key, value] of Object.entries(overrideValue)) {\r\n merged[key] = this.deepMerge((baseValue as Record<string, unknown>)[key], value);\r\n }\r\n return merged;\r\n }\r\n\r\n return this.deepClone(overrideValue);\r\n }\r\n\r\n formatBytes(bytes: number): string {\r\n if (!bytes || bytes <= 0) {\r\n return \"0 B\";\r\n }\r\n const k = 1024;\r\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\r\n const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);\r\n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + \" \" + sizes[i];\r\n }\r\n\r\n escapeHtml(text: string): string {\r\n const div = document.createElement(\"div\");\r\n div.textContent = text;\r\n return div.innerHTML;\r\n }\r\n\r\n showNotification(message: string, type = \"success\") {\r\n const notification = document.getElementById(\"notification\");\r\n if (!notification) {\r\n return;\r\n }\r\n if (this._notificationTimer) {\r\n clearTimeout(this._notificationTimer);\r\n }\r\n notification.textContent = message;\r\n notification.className = `notification ${type} show`;\r\n\r\n this._notificationTimer = setTimeout(() => {\r\n notification.classList.remove(\"show\");\r\n this._notificationTimer = null;\r\n }, NOTIFICATION_TIMEOUT);\r\n }\r\n}\r\n\r\n// Extend Window interface for the global config instance\r\ndeclare global {\r\n interface Window {\r\n dataConnectorConfig: DataConnectorConfig;\r\n }\r\n}\r\n\r\n// Initialize when DOM is loaded\r\ndocument.addEventListener(\"DOMContentLoaded\", () => {\r\n window.dataConnectorConfig = new DataConnectorConfig();\r\n});\r\n\r\n// Clean up metrics refresh interval when page is hidden or unloaded\r\ndocument.addEventListener(\"visibilitychange\", () => {\r\n if (!window.dataConnectorConfig) {\r\n return;\r\n }\r\n\r\n if (document.hidden) {\r\n if (window.dataConnectorConfig.metricsInterval) {\r\n clearInterval(window.dataConnectorConfig.metricsInterval);\r\n window.dataConnectorConfig.metricsInterval = null;\r\n }\r\n } else if (!window.dataConnectorConfig.metricsInterval) {\r\n window.dataConnectorConfig.loadMetrics();\r\n window.dataConnectorConfig.startMetricsRefresh();\r\n }\r\n});\r\n\r\nwindow.addEventListener(\"beforeunload\", () => {\r\n if (window.dataConnectorConfig && window.dataConnectorConfig.metricsInterval) {\r\n clearInterval(window.dataConnectorConfig.metricsInterval);\r\n window.dataConnectorConfig.metricsInterval = null;\r\n }\r\n});\r\n\r\nexport default DataConnectorConfig;\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n// expose the module cache\n__webpack_require__.c = __webpack_module_cache__;\n\n","__webpack_require__.f = {};\n// This file contains only the entry chunk.\n// The chunk loading function for additional chunks\n__webpack_require__.e = (chunkId) => {\n\treturn Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {\n\t\t__webpack_require__.f[key](chunkId, promises);\n\t\treturn promises;\n\t}, []));\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".\" + \"70a0bc6aadb412703390\" + \".js\";\n};","// This function allow to reference async chunks\n__webpack_require__.miniCssF = (chunkId) => {\n\t// return url for filenames based on template\n\treturn undefined;\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","__webpack_require__.S = {};\nvar initPromises = {};\nvar initTokens = {};\n__webpack_require__.I = (name, initScope) => {\n\tif(!initScope) initScope = [];\n\t// handling circular init calls\n\tvar initToken = initTokens[name];\n\tif(!initToken) initToken = initTokens[name] = {};\n\tif(initScope.indexOf(initToken) >= 0) return;\n\tinitScope.push(initToken);\n\t// only runs once\n\tif(initPromises[name]) return initPromises[name];\n\t// creates a new share scope if needed\n\tif(!__webpack_require__.o(__webpack_require__.S, name)) __webpack_require__.S[name] = {};\n\t// runs all init snippets from all modules reachable\n\tvar scope = __webpack_require__.S[name];\n\tvar warn = (msg) => {\n\t\tif (typeof console !== \"undefined\" && console.warn) console.warn(msg);\n\t};\n\tvar uniqueName = \"signalk-edge-link\";\n\tvar register = (name, version, factory, eager) => {\n\t\tvar versions = scope[name] = scope[name] || {};\n\t\tvar activeVersion = versions[version];\n\t\tif(!activeVersion || (!activeVersion.loaded && (!eager != !activeVersion.eager ? eager : uniqueName > activeVersion.from))) versions[version] = { get: factory, from: uniqueName, eager: !!eager };\n\t};\n\tvar initExternal = (id) => {\n\t\tvar handleError = (err) => (warn(\"Initialization of sharing external failed: \" + err));\n\t\ttry {\n\t\t\tvar module = __webpack_require__(id);\n\t\t\tif(!module) return;\n\t\t\tvar initFn = (module) => (module && module.init && module.init(__webpack_require__.S[name], initScope))\n\t\t\tif(module.then) return promises.push(module.then(initFn, handleError));\n\t\t\tvar initResult = initFn(module);\n\t\t\tif(initResult && initResult.then) return promises.push(initResult['catch'](handleError));\n\t\t} catch(err) { handleError(err); }\n\t}\n\tvar promises = [];\n\tswitch(name) {\n\t\tcase \"default\": {\n\t\t\tregister(\"react\", \"16.14.0\", () => (__webpack_require__.e(540).then(() => (() => (__webpack_require__(6540))))));\n\t\t}\n\t\tbreak;\n\t}\n\tif(!promises.length) return initPromises[name] = 1;\n\treturn initPromises[name] = Promise.all(promises).then(() => (initPromises[name] = 1));\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT')\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/^blob:/, \"\").replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t792: 0\n};\n\n__webpack_require__.f.j = (chunkId, promises) => {\n\t\t// JSONP chunk loading for javascript\n\t\tvar installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;\n\t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n\t\t\t// a Promise means \"currently loading\".\n\t\t\tif(installedChunkData) {\n\t\t\t\tpromises.push(installedChunkData[2]);\n\t\t\t} else {\n\t\t\t\tif(true) { // all chunks have JS\n\t\t\t\t\t// setup Promise in chunk cache\n\t\t\t\t\tvar promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));\n\t\t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n\t\t\t\t\t// start chunk loading\n\t\t\t\t\tvar url = __webpack_require__.p + __webpack_require__.u(chunkId);\n\t\t\t\t\t// create error before stack unwound to get useful stacktrace later\n\t\t\t\t\tvar error = new Error();\n\t\t\t\t\tvar loadingEnded = (event) => {\n\t\t\t\t\t\tif(__webpack_require__.o(installedChunks, chunkId)) {\n\t\t\t\t\t\t\tinstalledChunkData = installedChunks[chunkId];\n\t\t\t\t\t\t\tif(installedChunkData !== 0) installedChunks[chunkId] = undefined;\n\t\t\t\t\t\t\tif(installedChunkData) {\n\t\t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n\t\t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n\t\t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n\t\t\t\t\t\t\t\terror.type = errorType;\n\t\t\t\t\t\t\t\terror.request = realSrc;\n\t\t\t\t\t\t\t\tinstalledChunkData[1](error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t__webpack_require__.l(url, loadingEnded, \"chunk-\" + chunkId, chunkId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n};\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar [chunkIds, moreModules, runtime] = data;\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunksignalk_edge_link\"] = self[\"webpackChunksignalk_edge_link\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// module cache are used so entry inlining is disabled\n// startup\n// Load entry module and return exports\nvar __webpack_exports__ = __webpack_require__(9217);\n"],"names":["inProgress","dataWebpackPrefix","MANAGEMENT_TOKEN_ERROR_MESSAGE","DEFAULT_AUTH_CONFIG","token","localStorageKey","queryParam","includeTokenInQuery","headerMode","readRuntimeAuthConfig","window","runtime","__EDGE_LINK_AUTH__","API_BASE_PATH","escapeHtml","str","String","replace","renderCard","title","subtitle","contentId","contentClass","toLowerCase","renderStatItem","label","value","hasError","renderMetricItem","statusClass","renderMetricItemHtml","htmlValue","renderBwStat","isHighlight","isSuccess","renderSectionGroup","description","content","id","DataConnectorConfig","constructor","this","connections","activeConnectionId","pluginConfig","pluginSchema","schemaCurrentMode","metricsInterval","syncTimeout","tokenHelpText","config","modeText","getTokenHelpText","deltaTimerConfig","subscriptionConfig","sentenceFilterConfig","protocolVersion","isServerMode","_refreshInFlight","_notificationTimer","init","loadPluginConfiguration","fetchConnections","renderPage","startMetricsRefresh","error","console","showNotification","Error","message","res","request","ok","json","_e","length","type","detectModeFromConfig","name","st","normalizeServerType","serverType","getActiveConnection","find","c","isLegacyMode","metricsPath","connId","encodeURIComponent","configPath","filename","monitoringPath","sub","congestionPath","bondingPath","bondingFailoverPath","input","response","trim","tokenFromQuery","URLSearchParams","location","search","get","localStorage","tokenFromStorage","getItem","resolveToken","headers","Headers","normalizedMode","set","attachAuthHeaders","fetch","apiFetch","status","isUnauthorized","authFailureMessage","context","renderTabs","renderConnectionContent","tabsDiv","document","getElementById","innerHTML","style","display","map","icon","statusDot","healthy","readyToSend","join","querySelectorAll","forEach","btn","addEventListener","dataset","connectionId","loadConnectionData","contentDiv","conn","renderServerContent","renderClientContent","setupEventListeners","container","telemetryAndHealth","renderPluginConfigurationCard","configuration","renderDeltaTimerCard","renderSubscriptionCard","renderSentenceFilterCard","updatePluginConfigUI","loadConfigurations","updateUI","updateStatus","loadMetrics","showErrors","configResponse","schemaResponse","Promise","all","configData","Array","isArray","schemaData","schema","currentMode","buildCompletePluginConfig","err","deltaResponse","subResponse","filterResponse","metrics","updateMetricsDisplay","loadV2Data","isClient","fetches","catch","push","results","alertsRes","packetLossRes","retransmissionsRes","congestionRes","bondingRes","monitoringData","alerts","packetLoss","retransmissions","updateMonitoringDisplay","congestionData","updateCongestionDisplay","bondingData","updateBondingDisplay","clearInterval","setInterval","refreshActiveTab","finally","updated","saveDeltaTimerBtn","saveDeltaTimer","saveSubscriptionBtn","saveSubscription","saveSentenceFilterBtn","saveSentenceFilter","addPathBtn","addPathItem","subscriptionJsonEditor","clearTimeout","setTimeout","syncFromJson","contextInput","updateJsonFromForm","savePluginConfigBtn","savePluginConfig","reloadPluginConfigBtn","reloadPluginConfiguration","loadDefaultPluginConfigBtn","loadDefaultPluginConfiguration","deltaTimer","el","cfg","ctxEl","pathsList","subscribe","path","jsonEl","JSON","stringify","excludedSentences","editor","summary","hasConnections","summaryScope","keyLabel","summaryConfig","totalConnections","runtimeIndex","findIndex","summaryIndex","candidate","mode","protocol","Number","keyCount","Object","keys","toUpperCase","pathItem","createElement","className","placeholder","button","textContent","remove","appendChild","contextEl","pathInputs","from","filter","parse","warn","saveConfig","configKey","method","body","parseInt","isNaN","jsonText","split","s","parsedConfig","requestConfig","deepClone","normalizedServerType","result","success","updateNetworkQualityDisplay","updateBandwidthDisplay","updatePathAnalyticsDisplay","metricsDiv","stats","uptime","cryptoErrors","errorCounts","crypto","malformedPackets","hasErrors","udpSendErrors","compressionErrors","encryptionErrors","subscriptionErrors","protocolLabel","metricsGridItems","formatted","deltasBuffered","subErrors","subscriptionErrorStat","metricsHtml","deltasSent","toLocaleString","deltasReceived","udpRetries","duplicatePackets","smartBatching","sb","totalSends","earlySends","timerSends","earlyPercent","Math","round","avgBytesPerDelta","maxDeltasPerBatch","oversizedPackets","recentErrors","errorItems","reverse","timeAgo","Date","now","timestamp","timeStr","floor","category","lastError","timeAgoStr","nqDiv","networkQuality","nq","qualityLabel","qualityColor","undefined","linkQuality","qualityPct","gaugeAngle","radStart","PI","radEnd","x1","cos","y1","sin","x2","y2","gaugeSvg","rttDisplay","rtt","jitterDisplay","jitter","nqHtml","queueDepth","acksSent","naksSent","bandwidthDiv","bandwidth","bw","savedBytes","bytesOutRaw","bytesOut","bytesInRaw","bytesIn","savedFormatted","formatBytes","bandwidthStats","bytesOutFormatted","bytesOutRawFormatted","packetsOut","bytesInFormatted","packetsIn","bandwidthHtml","rateOutFormatted","rateInFormatted","compressionRatio","avgPacketSizeFormatted","renderBandwidthChart","history","maxRate","max","h","rateOut","rateIn","points","i","maxRateFormatted","METRICS_REFRESH_INTERVAL","pathDiv","pathStats","paths","categoryCount","Set","p","size","pathHtml","slice","barWidth","percentage","updatesPerMinute","bytesFormatted","data","section","div","enabled","manualMode","stateLabel","stateClass","modeLabel","currentDeltaTimer","nominalDeltaTimer","html","minDeltaTimer","maxDeltaTimer","targetRTT","avgRTT","avgLoss","toFixed","activeLink","linksHtml","links","entries","link","isActive","aliveClass","loss","failoverBtn","triggerFailover","disabled","activeAlerts","alertEntries","metric","alert","level","alertCount","alertItems","entry","alertValue","renderStatItemHtml","overallLossRate","maxLossRate","trend","currentRate","avgRate","statusDiv","statusHtml","currentConfig","defaults","extractSchemaDefaults","merged","deepMerge","schemaNode","isObjectNode","properties","hasData","default","isPlainObject","assign","key","childDefaults","dependencies","dependencyValue","values","dependencyDefaults","compositeKey","composite","item","itemDefaults","clone","childValue","baseValue","overrideValue","bytes","sizes","min","log","parseFloat","pow","text","notification","classList","dataConnectorConfig","hidden","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","__webpack_modules__","m","f","e","chunkId","reduce","promises","u","miniCssF","g","globalThis","Function","o","obj","prop","prototype","hasOwnProperty","call","l","url","done","script","needAttach","scripts","getElementsByTagName","getAttribute","charset","nc","setAttribute","src","onScriptComplete","prev","event","onerror","onload","timeout","doneFns","parentNode","removeChild","fn","bind","target","head","S","initPromises","initTokens","I","initScope","initToken","indexOf","scope","uniqueName","version","factory","eager","versions","activeVersion","loaded","then","register","scriptUrl","importScripts","currentScript","tagName","test","installedChunks","j","installedChunkData","promise","resolve","reject","errorType","realSrc","webpackJsonpCallback","parentChunkLoadingFunction","chunkIds","moreModules","some","chunkLoadingGlobal","self"],"sourceRoot":""}
|