(name)
| 864 | const realSub = onDiag[sub] ? 'diagnostics' : sub; |
| 865 | const anchor = onDiag[sub]; |
| 866 | // Let the network tab finish mounting before selecting a subtab. |
| 867 | setTimeout(() => { |
| 868 | try { showNetworkSubtab(realSub); } catch (e) {} |
| 869 | if (anchor) { |
| 870 | setTimeout(() => { |
| 871 | const el = document.getElementById(anchor); |
| 872 | if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
| 873 | }, 120); |
| 874 | } |
| 875 | }, 60); |
| 876 | } |
| 877 | return true; |
| 878 | } catch (e) { |
| 879 | return false; |
| 880 | } |
| 881 | } |
| 882 | |
| 883 | // Re-route when the hash changes in an already-open tab (e.g. a second launch |
| 884 | // click into the same unit's window). |
| 885 | window.addEventListener('hashchange', () => { routeFromHash(); }); |
| 886 | |
| 887 | // ── RuSense (native RuView WiFi-CSI console, Shadow-DOM island) ───────────── |
| 888 | // The RuView SPA is served from /web/rusense/ and mounted into a shadow root |
| 889 | // under #rusense-host, so its compiled Tailwind never collides with Ragnar's |
| 890 | // styles. Sensing data is proxied by this Flask app (/ws/sensing, /api/v1/*). |
| 891 | const RUSENSE_SUBTABS = ['dashboard', 'observatory', 'sensing', 'nodes', 'training', 'settings', 'about']; |
| 892 | let _rusenseLoader = null; // resolved loader module |
| 893 | let _rusenseLoading = null; // in-flight import() promise |
| 894 | let _rusenseCurrent = 'dashboard'; |
| 895 | |
| 896 | function _setRusenseActive(name) { |
| 897 | RUSENSE_SUBTABS.forEach(k => |
| 898 | _setSubtabActive(document.getElementById('rusense-subtab-' + k), k === name)); |
| 899 | } |
| 900 | |
| 901 | function loadRusenseLoader() { |
no test coverage detected