About API Keys
@@ -1811,7 +1844,15 @@
Did you know?
window.addEventListener('DOMContentLoaded', initAdmin);
} else if (window._pendingAdminInit) {
window._pendingAdminInit = false;
- initAdmin();
+ // Defer: on SPA navigation router.js rewrites top-level `function`
+ // declarations (e.g. showTab, further down this same script) into
+ // plain `window.x = function` assignments, which - unlike real
+ // function declarations - only exist once that line actually runs.
+ // Calling initAdmin() synchronously here, before the script has
+ // finished executing top to bottom, throws "showTab is not
+ // defined". setTimeout pushes the call to a macrotask, after the
+ // whole script (and all those assignments) has finished.
+ setTimeout(initAdmin, 0);
}
function getLogLevelColor(level) {
@@ -1875,6 +1916,70 @@
Did you know?
else if (tab === 'apikeys') loadApiKeys();
else if (tab === 'system') loadSystemInfo();
else if (tab === 'notifications' && typeof window.loadNotificationSettings === 'function') loadNotificationSettings();
+ else if (tab === 'interpreters') loadInterpreters();
+ }
+
+ async function loadInterpreters() {
+ const list = document.getElementById('interpretersList');
+ const spinner = document.getElementById('interpreters-refresh-spinner');
+ spinner.classList.remove('hidden');
+
+ try {
+ const resp = await fetch(`${API_BASE}/api/profiles/interpreters`, { headers: getAuthHeaders() });
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
+ const interpreters = await resp.json();
+ renderInterpretersList(interpreters);
+ } catch (e) {
+ list.innerHTML = `
Failed to load interpreters: ${e.message}
`;
+ } finally {
+ spinner.classList.add('hidden');
+ queueLucideRender();
+ }
+ }
+
+ function interpreterRowHtml(i) {
+ return `
+
+
+
+
+
${escapeHtml(i.displayName)} ${escapeHtml(i.name)}
+
+ ${i.available ? (i.output ? escapeHtml(i.output) : 'Available') : (i.error || 'Not available on this host')}
+
+
+
+
+
`;
+ }
+
+ function renderInterpretersList(interpreters) {
+ document.getElementById('interpretersList').innerHTML = interpreters.map(interpreterRowHtml).join('');
+ queueLucideRender();
+ }
+
+ async function testInterpreter(name) {
+ try {
+ const resp = await fetch(`${API_BASE}/api/profiles/interpreters/${name}/test`, {
+ method: 'POST',
+ headers: getAuthHeaders()
+ });
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
+ const status = await resp.json();
+
+ const existingRow = document.getElementById(`interpreter-row-${name}`);
+ if (existingRow) {
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = interpreterRowHtml(status).trim();
+ existingRow.replaceWith(wrapper.firstElementChild);
+ queueLucideRender();
+ }
+ showToast(`${status.displayName}: ${status.available ? 'available' : 'not available'}`, status.available ? 'success' : 'error');
+ } catch (e) {
+ showToast(`Test failed: ${e.message}`, 'error');
+ }
}
/**
@@ -2553,9 +2658,13 @@
Did you know?
document.getElementById('system-uptime').textContent = data.uptime.days + ' days';
}
- // Start real-time clock
+ // Start real-time clock. Clear any previous interval first -
+ // loadSystemInfo() runs every time the System Info tab is
+ // reselected, and without this, switching tabs back and forth
+ // stacked one interval per visit.
+ if (window.systemTimeInterval) clearInterval(window.systemTimeInterval);
updateServerTime();
- setInterval(updateServerTime, 1000);
+ window.systemTimeInterval = setInterval(updateServerTime, 1000);
} catch (error) {
console.error('Error loading system info:', error);
@@ -2563,19 +2672,25 @@
Did you know?
}
function updateServerTime() {
+ const timeEl = document.getElementById('system-time');
+ const dateEl = document.getElementById('system-date');
+ if (!timeEl || !dateEl) {
+ // Tab content no longer in the DOM (navigated away) - stop polling.
+ if (window.systemTimeInterval) clearInterval(window.systemTimeInterval);
+ return;
+ }
+
const now = new Date();
-
+
// Format time as HH:MM:SS
- const timeStr = now.toUTCString().split(' ')[4];
- document.getElementById('system-time').textContent = timeStr;
-
+ timeEl.textContent = now.toUTCString().split(' ')[4];
+
// Format date more compactly
- const dateStr = now.toLocaleDateString('nl-NL', {
- year: 'numeric',
- month: 'long',
- day: 'numeric'
+ dateEl.textContent = now.toLocaleDateString('nl-NL', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
});
- document.getElementById('system-date').textContent = dateStr;
}
window.onload = async function() {
diff --git a/Source/Reef/Views/connections.html b/Source/Reef/Views/connections.html
index c488687..e6d5093 100644
--- a/Source/Reef/Views/connections.html
+++ b/Source/Reef/Views/connections.html
@@ -43,7 +43,7 @@
Connections