Add End All Sessions button to dashboard and API

This commit is contained in:
MiTHRAL 2026-05-26 18:00:52 -04:00
parent 56a4be7ea6
commit d9c9d8ee8e
3 changed files with 68 additions and 0 deletions

View file

@ -41,6 +41,7 @@ from .watchparty import (
add_watch_party_queue_item, add_watch_party_queue_item,
control_watch_party_worker, control_watch_party_worker,
create_watch_party_session, create_watch_party_session,
end_all_watch_party_sessions,
find_watch_party_session, find_watch_party_session,
first_queued_entry, first_queued_entry,
get_watch_party_session, get_watch_party_session,
@ -251,6 +252,9 @@ def make_dashboard_handler(runtime: BotRuntime, auth: DashboardAuth | None) -> t
if path == "/api/watchparty/panel/publish": if path == "/api/watchparty/panel/publish":
self.handle_watchparty_panel_publish() self.handle_watchparty_panel_publish()
return return
if path == "/api/watchparty/end-all":
self.handle_watchparty_end_all()
return
self.send_error(HTTPStatus.NOT_FOUND) self.send_error(HTTPStatus.NOT_FOUND)
def require_auth(self, require_csrf: bool = False) -> tuple[str, DashboardSession] | None: 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) 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: def handle_watchparty_queue(self) -> None:
try: try:
data = self.read_json() data = self.read_json()

View file

@ -703,3 +703,41 @@ def end_watch_party_session(session_id: int) -> dict[str, Any]:
) )
return result 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)}

View file

@ -804,6 +804,7 @@
<div class="actions"> <div class="actions">
<button id="watchRefresh" type="button">Refresh</button> <button id="watchRefresh" type="button">Refresh</button>
<button id="watchPublishPanel" type="button">Publish Control Panel</button> <button id="watchPublishPanel" type="button">Publish Control Panel</button>
<button id="watchEndAll" class="danger" type="button">End All Sessions</button>
<button id="watchCreate" class="primary" type="button">Create session</button> <button id="watchCreate" class="primary" type="button">Create session</button>
</div> </div>
</div> </div>
@ -1567,6 +1568,16 @@
return payload; 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") { function normalizeMediaItem(item = {}, mediaType = "movie") {
return { return {
title: item.title || "", title: item.title || "",
@ -1859,6 +1870,12 @@
publishWatchPanel().catch((error) => setWatchMessage(error.message, true)); 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", () => { document.querySelector("#watchSearchRun").addEventListener("click", () => {
searchWatchMedia().catch((error) => setWatchSearchMessage(error.message, true)); searchWatchMedia().catch((error) => setWatchSearchMessage(error.message, true));
}); });