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:
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user