bmc_hub/static/js/remote-hub.js
Christian 85e019783b Add comprehensive test coverage for case creation, assignment, and remote recovery
- Introduced tests for case creation commands, ensuring proper handling of UUIDs and payloads.
- Added tests for v5 assignment functionality, validating employee identification and group assignment.
- Implemented tests for remote recovery scenarios, including handling network failures and response validation.
- Created tests for hardware label generation and validation, ensuring proper URL formatting and QR code generation.
- Developed extensive tests for remote control functionality, covering connection isolation, command execution, and user permissions.
- Added tests for workflow timer registration, ensuring correct handling of timer states and ownership validation.
2026-10-15 12:17:09 +02:00

71 lines
4.3 KiB
JavaScript

// Loaded by normal Hub pages; dormant when the remote feature is disabled.
(async () => {
if (/^\/sag\/\d+\/v5\/?$/.test(location.pathname)) return;
const {api, bootstrap, requestId} = await import('/static/js/remote-api.js');
try { await bootstrap(); } catch (_) { return; }
const panel = document.createElement('div');
panel.style.cssText = 'position:fixed;right:12px;top:74px;z-index:1025;background:var(--bg-card,#fff);border:1px solid #ccd6df;border-radius:8px;padding:6px;display:flex;gap:8px;align-items:center;font-size:12px';
const link = document.createElement('a'); link.href = '/remote/manage'; link.textContent = 'Fjernbetjening';
const button = document.createElement('button'); button.className = 'btn btn-sm btn-outline-secondary'; button.textContent = 'Følg i denne fane';
panel.append(link, button); document.body.append(panel);
const previousTabId = sessionStorage.getItem('hubRemoteTab');
const tabId = requestId();
let following = sessionStorage.getItem('hubRemoteFollow') === 'yes';
// Browsers copy sessionStorage when duplicating a tab. Detect a living original
// so a duplicate cannot silently take over its remote connection.
if (typeof BroadcastChannel !== 'undefined') {
const channel = new BroadcastChannel('hub-remote-tabs');
let duplicate = false;
channel.onmessage = ({data}) => {
if (data.type === 'probe' && data.previous === tabId) channel.postMessage({type:'occupied',target:data.target});
if (data.type === 'occupied' && data.target === tabId) duplicate = true;
};
if (previousTabId) {
channel.postMessage({type:'probe',previous:previousTabId,target:tabId});
await new Promise(resolve => setTimeout(resolve, 150));
if (duplicate) { following = false; sessionStorage.setItem('hubRemoteFollow', 'no'); }
}
window.addEventListener('pagehide', () => channel.close(), {once:true});
}
sessionStorage.setItem('hubRemoteTab', tabId);
let sequence = 0, pending = false;
let dirty = false;
document.addEventListener('input', event => { if (event.isTrusted && !panel.contains(event.target)) dirty = true; });
document.addEventListener('change', event => { if (event.isTrusted && !panel.contains(event.target)) dirty = true; });
const label = () => { button.textContent = following ? 'Stop med at følge' : 'Følg i denne fane'; };
async function enable() {
const result = await api('/follow', {tab_id: tabId, enable: true});
following = result.following; sequence = result.sequence; sessionStorage.setItem('hubRemoteFollow', following ? 'yes' : 'no'); label();
}
button.onclick = async () => {
if (pending) return;
pending = true; button.disabled = true;
try {
if (following) { await api('/follow', {tab_id: tabId, disable: true}); following = false; sessionStorage.setItem('hubRemoteFollow', 'no'); label(); }
else await enable();
} catch (error) { button.title = error.message; }
finally { pending = false; button.disabled = false; }
};
async function poll() {
if (!following || pending || document.hidden) return;
pending = true;
try {
const result = await api('/follow', {tab_id: tabId, after: sequence});
following = result.following; sequence = result.sequence; label();
if (!following) sessionStorage.setItem('hubRemoteFollow', 'no');
if (result.selection) {
const path = `/sag/${result.selection.case_id}`;
if (!location.pathname.match(new RegExp(`^${path}(/|$)`))) {
// Keep an in-progress user edit visible before leaving the page.
const editing = dirty || ['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement?.tagName);
if (!editing || confirm('Fjernbetjeningen har valgt en anden sag. Skift nu?')) location.assign(path);
}
}
} catch (error) { button.title = 'Forbindelse afbrudt · ' + error.message; }
finally { pending = false; }
}
if (following) { try { await enable(); } catch (_) { following = false; } }
label(); setInterval(poll, 3000);
document.addEventListener('visibilitychange', () => { if (!document.hidden) poll(); });
})();