import { S } from './state.js';
// Global error trap — logs JS errors to console without blocking the page
window.onerror = function(msg, src, line, col, err) {
console.error('JS Error [' + (src||'').split('/').pop() + ':' + line + '] ' + msg, err);
return false;
};
window.addEventListener('unhandledrejection', function(e) {
console.error('Unhandled promise rejection:', e.reason);
});
// ── Theme ────────────────────────────────────────────────────────────────────
function openAbout() {
document.getElementById('aboutBackdrop').classList.add('open');
fetch('/api/about').then(r => r.json()).then(d => {
document.getElementById('about-python').textContent = d.python || '—';
document.getElementById('about-msal').textContent = d.msal || '—';
document.getElementById('about-requests').textContent = d.requests || '—';
document.getElementById('about-openpyxl').textContent = d.openpyxl || '—';
}).catch(() => {});
}
function closeAbout() {
document.getElementById('aboutBackdrop').classList.remove('open');
}
// ── Mode info modal ───────────────────────────────────────────────────────────
function openModeInfo() {
const isApp = S._currentAppMode === true;
const title = document.getElementById('modeInfoTitle');
const sub = document.getElementById('modeInfoSubtitle');
const rows = document.getElementById('modeInfoRows');
if (isApp) {
title.textContent = t('m365_mode_app', '🔑 App mode — org-wide');
sub.textContent = t('m365_auth_mode_app_short', 'Application permissions · client credentials');
rows.innerHTML = `
${t('m365_info_permissions','Permissions')}Application
${t('m365_info_signin','Sign-in required')}${t('m365_info_no','No')}
${t('m365_info_scope','Scope')}${t('m365_info_scope_org','All users in tenant')}
${t('m365_info_consent','Admin consent')}${t('m365_info_required','Required')}
${t('m365_info_app_desc','The app authenticates with a Client Secret and accesses all users\' data directly via Microsoft Graph — no interactive sign-in needed. Ideal for automated or scheduled scans.')}
`;
} else {
title.textContent = t('m365_mode_delegated', '👤 Delegated');
sub.textContent = t('m365_auth_mode_delegated_short', 'Delegated permissions · device code flow');
rows.innerHTML = `
${t('m365_info_permissions','Permissions')}Delegated
${t('m365_info_signin','Sign-in required')}${t('m365_info_yes','Yes')}
${t('m365_info_scope','Scope')}${t('m365_info_scope_user','Signed-in user only')}
${t('m365_info_admin','Global Admin')}${t('m365_info_expands_scope','Expands scope to all users')}
${t('m365_info_delegated_desc','The app acts on behalf of the signed-in user via the device code flow. By default only that user\'s data is accessible. A Global Admin can grant broader consent to scan all users.')}
`;
}
document.getElementById('modeInfoBackdrop').classList.add('open');
}
function closeModeInfo() {
document.getElementById('modeInfoBackdrop').classList.remove('open');
}
function toggleTheme() {
const t = document.body.dataset.theme === 'dark' ? 'light' : 'dark';
document.body.dataset.theme = t;
document.getElementById('themeBtn').textContent = t === 'dark' ? '🌙' : '☀️';
try { localStorage.setItem('m365_theme', t); } catch(e) {}
}
(function() {
try {
const t = localStorage.getItem('m365_theme');
if (t) {
document.body.dataset.theme = t;
const btn = document.getElementById('themeBtn');
if (btn) btn.textContent = t === 'dark' ? '🌙' : '☀️';
}
} catch(e) {}
})();
// ── Language selector ─────────────────────────────────────────────────────────
fetch('/api/langs').then(r => r.json()).then(d => {
const sel = document.getElementById('langSelect');
if (!sel || !d.langs || d.langs.length < 2) {
if (sel) sel.style.display = 'none';
return;
}
d.langs.forEach(l => {
const opt = document.createElement('option');
opt.value = l.code;
opt.textContent = l.name;
if (l.code === d.current) opt.selected = true;
sel.appendChild(opt);
});
}).catch(() => {
const sel = document.getElementById('langSelect');
if (sel) sel.style.display = 'none';
});
async function setLang(code) {
const r = await fetch('/api/set_lang', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({lang: code})
});
const d = await r.json();
if (d.translations) {
// Update the in-memory LANG dict and re-apply all translations in place.
// This keeps all scan results, cards, and state intact.
Object.assign(LANG, d.translations);
applyI18n();
// Re-render the grid so card text (source badges etc.) picks up new strings
if (S.flaggedData.length) renderGrid(S.filteredData.length ? S.filteredData : S.flaggedData);
}
}
// ── Window exports (HTML handlers + cross-module calls) ─────────────────────
window.openAbout = openAbout;
window.closeAbout = closeAbout;
window.openModeInfo = openModeInfo;
window.closeModeInfo = closeModeInfo;
window.toggleTheme = toggleTheme;
window.setLang = setLang;