From d9c9d8ee8eb5a7076db75792a885f5fd7ff9394a Mon Sep 17 00:00:00 2001 From: MiTHRAL Date: Tue, 26 May 2026 18:00:52 -0400 Subject: [PATCH] Add End All Sessions button to dashboard and API --- archive_bot/dashboard.py | 13 +++++++++++++ archive_bot/watchparty.py | 38 ++++++++++++++++++++++++++++++++++++++ dashboard.html | 17 +++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/archive_bot/dashboard.py b/archive_bot/dashboard.py index 960df4e..41e3c4f 100644 --- a/archive_bot/dashboard.py +++ b/archive_bot/dashboard.py @@ -41,6 +41,7 @@ from .watchparty import ( add_watch_party_queue_item, control_watch_party_worker, create_watch_party_session, + end_all_watch_party_sessions, find_watch_party_session, first_queued_entry, get_watch_party_session, @@ -251,6 +252,9 @@ def make_dashboard_handler(runtime: BotRuntime, auth: DashboardAuth | None) -> t if path == "/api/watchparty/panel/publish": self.handle_watchparty_panel_publish() return + if path == "/api/watchparty/end-all": + self.handle_watchparty_end_all() + return self.send_error(HTTPStatus.NOT_FOUND) def require_auth(self, require_csrf: bool = False) -> tuple[str, DashboardSession] | None: @@ -575,6 +579,15 @@ def make_dashboard_handler(runtime: BotRuntime, auth: DashboardAuth | None) -> t self.send_json(HTTPStatus.OK, result) + def handle_watchparty_end_all(self) -> None: + try: + result = end_all_watch_party_sessions() + except Exception as exc: + self.send_json(HTTPStatus.BAD_REQUEST, {"error": str(exc)}) + return + + self.send_json(HTTPStatus.OK, result) + def handle_watchparty_queue(self) -> None: try: data = self.read_json() diff --git a/archive_bot/watchparty.py b/archive_bot/watchparty.py index 1a81c8d..eecaec8 100644 --- a/archive_bot/watchparty.py +++ b/archive_bot/watchparty.py @@ -703,3 +703,41 @@ def end_watch_party_session(session_id: int) -> dict[str, Any]: ) return result + +def end_all_watch_party_sessions() -> dict[str, Any]: + from .worker_client import worker_control, worker_enabled + + initialize_watchparty_schema() + with connect_db() as connection: + rows = connection.execute( + "SELECT id FROM watch_party_sessions WHERE status != 'stopped'" + ).fetchall() + session_ids = [int(row["id"]) for row in rows] + + for session_id in session_ids: + if worker_enabled(): + try: + worker_control(session_id=session_id, action="stop") + except Exception as exc: + print(f"Failed to notify worker of stop during session end_all: {exc}", flush=True) + + with connect_db() as connection: + connection.execute( + "UPDATE watch_party_sessions SET status = 'stopped', updated_at = ? WHERE id = ?", + (utc_now(), session_id), + ) + connection.execute( + """ + INSERT OR REPLACE INTO watch_party_worker_state( + session_id, worker_status, playback_state, current_title, position_seconds, + duration_seconds, last_error, updated_at + ) + VALUES (?, 'idle', 'idle', '', 0, 0, '', ?) + """, + (session_id, utc_now()), + ) + log_watch_party_event(connection, session_id, "session.status", {"status": "stopped"}) + connection.commit() + + return {"ok": True, "endedCount": len(session_ids)} + diff --git a/dashboard.html b/dashboard.html index 3fedb61..6cac040 100644 --- a/dashboard.html +++ b/dashboard.html @@ -804,6 +804,7 @@
+
@@ -1567,6 +1568,16 @@ return payload; } + async function endAllWatchSessions() { + const payload = await api("/api/watchparty/end-all", { + method: "POST", + body: "{}" + }); + setWatchMessage(`Ended all watch-party sessions (${payload.endedCount} ended).`); + await loadWatchSessions(); + return payload; + } + function normalizeMediaItem(item = {}, mediaType = "movie") { return { title: item.title || "", @@ -1859,6 +1870,12 @@ publishWatchPanel().catch((error) => setWatchMessage(error.message, true)); }); + document.querySelector("#watchEndAll").addEventListener("click", () => { + if (confirm("Are you sure you want to end all watch party sessions? This will disconnect any active workers.")) { + endAllWatchSessions().catch((error) => setWatchMessage(error.message, true)); + } + }); + document.querySelector("#watchSearchRun").addEventListener("click", () => { searchWatchMedia().catch((error) => setWatchSearchMessage(error.message, true)); });