diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index cb5f41d..f99ac04 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -137,6 +137,7 @@ let _settingsOpen = false; let _serverSettings = null; let _gridViewMode = 'flat'; let _activeFilterDevice = 'all'; +let _activeView = 'all'; const DISPLAY_DEFAULTS = { fontSize: 14, hoverPreviewDelay: 1500, @@ -531,18 +532,45 @@ function buildStatusTileHTML(deviceName, statusText, statusClass) { } /** - * Returns sessions with hidden session names removed. - * Hides any session whose name appears in the hidden_sessions list, - * regardless of whether it is local or remote (federated). - * Consolidates the hidden-session filter used by all render paths. + * Returns sessions filtered by the active view. + * + * - 'all' view: excludes hidden sessions (sessions in hidden_sessions list) + * - 'hidden' view: shows only hidden sessions + * - user view: shows only sessions whose sessionKey is in that view's sessions list + * + * Status entries (unreachable, auth_failed, empty) are always excluded — + * they are rendered separately as status tiles. + * + * Falls back to 'all' behaviour if the active view no longer exists. + * * @param {object[]} sessions * @returns {object[]} */ function getVisibleSessions(sessions) { var hidden = (_serverSettings && _serverSettings.hidden_sessions) || []; + var views = (_serverSettings && _serverSettings.views) || []; + var view = _resolveActiveView(_activeView, views); + return (sessions || []).filter(function(s) { - // Skip status entries (unreachable, auth_failed) — rendered separately as status tiles + // Skip status entries (unreachable, auth_failed, empty) — rendered separately as status tiles if (s.status) return false; + + if (view === 'hidden') { + // 'hidden' view: only show sessions that are in the hidden list + return hidden.length > 0 && (hidden.includes(s.sessionKey || s.name) || hidden.includes(s.name)); + } + + if (view !== 'all') { + // User-defined view: show only sessions whose sessionKey is in this view's sessions list + var userView = views.find(function(v) { return v.name === view; }); + if (userView) { + var viewSessions = userView.sessions || []; + return viewSessions.includes(s.sessionKey || s.name) || viewSessions.includes(s.name); + } + // View no longer exists — fall through to 'all' behaviour + } + + // 'all' view: exclude hidden sessions if (hidden.length > 0 && (hidden.includes(s.sessionKey || s.name) || hidden.includes(s.name))) { return false; } @@ -2499,6 +2527,12 @@ function _setActiveFilterDevice(device) { _activeFilterDevice = device; } +/** Test-only: get current _activeView value. */ +function _getActiveView() { return _activeView; } + +/** Test-only: set _activeView directly. */ +function _setActiveView(view) { _activeView = view; } + // Recalculate fit layout on window resize window.addEventListener('resize', function() { var ds = getDisplaySettings(); @@ -2625,5 +2659,7 @@ if (typeof module !== 'undefined' && module.exports) { _getGridViewMode, _setGridViewMode, _setActiveFilterDevice, + _getActiveView, + _setActiveView, }; } diff --git a/muxplex/tests/test_frontend_js.py b/muxplex/tests/test_frontend_js.py index 50ef6b0..00815e2 100644 --- a/muxplex/tests/test_frontend_js.py +++ b/muxplex/tests/test_frontend_js.py @@ -3010,3 +3010,86 @@ def test_settings_hidden_sessions_uses_session_key() -> None: "(with s.name fallback) as the checkbox value so remote sessions are " "stored in device_id:name format in hidden_sessions" ) + + +# --------------------------------------------------------------------------- +# Active view state variable and getVisibleSessions (task-2-active-view) +# --------------------------------------------------------------------------- + + +def test_active_view_state_variable_exists() -> None: + """_activeView state variable must be declared with default 'all'.""" + assert "let _activeView = 'all'" in _JS, ( + "let _activeView = 'all' must be declared in the App state section of app.js" + ) + + +def test_get_visible_sessions_all_view_excludes_hidden() -> None: + """getVisibleSessions must reference _activeView to honour the active view.""" + match = re.search( + r"function getVisibleSessions\(sessions\)\s*\{(.*?)\n\}", + _JS, + re.DOTALL, + ) + assert match, "getVisibleSessions function not found" + body = match.group(1) + assert "_activeView" in body, ( + "getVisibleSessions must check _activeView to filter sessions by view" + ) + + +def test_get_visible_sessions_hidden_view_shows_hidden() -> None: + """getVisibleSessions must handle the 'hidden' view case.""" + match = re.search( + r"function getVisibleSessions\(sessions\)\s*\{(.*?)\n\}", + _JS, + re.DOTALL, + ) + assert match, "getVisibleSessions function not found" + body = match.group(1) + assert "'hidden'" in body or '"hidden"' in body, ( + "getVisibleSessions must explicitly handle the 'hidden' view (show only hidden sessions)" + ) + + +def test_get_visible_sessions_user_view_filters_by_session_key() -> None: + """getVisibleSessions must use sessionKey when filtering for a user-defined view.""" + match = re.search( + r"function getVisibleSessions\(sessions\)\s*\{(.*?)\n\}", + _JS, + re.DOTALL, + ) + assert match, "getVisibleSessions function not found" + body = match.group(1) + assert "sessionKey" in body, ( + "getVisibleSessions must use sessionKey to match sessions against the user view's " + "sessions list" + ) + + +def test_get_active_view_helper_exported() -> None: + """window.MuxplexApp must export _getActiveView test helper.""" + match = re.search( + r"module\.exports\s*=\s*\{(.*?)\};", + _JS, + re.DOTALL, + ) + assert match, "module.exports block not found" + exports = match.group(1) + assert "_getActiveView" in exports, ( + "module.exports must export _getActiveView test helper" + ) + + +def test_set_active_view_helper_exported() -> None: + """window.MuxplexApp must export _setActiveView test helper.""" + match = re.search( + r"module\.exports\s*=\s*\{(.*?)\};", + _JS, + re.DOTALL, + ) + assert match, "module.exports block not found" + exports = match.group(1) + assert "_setActiveView" in exports, ( + "module.exports must export _setActiveView test helper" + )