// Login page: language, theme, local form and/or OIDC button (from /auth/config). (function () { var I18N = { fr: { intro: 'Réplication de configuration Proxmox VE', user: 'Utilisateur', pass: 'Mot de passe', submit: 'Se connecter', sso: 'Se connecter avec le SSO', or: 'ou', error: 'Utilisateur ou mot de passe incorrect.', theme: 'Thème clair / sombre', demo: 'Mode démo : comptes {a}, {o} et {l}, mot de passe {p}.', setupTitle: 'Premier démarrage', setupIntro: 'La base est vide : créez le compte administrateur de pve-confsync (12 caractères minimum).', pass2: 'Confirmation', setupBtn: 'Créer le compte et continuer', mismatch: 'Les deux mots de passe diffèrent.', invalid: 'Nom ou mot de passe refusé (12 caractères minimum).' }, en: { intro: 'Proxmox VE configuration replication', user: 'User', pass: 'Password', submit: 'Sign in', sso: 'Sign in with SSO', or: 'or', error: 'Wrong user or password.', theme: 'Light / dark theme', demo: 'Demo mode: accounts {a}, {o} and {l}, password {p}.', setupTitle: 'First start', setupIntro: 'The database is empty: create the administrator account of pve-confsync (12 characters minimum).', pass2: 'Confirm', setupBtn: 'Create the account and continue', mismatch: 'The two passwords differ.', invalid: 'Name or password refused (12 characters minimum).' }, }; function get(k) { try { return localStorage.getItem('confsync.' + k); } catch (e) { return null; } } function set(k, v) { try { localStorage.setItem('confsync.' + k, v); } catch (e) {} } var lang = get('lang'); if (!I18N[lang]) lang = /^en\b/i.test(navigator.language || '') ? 'en' : 'fr'; var cfg = { local: true, oidc: false, demo: false, setup: false }; function el(id) { return document.getElementById(id); } function code(s) { var c = document.createElement('code'); c.textContent = s; return c; } function applyLang() { var d = I18N[lang]; document.documentElement.lang = lang; document.querySelectorAll('[data-i18n]').forEach(function (e) { e.textContent = d[e.dataset.i18n]; }); document.querySelectorAll('[data-i18n-title]').forEach(function (e) { e.title = d[e.dataset.i18nTitle]; }); document.querySelectorAll('[data-i18n-aria]').forEach(function (e) { e.setAttribute('aria-label', d[e.dataset.i18nAria]); }); document.querySelectorAll('#langSwitch [data-lang]').forEach(function (b) { b.setAttribute('aria-checked', String(b.dataset.lang === lang)); }); var se = q.get('setup'); el('sErr').hidden = !se; if (se) el('sErr').textContent = d[se === 'mismatch' ? 'mismatch' : 'invalid']; var h = el('demoHint'); h.textContent = ''; if (cfg.demo) { // "{a}" placeholders become elements, never HTML from a string d.demo.split(/(\{[aolp]\})/).forEach(function (part) { var v = { '{a}': 'admin', '{o}': 'operateur', '{l}': 'lecteur', '{p}': 'confsync-demo' }[part]; h.appendChild(v ? code(v) : document.createTextNode(part)); }); } } document.querySelectorAll('#langSwitch [data-lang]').forEach(function (b) { b.addEventListener('click', function () { lang = b.dataset.lang; set('lang', lang); applyLang(); }); }); el('themeBtn').addEventListener('click', function () { var root = document.documentElement; var dark = root.dataset.theme ? root.dataset.theme === 'dark' : matchMedia('(prefers-color-scheme: dark)').matches; root.dataset.theme = dark ? 'light' : 'dark'; set('theme', root.dataset.theme); }); var q = new URLSearchParams(location.search); var ret = q.get('r') || '/'; el('ret').value = ret; el('err').hidden = q.get('e') !== '1'; el('ssoBtn').href = 'auth/oidc?r=' + encodeURIComponent(ret); el('setupForm').addEventListener('submit', function (e) { if (el('sPass').value !== el('sPass2').value) { e.preventDefault(); el('sErr').textContent = I18N[lang].mismatch; el('sErr').hidden = false; } }); function show() { // empty database: the first administrator is created here el('setupForm').hidden = !cfg.setup; el('localForm').hidden = !cfg.local || cfg.setup; el('ssoBtn').hidden = !cfg.oidc; el('sep').hidden = !(cfg.local && cfg.oidc); el('submitBtn').className = cfg.oidc ? 'btn' : 'btn primary'; el('demoHint').hidden = !cfg.demo; applyLang(); } show(); fetch('auth/config', { headers: { Accept: 'application/json' } }) .then(function (r) { return r.json(); }) .then(function (c) { cfg = c; show(); }) .catch(function () {}); })();