imaging: LAPS-password-to-QR generator per bay card

Per-bay <details> section with:
 - Input field for LAPS password (paste from Intune portal manually,
   since deep-link to LAPS blade needs AAD objectId we can't obtain)
 - Make QR button generates a client-side QR from the input
 - QR displayed below at 280px with 4-cell quiet zone
 - Auto-clears input + QR after 60s with live countdown
 - Manual Clear button
 - Enter key on the input also triggers QR generation

Password never POSTs to server, never logged, never persists past the
60s window. Generated using the same qrcode-generator lib already
loaded for the device-id QR. Scan with a USB barcode scanner plugged
into the bay (HID keyboard mode) -> password types into bay login
field. Faster than reading off the Intune portal letter-by-letter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-05-14 19:48:43 -04:00
parent cdb6655e4a
commit d5398bdd74

View File

@@ -96,6 +96,23 @@
</div>
{% endif %}
{% if s.intune_device_id %}
<details class="mt-2 laps-card">
<summary class="text-muted small">LAPS password QR (paste -> scan on bay)</summary>
<div class="d-flex align-items-center gap-2 mt-2">
<input type="text"
class="form-control form-control-sm laps-input"
style="font-family: monospace; max-width: 22rem;"
placeholder="paste LAPS password from Intune portal here"
autocomplete="off">
<button type="button" class="btn btn-sm btn-primary laps-make-btn">Make QR</button>
<button type="button" class="btn btn-sm btn-outline-secondary laps-clear-btn" style="display:none;">Clear</button>
<span class="laps-timer text-muted small"></span>
</div>
<div class="laps-qr-container mt-2"></div>
</details>
{% endif %}
{% if s.log_tail %}
<details>
<summary class="text-muted small">Log tail ({{ s.log_tail | length }} line{{ 's' if s.log_tail | length != 1 }})</summary>
@@ -202,5 +219,67 @@ document.addEventListener('click', function(e) {
copyText(text).then(function() { flashCopied(btn, true); })
.catch(function(err) { console.error('copy failed:', err); flashCopied(btn, false); });
});
// LAPS password -> client-side QR. Password never leaves browser tab.
// Auto-clears input + QR after 60s so it doesn't linger on shared screens.
function renderLapsQR(card) {
var input = card.querySelector('.laps-input');
var container = card.querySelector('.laps-qr-container');
var makeBtn = card.querySelector('.laps-make-btn');
var clearBtn = card.querySelector('.laps-clear-btn');
var timerEl = card.querySelector('.laps-timer');
var text = input.value;
if (!text) { input.focus(); return; }
try {
var qr = qrcode(0, 'M');
qr.addData(text);
qr.make();
var modules = qr.getModuleCount();
var size = 280;
var cellSize = Math.max(1, Math.floor(size / (modules + 8)));
container.innerHTML = qr.createImgTag(cellSize, 4);
makeBtn.style.display = 'none';
clearBtn.style.display = '';
var remaining = 60;
timerEl.textContent = '(auto-clears in ' + remaining + 's)';
if (card._lapsTimer) clearInterval(card._lapsTimer);
card._lapsTimer = setInterval(function() {
remaining--;
if (remaining <= 0) { clearLapsQR(card); return; }
timerEl.textContent = '(auto-clears in ' + remaining + 's)';
}, 1000);
} catch (err) {
container.textContent = 'QR error: ' + err;
}
}
function clearLapsQR(card) {
var input = card.querySelector('.laps-input');
var container = card.querySelector('.laps-qr-container');
var makeBtn = card.querySelector('.laps-make-btn');
var clearBtn = card.querySelector('.laps-clear-btn');
var timerEl = card.querySelector('.laps-timer');
if (card._lapsTimer) { clearInterval(card._lapsTimer); card._lapsTimer = null; }
input.value = '';
container.innerHTML = '';
timerEl.textContent = '';
makeBtn.style.display = '';
clearBtn.style.display = 'none';
}
document.addEventListener('click', function(e) {
var card;
if (e.target.classList.contains('laps-make-btn')) {
card = e.target.closest('.laps-card');
if (card) renderLapsQR(card);
} else if (e.target.classList.contains('laps-clear-btn')) {
card = e.target.closest('.laps-card');
if (card) clearLapsQR(card);
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && e.target.classList.contains('laps-input')) {
var card = e.target.closest('.laps-card');
if (card) { e.preventDefault(); renderLapsQR(card); }
}
});
</script>
{% endblock %}