From 7ab0384e0b2cfc9a04620b7b175aa17f415b5584 Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 18 Oct 2025 01:45:02 +0300 Subject: [PATCH] fix: correct dynamic page behavior on lifecycle events --- .../src/podkop/services/socket.service.ts | 21 ++++ .../podkop/tabs/dashboard/initController.ts | 72 ++++++++--- .../podkop/tabs/diagnostic/initController.ts | 80 +++++++++--- .../luci-static/resources/view/podkop/main.js | 116 ++++++++++++++---- 4 files changed, 231 insertions(+), 58 deletions(-) diff --git a/fe-app-podkop/src/podkop/services/socket.service.ts b/fe-app-podkop/src/podkop/services/socket.service.ts index 5210155..adb6e13 100644 --- a/fe-app-podkop/src/podkop/services/socket.service.ts +++ b/fe-app-podkop/src/podkop/services/socket.service.ts @@ -18,6 +18,27 @@ class SocketManager { return SocketManager.instance; } + resetAll(): void { + for (const [url, ws] of this.sockets.entries()) { + try { + if ( + ws.readyState === WebSocket.OPEN || + ws.readyState === WebSocket.CONNECTING + ) { + ws.close(); + } + } catch (err) { + console.warn(`resetAll: failed to close socket ${url}`, err); + } + } + + this.sockets.clear(); + this.listeners.clear(); + this.errorListeners.clear(); + this.connected.clear(); + console.info('[SocketManager] All connections and state have been reset.'); + } + connect(url: string): void { if (this.sockets.has(url)) return; diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts index 4734098..04b6af0 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts @@ -38,6 +38,7 @@ async function fetchDashboardSections() { } async function connectToClashSockets() { + console.log('[SOCKET] connectToClashSockets'); socket.subscribe( `${getClashWsUrl()}/traffic?token=`, (msg) => { @@ -388,25 +389,60 @@ async function onStoreUpdate( } } -export async function initController(): Promise { - onMount('dashboard-status').then(() => { - // Remove old listener - store.unsubscribe(onStoreUpdate); - // Clear store - store.reset([ - 'bandwidthWidget', - 'trafficTotalWidget', - 'systemInfoWidget', - 'servicesInfoWidget', - 'sectionsWidget', - ]); +async function onPageMount() { + // Cleanup before mount + onPageUnmount(); - // Add new listener - store.subscribe(onStoreUpdate); + // Add new listener + store.subscribe(onStoreUpdate); - // Initial sections fetch - fetchDashboardSections(); - fetchServicesInfo(); - connectToClashSockets(); + // Initial sections fetch + await fetchDashboardSections(); + await fetchServicesInfo(); + await connectToClashSockets(); +} + +function onPageUnmount() { + // Remove old listener + store.unsubscribe(onStoreUpdate); + // Clear store + store.reset([ + 'bandwidthWidget', + 'trafficTotalWidget', + 'systemInfoWidget', + 'servicesInfoWidget', + 'sectionsWidget', + ]); + socket.resetAll(); +} + +function registerLifecycleListeners() { + store.subscribe((next, prev, diff) => { + if ( + diff.tabService && + next.tabService.current !== prev.tabService.current + ) { + console.log( + new Date().toISOString(), + '[Active Tab on dashboard]', + diff.tabService.current, + ); + const isDashboardVisible = next.tabService.current === 'dashboard'; + + if (isDashboardVisible) { + return onPageMount(); + } + + if (!isDashboardVisible) { + onPageUnmount(); + } + } + }); +} + +export async function initController(): Promise { + onMount('dashboard-status').then(() => { + onPageMount(); + registerLifecycleListeners(); }); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index 70fbc6d..5daa4ea 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -477,31 +477,73 @@ async function runChecks() { } } -export async function initController(): Promise { - onMount('diagnostic-status').then(() => { - console.log('diagnostic controller initialized.'); - // Remove old listener - store.unsubscribe(onStoreUpdate); +function onPageMount() { + console.log('diagnostic controller initialized.'); + // Cleanup before mount + onPageUnmount(); - // Add new listener - store.subscribe(onStoreUpdate); + // Add new listener + store.subscribe(onStoreUpdate); - // Initial checks render - renderDiagnosticsChecks(); + // Initial checks render + renderDiagnosticsChecks(); - // Initial run checks action render - renderDiagnosticRunActionWidget(); + // Initial run checks action render + renderDiagnosticRunActionWidget(); - // Initial available actions render - renderDiagnosticAvailableActionsWidget(); + // Initial available actions render + renderDiagnosticAvailableActionsWidget(); - // Initial system info render - renderDiagnosticSystemInfoWidget(); + // Initial system info render + renderDiagnosticSystemInfoWidget(); - // Initial services info fetch - fetchServicesInfo(); + // Initial services info fetch + fetchServicesInfo(); - // Initial system info fetch - fetchSystemInfo(); + // Initial system info fetch + fetchSystemInfo(); +} + +function onPageUnmount() { + // Remove old listener + store.unsubscribe(onStoreUpdate); + + // Clear store + store.reset([ + 'diagnosticsActions', + 'diagnosticsSystemInfo', + 'diagnosticsChecks', + 'diagnosticsRunAction', + ]); +} + +function registerLifecycleListeners() { + store.subscribe((next, prev, diff) => { + if ( + diff.tabService && + next.tabService.current !== prev.tabService.current + ) { + console.log( + new Date().toISOString(), + '[Active Tab on diagnostics]', + diff.tabService.current, + ); + const isDashboardVisible = next.tabService.current === 'diagnostic'; + + if (isDashboardVisible) { + return onPageMount(); + } + + if (!isDashboardVisible) { + onPageUnmount(); + } + } + }); +} + +export async function initController(): Promise { + onMount('diagnostic-status').then(() => { + onPageMount(); + registerLifecycleListeners(); }); } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 633e57e..4140a1f 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1192,6 +1192,22 @@ var SocketManager = class _SocketManager { } return _SocketManager.instance; } + resetAll() { + for (const [url, ws] of this.sockets.entries()) { + try { + if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) { + ws.close(); + } + } catch (err) { + console.warn(`resetAll: failed to close socket ${url}`, err); + } + } + this.sockets.clear(); + this.listeners.clear(); + this.errorListeners.clear(); + this.connected.clear(); + console.info("[SocketManager] All connections and state have been reset."); + } connect(url) { if (this.sockets.has(url)) return; let ws; @@ -1576,6 +1592,7 @@ async function fetchDashboardSections() { }); } async function connectToClashSockets() { + console.log("[SOCKET] connectToClashSockets"); socket.subscribe( `${getClashWsUrl()}/traffic?token=`, (msg) => { @@ -1866,20 +1883,46 @@ async function onStoreUpdate(next, prev, diff) { renderServicesInfoWidget(); } } +async function onPageMount() { + onPageUnmount(); + store.subscribe(onStoreUpdate); + await fetchDashboardSections(); + await fetchServicesInfo(); + await connectToClashSockets(); +} +function onPageUnmount() { + store.unsubscribe(onStoreUpdate); + store.reset([ + "bandwidthWidget", + "trafficTotalWidget", + "systemInfoWidget", + "servicesInfoWidget", + "sectionsWidget" + ]); + socket.resetAll(); +} +function registerLifecycleListeners() { + store.subscribe((next, prev, diff) => { + if (diff.tabService && next.tabService.current !== prev.tabService.current) { + console.log( + (/* @__PURE__ */ new Date()).toISOString(), + "[Active Tab on dashboard]", + diff.tabService.current + ); + const isDashboardVisible = next.tabService.current === "dashboard"; + if (isDashboardVisible) { + return onPageMount(); + } + if (!isDashboardVisible) { + onPageUnmount(); + } + } + }); +} async function initController() { onMount("dashboard-status").then(() => { - store.unsubscribe(onStoreUpdate); - store.reset([ - "bandwidthWidget", - "trafficTotalWidget", - "systemInfoWidget", - "servicesInfoWidget", - "sectionsWidget" - ]); - store.subscribe(onStoreUpdate); - fetchDashboardSections(); - fetchServicesInfo(); - connectToClashSockets(); + onPageMount(); + registerLifecycleListeners(); }); } @@ -3744,17 +3787,48 @@ async function runChecks() { store.set({ diagnosticsRunAction: { loading: false } }); } } +function onPageMount2() { + console.log("diagnostic controller initialized."); + onPageUnmount2(); + store.subscribe(onStoreUpdate2); + renderDiagnosticsChecks(); + renderDiagnosticRunActionWidget(); + renderDiagnosticAvailableActionsWidget(); + renderDiagnosticSystemInfoWidget(); + fetchServicesInfo(); + fetchSystemInfo(); +} +function onPageUnmount2() { + store.unsubscribe(onStoreUpdate2); + store.reset([ + "diagnosticsActions", + "diagnosticsSystemInfo", + "diagnosticsChecks", + "diagnosticsRunAction" + ]); +} +function registerLifecycleListeners2() { + store.subscribe((next, prev, diff) => { + if (diff.tabService && next.tabService.current !== prev.tabService.current) { + console.log( + (/* @__PURE__ */ new Date()).toISOString(), + "[Active Tab on diagnostics]", + diff.tabService.current + ); + const isDashboardVisible = next.tabService.current === "diagnostic"; + if (isDashboardVisible) { + return onPageMount2(); + } + if (!isDashboardVisible) { + onPageUnmount2(); + } + } + }); +} async function initController2() { onMount("diagnostic-status").then(() => { - console.log("diagnostic controller initialized."); - store.unsubscribe(onStoreUpdate2); - store.subscribe(onStoreUpdate2); - renderDiagnosticsChecks(); - renderDiagnosticRunActionWidget(); - renderDiagnosticAvailableActionsWidget(); - renderDiagnosticSystemInfoWidget(); - fetchServicesInfo(); - fetchSystemInfo(); + onPageMount2(); + registerLifecycleListeners2(); }); }