From d5398bdd742c2abc1637b60e38cf0d1c2c99d72f Mon Sep 17 00:00:00 2001 From: cproudlock Date: Thu, 14 May 2026 19:48:43 -0400 Subject: [PATCH] imaging: LAPS-password-to-QR generator per bay card Per-bay
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) --- webapp/templates/imaging.html | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/webapp/templates/imaging.html b/webapp/templates/imaging.html index 9162af1..d5a6a31 100644 --- a/webapp/templates/imaging.html +++ b/webapp/templates/imaging.html @@ -96,6 +96,23 @@ {% endif %} + {% if s.intune_device_id %} +
+ LAPS password QR (paste -> scan on bay) +
+ + + + +
+
+
+ {% endif %} + {% if s.log_tail %}
Log tail ({{ s.log_tail | length }} line{{ 's' if s.log_tail | length != 1 }}) @@ -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); } + } +}); {% endblock %}