feat: add federation proxy for deleting remote sessions

- Add DELETE /api/federation/{remote_id}/sessions/{session_name} endpoint
  to proxy session deletion to remote instances with Bearer auth
  (follows same pattern as existing create/connect/bell-clear proxies)
- Update killSession() to accept optional remoteId parameter and route
  through federation delete proxy when present
- Update delegated click handler to extract data-remote-id from parent
  tile/sidebar element and pass to killSession() for remote routing
- Now users can delete sessions on remote devices from their local client

Fixes: https://github.com/bkrabach/muxplex/issues (delete remote sessions)
Generated with Amplifier
This commit is contained in:
Brian Krabach
2026-04-08 08:21:22 -07:00
parent 67d68d8cf0
commit 12c8f8f550
2 changed files with 58 additions and 3 deletions
+11 -3
View File
@@ -2154,12 +2154,17 @@ async function createNewSession(name, remoteId) {
/** /**
* Kill a tmux session by name via DELETE /api/sessions/{name}. * Kill a tmux session by name via DELETE /api/sessions/{name}.
* For remote sessions, proxies through the federation delete route.
* Shows a confirmation dialog before killing. Refreshes the session list on success. * Shows a confirmation dialog before killing. Refreshes the session list on success.
* @param {string} name - The session name to kill. * @param {string} name - The session name to kill.
* @param {string} [remoteId] - Remote instance index (empty or absent for local).
*/ */
function killSession(name) { function killSession(name, remoteId) {
if (!confirm('Kill session "' + name + '"?')) return; if (!confirm('Kill session "' + name + '"?')) return;
api('DELETE', '/api/sessions/' + name) var endpoint = remoteId
? '/api/federation/' + encodeURIComponent(remoteId) + '/sessions/' + encodeURIComponent(name)
: '/api/sessions/' + encodeURIComponent(name);
api('DELETE', endpoint)
.then(function() { .then(function() {
showToast('Session \'' + name + '\' killed'); showToast('Session \'' + name + '\' killed');
// If we deleted the session we're currently viewing, return to dashboard // If we deleted the session we're currently viewing, return to dashboard
@@ -2184,7 +2189,10 @@ function bindStaticEventListeners() {
if (!deleteBtn) return; if (!deleteBtn) return;
e.stopPropagation(); e.stopPropagation();
var name = deleteBtn.dataset.session; var name = deleteBtn.dataset.session;
if (name) killSession(name); // Walk up to the tile/sidebar-item to get remoteId for federation routing
var container = deleteBtn.closest('[data-remote-id]');
var remoteId = container ? container.dataset.remoteId : '';
if (name) killSession(name, remoteId);
}); });
on($('back-btn'), 'click', closeSession); on($('back-btn'), 'click', closeSession);
+47
View File
@@ -1248,6 +1248,53 @@ async def federation_create_session(
) )
@app.delete("/api/federation/{remote_id}/sessions/{session_name}")
async def federation_delete_session(
remote_id: int, session_name: str, request: Request
) -> dict:
"""Proxy a delete-session DELETE to a remote instance.
Looks up the remote by integer index into ``remote_instances`` in settings,
sends ``DELETE {remote_url}/api/sessions/{session_name}`` with a Bearer auth
header, and returns the remote's JSON response.
Raises HTTP 404 if ``remote_id`` is not a valid integer index,
503 when remote is unreachable, 502 when remote returns HTTP error.
"""
settings = load_settings()
remotes = settings.get("remote_instances", [])
if remote_id < 0 or remote_id >= len(remotes):
raise HTTPException(
status_code=404,
detail=f"Remote instance '{remote_id}' not found",
)
remote = remotes[remote_id]
remote_url: str = remote.get("url", "").rstrip("/")
remote_key: str = remote.get("key", "")
url = f"{remote_url}/api/sessions/{session_name}"
http_client: httpx.AsyncClient = request.app.state.federation_client
try:
resp = await http_client.delete(
url,
headers={"Authorization": f"Bearer {remote_key}"} if remote_key else {},
)
resp.raise_for_status()
return resp.json()
except httpx.HTTPStatusError as exc:
raise HTTPException(
status_code=502,
detail=f"Remote returned {exc.response.status_code}",
)
except Exception as exc:
_log.warning(
"federation_delete_session: remote %s unreachable: %s", remote_url, exc
)
raise HTTPException(
status_code=503,
detail=f"Remote unreachable: {remote_url} ({type(exc).__name__}: {exc})",
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Static file serving — MUST come after all API routes (first-match-wins) # Static file serving — MUST come after all API routes (first-match-wins)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------