// The PIN lock: in a new browser tab the panel asks for the admin's PIN
// before showing anything. The tab remembers it was unlocked (sessionStorage
// lives as long as the tab, through reloads, and a new tab starts empty), so
// a reload does not ask again. Loaded in
, before the page shows.
(function () {
'use strict';
var KEY = 'lpra.pin.ok';
var ok = false;
try { ok = sessionStorage.getItem(KEY) === '1'; } catch (e) { /* no storage: ask */ }
if (ok) return;
var root = document.documentElement;
root.classList.add('pin-wait');
function release() { root.classList.remove('pin-wait'); }
function remember() { try { sessionStorage.setItem(KEY, '1'); } catch (e) { /* asked again next load */ } }
fetch('/api/panel/pin', {cache: 'no-store'})
.then(function (r) { return r.ok ? r.json() : {pin: false}; })
.then(function (d) {
if (!d.pin) { remember(); release(); return; }
if (document.body) lock(); else document.addEventListener('DOMContentLoaded', lock);
})
.catch(release);
function lock() {
var LOCK = '';
var BACK = '';
var box = document.createElement('div');
box.className = 'pinlock';
box.setAttribute('role', 'dialog');
box.setAttribute('aria-modal', 'true');
box.setAttribute('aria-label', 'Ввод PIN-кода');
var keys = '';
['1', '2', '3', '4', '5', '6', '7', '8', '9', '', '0', 'back'].forEach(function (k) {
if (!k) { keys += ''; return; }
keys += '';
});
box.innerHTML =
'
' +
'
' + LOCK + '
' +
'
Введите PIN-код
' +
'
' +
'' +
'
' + keys + '
' +
'' +
'
';
document.body.appendChild(box);
requestAnimationFrame(function () { box.classList.add('on'); });
var dots = box.querySelectorAll('.pin-dots i'), msg = box.querySelector('.pin-msg'), card = box.querySelector('.pin-card');
var code = '', busy = false;
function show() {
for (var i = 0; i < 4; i++) dots[i].classList.toggle('on', i < code.length);
}
function press(k) {
if (busy) return;
if (k === 'back') { code = code.slice(0, -1); show(); return; }
if (code.length >= 4) return;
code += k;
msg.textContent = '';
card.classList.remove('bad');
show();
if (code.length === 4) check();
}
function check() {
busy = true;
fetch('/api/panel/pin/check', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({pin: code})})
.then(function (r) { return r.json(); })
.then(function (r) {
if (r.ok) {
remember();
box.classList.add('done');
release();
setTimeout(function () { box.remove(); }, 320);
document.removeEventListener('keydown', onKey, true);
return;
}
if (r.out) { location.href = '/login'; return; }
card.classList.remove('bad');
void card.offsetWidth; // restart the shake
card.classList.add('bad');
msg.textContent = r.error + (r.left ? ' - осталось попыток: ' + r.left : '');
setTimeout(function () { code = ''; show(); busy = false; }, 380);
})
.catch(function () { msg.textContent = 'нет связи с сервером'; code = ''; show(); busy = false; });
}
box.querySelector('.pin-pad').addEventListener('click', function (ev) {
var b = ev.target.closest('button');
if (b) press(b.dataset.k);
});
// Typed digits, ahead of anything on the page (the terminal included).
function onKey(ev) {
if (/^[0-9]$/.test(ev.key)) press(ev.key);
else if (ev.key === 'Backspace') press('back');
else if (ev.key !== 'Tab') return;
ev.preventDefault();
ev.stopPropagation();
}
document.addEventListener('keydown', onKey, true);
if (document.activeElement && document.activeElement.blur) document.activeElement.blur();
}
})();