diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index 17e71c7..73289ad 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -1890,6 +1890,85 @@ function _doRemoveFromView() { }); } +/** + * Show inline kill confirmation inside the flyout menu. + * Replaces the "Kill Session" item with "Kill? [Yes] [No]". + * No timeout — stays until click-outside closes the menu. + * On error: "Failed" for 2 seconds then reverts. + * @param {HTMLElement} killItem - The "Kill Session" menu item element + */ +function _doKillSessionInline(killItem) { + var sessionName = _flyoutSessionName; + var remoteId = _flyoutRemoteId; + + // Replace the kill item with confirmation UI + var confirmHtml = + '
' + + 'Kill?' + + '' + + '' + + '
'; + + killItem.outerHTML = confirmHtml; + + // Re-attach handlers on the confirm/cancel buttons + if (!_flyoutMenuEl) return; + + var confirmBtn = _flyoutMenuEl.querySelector('[data-action="confirm-kill"]'); + var cancelBtn = _flyoutMenuEl.querySelector('[data-action="cancel-kill"]'); + + if (confirmBtn) { + confirmBtn.addEventListener('click', function(e) { + e.stopPropagation(); + _executeKill(sessionName, remoteId); + }); + } + + if (cancelBtn) { + cancelBtn.addEventListener('click', function(e) { + e.stopPropagation(); + closeFlyoutMenu(); + }); + } +} + +/** + * Execute the kill session API call from the flyout inline confirmation. + * On success: closes flyout, shows toast, refreshes sessions. + * On error: shows "Failed" for 2s in the confirm area, then reverts. + * @param {string} name + * @param {string} remoteId + */ +function _executeKill(name, remoteId) { + var endpoint = remoteId + ? '/api/federation/' + encodeURIComponent(remoteId) + '/sessions/' + encodeURIComponent(name) + : '/api/sessions/' + encodeURIComponent(name); + + api('DELETE', endpoint) + .then(function() { + closeFlyoutMenu(); + showToast('Session \'' + name + '\' killed'); + if (_viewingSession === name) { + closeSession(); + } + pollSessions(); + }) + .catch(function(err) { + // Show "Failed" for 2 seconds + var confirmDiv = _flyoutMenuEl && _flyoutMenuEl.querySelector('.flyout-menu__confirm'); + if (confirmDiv) { + confirmDiv.innerHTML = 'Failed'; + setTimeout(function() { + // Revert to original kill button if menu is still open + if (_flyoutMenuEl && confirmDiv.parentNode) { + confirmDiv.outerHTML = + ''; + } + }, 2000); + } + }); +} + // ─── Notification permission ──────────────────────────────────────────────── /** @@ -3190,7 +3269,6 @@ async function createNewSession(name, remoteId) { * @param {string} [remoteId] - Remote instance index (empty or absent for local). */ function killSession(name, remoteId) { - if (!confirm('Kill session "' + name + '"?')) return; var endpoint = remoteId ? '/api/federation/' + encodeURIComponent(remoteId) + '/sessions/' + encodeURIComponent(name) : '/api/sessions/' + encodeURIComponent(name); diff --git a/muxplex/tests/test_frontend_js.py b/muxplex/tests/test_frontend_js.py index f0ce336..22921ab 100644 --- a/muxplex/tests/test_frontend_js.py +++ b/muxplex/tests/test_frontend_js.py @@ -3856,3 +3856,29 @@ def test_hide_session_removes_from_all_views() -> None: assert "views" in fn_body, ( "_doHideSession must remove session from all views (mutual exclusion)" ) + + +# ─── Task 7: Kill session inline confirmation ───────────────────────────────── + + +def test_do_kill_session_inline_function_exists() -> None: + """app.js must define a _doKillSessionInline function.""" + assert "function _doKillSessionInline" in _JS, ( + "app.js must contain a _doKillSessionInline function" + ) + + +def test_kill_session_no_confirm_dialog() -> None: + """killSession must NOT use window.confirm() (replaced by inline confirmation).""" + fn_body = _JS.split("function killSession")[1].split("\nfunction ")[0] + assert "confirm(" not in fn_body, ( + "killSession must not use confirm() — replaced by inline flyout confirmation" + ) + + +def test_do_kill_inline_shows_confirmation_buttons() -> None: + """_doKillSessionInline must render Yes/No confirmation buttons.""" + fn_body = _JS.split("function _doKillSessionInline")[1].split("\nfunction ")[0] + assert "Yes" in fn_body and "No" in fn_body, ( + "_doKillSessionInline must show 'Kill? [Yes] [No]' inline" + )