110 lines
4.9 KiB
JavaScript
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();
|