fix: correct dynamic page behavior on lifecycle events

This commit is contained in:
divocat
2025-10-18 01:45:02 +03:00
parent 4d4164ae6f
commit 7ab0384e0b
4 changed files with 231 additions and 58 deletions

View File

@@ -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;

View File

@@ -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<void> {
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<void> {
onMount('dashboard-status').then(() => {
onPageMount();
registerLifecycleListeners();
});
}

View File

@@ -477,31 +477,73 @@ async function runChecks() {
}
}
export async function initController(): Promise<void> {
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<void> {
onMount('diagnostic-status').then(() => {
onPageMount();
registerLifecycleListeners();
});
}

View File

@@ -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();
});
}