Files

110 lines
4.9 KiB
JavaScript

'use strict';
const $ = id => document.getElementById(id);
let adminKey = sessionStorage.getItem('captionAdminKey') || '';
const formatTime = seconds => seconds ? new Date(seconds * 1000).toLocaleString() : '';
function setStatus(id, state, text) {
document.getElementById(id + 'Status').textContent = text;
window.CaptionStatus?.set(id + 'Dot', state, text);
}
function statusCell(active) {
const td = document.createElement('td');
const dot = document.createElement('span');
dot.className = 'cc-dot'; dot.dataset.state = active ? 'good' : 'danger';
dot.setAttribute('aria-hidden', 'true');
td.append(dot, document.createTextNode(active ? ' Active' : ' Closed'));
return td;
}
async function checkService() {
try {
const response = await fetch('/health', {credentials: 'same-origin', signal: AbortSignal.timeout(10000)});
if (!response.ok) throw new Error();
const info = await response.json();
setStatus('service', info.ready ? 'good' : 'warn', info.ready ? 'Speech service ready' : 'Speech service starting');
} catch (_) {
setStatus('service', 'danger', 'Speech service unavailable');
}
}
async function api(path, options = {}) {
const headers = new Headers(options.headers || {});
if (adminKey) headers.set('Authorization', 'Bearer ' + adminKey);
if (options.body) headers.set('Content-Type', 'application/json');
const response = await fetch(path, {...options, headers, credentials: 'same-origin', signal: AbortSignal.timeout(10000)});
let data = {};
try { data = await response.json(); } catch (_) {}
if (response.status === 401) throw new Error('The admin key was rejected.');
if (!response.ok) throw new Error(data.detail || `Request failed (HTTP ${response.status}).`);
return data;
}
function cell(text) { const td = document.createElement('td'); td.textContent = text; return td; }
function actionCell(label, onclick) {
const td = document.createElement('td'); const button = document.createElement('button');
button.type = 'button'; button.textContent = label; button.onclick = onclick; td.append(button); return td;
}
function showLink(label, href) {
$('roomResult').replaceChildren(document.createTextNode(label + ' '));
const anchor = document.createElement('a'); anchor.href = href; anchor.textContent = href;
anchor.target = '_blank'; anchor.rel = 'noopener noreferrer';
$('roomResult').append(anchor);
}
async function refresh() {
const data = await api('/api/admin/rooms');
const body = $('roomTable').tBodies[0]; body.replaceChildren();
if (!data.rooms.length) {
const row = body.insertRow(); const empty = cell('No rooms yet.');
empty.colSpan = 6; row.append(empty);
return;
}
for (const room of data.rooms) {
const row = body.insertRow();
row.append(cell(room.public_id), cell(room.label || '—'), cell(formatTime(room.created)),
cell(formatTime(room.last_seen)), statusCell(!room.revoked));
if (room.revoked) {
row.append(cell(''));
} else {
const actions = document.createElement('td');
const rotate = document.createElement('button');
rotate.type = 'button'; rotate.textContent = 'New admin link';
rotate.onclick = async () => {
try {
const result = await api('/api/admin/rooms/' + room.public_id + '/rotate', {method: 'POST'});
showLink('New admin link (copy now):', result.adminUrl);
} catch (error) { $('roomResult').textContent = error.message; }
};
const close = document.createElement('button');
close.type = 'button'; close.textContent = 'Close room';
close.onclick = async () => {
if (!confirm('Close this room? Existing admin, editor and audience links will stop working.')) return;
try {
await api('/api/admin/rooms/' + room.public_id, {method: 'DELETE'});
await refresh();
} catch (error) { $('roomResult').textContent = error.message; }
};
actions.append(rotate, ' ', close); row.append(actions);
}
}
}
async function signIn() {
$('adminError').textContent = '';
setStatus('admin', 'warn', 'Checking admin key…');
try {
await refresh();
$('adminKeyPanel').hidden = true; $('adminActions').hidden = false;
setStatus('admin', 'good', 'Signed in');
} catch (error) {
$('adminError').textContent = error.message; $('adminActions').hidden = true;
setStatus('admin', 'danger', error.message);
}
}
$('adminForm').onsubmit = event => {
event.preventDefault(); adminKey = $('adminKey').value.trim();
sessionStorage.setItem('captionAdminKey', adminKey); $('adminKey').value = ''; signIn();
};
$('refresh').onclick = () => refresh().catch(error => { $('roomResult').textContent = error.message; });
$('signOut').onclick = () => {
adminKey = ''; sessionStorage.removeItem('captionAdminKey');
$('adminActions').hidden = true; $('adminKeyPanel').hidden = false;
setStatus('admin', 'idle', 'Not signed in');
};
checkService(); setInterval(checkService, 30000);
if (adminKey) signIn();