feat(fusion_clock): NFC kiosk on-screen debug overlay + clearer settings label
This commit is contained in:
@@ -253,11 +253,13 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
"Leave empty to fall back to manager-group membership only.",
|
"Leave empty to fall back to manager-group membership only.",
|
||||||
)
|
)
|
||||||
fclk_nfc_kiosk_debug = fields.Boolean(
|
fclk_nfc_kiosk_debug = fields.Boolean(
|
||||||
string='Enable Mock-Tap Debug',
|
string='Debug Mode (overlay + mock-tap)',
|
||||||
config_parameter='fusion_clock.nfc_kiosk_debug',
|
config_parameter='fusion_clock.nfc_kiosk_debug',
|
||||||
default=False,
|
default=False,
|
||||||
help="Enables a Ctrl+Shift+T keyboard shortcut on the kiosk page for "
|
help="Enables two dev/troubleshooting features on the NFC kiosk page: "
|
||||||
"simulating a tap with a configurable UID. Off in production.",
|
"(1) a green-text debug overlay at the top of the screen logging every NFC and tap event in real time, "
|
||||||
|
"and (2) a Ctrl+Shift+T keyboard shortcut that simulates a tap with a configurable UID. "
|
||||||
|
"Turn OFF in production — the overlay is intrusive for end users.",
|
||||||
)
|
)
|
||||||
fclk_nfc_kiosk_location_id = fields.Many2one(
|
fclk_nfc_kiosk_location_id = fields.Many2one(
|
||||||
related='company_id.x_fclk_nfc_kiosk_location_id',
|
related='company_id.x_fclk_nfc_kiosk_location_id',
|
||||||
|
|||||||
@@ -16,6 +16,29 @@
|
|||||||
const debugEnabled = root.dataset.debugEnabled === "1";
|
const debugEnabled = root.dataset.debugEnabled === "1";
|
||||||
const locationConfigured = root.dataset.locationConfigured === "1";
|
const locationConfigured = root.dataset.locationConfigured === "1";
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
// Debug overlay (visible only when fusion_clock.nfc_kiosk_debug = True)
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
let _debugOverlayEl = null;
|
||||||
|
function debugLog(msg) {
|
||||||
|
try { console.log("[nfc-kiosk-debug]", msg); } catch (e) {}
|
||||||
|
if (!debugEnabled) return;
|
||||||
|
if (!_debugOverlayEl) {
|
||||||
|
_debugOverlayEl = document.createElement("div");
|
||||||
|
_debugOverlayEl.style.cssText = "position:fixed;top:0;left:0;right:0;background:rgba(0,0,0,0.9);color:#0f0;font-family:monospace;font-size:11px;padding:0.5rem;max-height:35vh;overflow-y:auto;z-index:9999;line-height:1.3;border-bottom:1px solid #0f0;";
|
||||||
|
document.body.appendChild(_debugOverlayEl);
|
||||||
|
}
|
||||||
|
const line = document.createElement("div");
|
||||||
|
const ts = new Date().toLocaleTimeString();
|
||||||
|
line.textContent = "[" + ts + "] " + msg;
|
||||||
|
_debugOverlayEl.appendChild(line);
|
||||||
|
while (_debugOverlayEl.childNodes.length > 40) {
|
||||||
|
_debugOverlayEl.removeChild(_debugOverlayEl.firstChild);
|
||||||
|
}
|
||||||
|
_debugOverlayEl.scrollTop = _debugOverlayEl.scrollHeight;
|
||||||
|
}
|
||||||
|
debugLog("page loaded; debugEnabled=" + debugEnabled + " photoRequired=" + photoRequired + " NDEFReader=" + ("NDEFReader" in window));
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────
|
||||||
// State machine
|
// State machine
|
||||||
// ──────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────
|
||||||
@@ -281,49 +304,68 @@
|
|||||||
let nfcReady = false;
|
let nfcReady = false;
|
||||||
|
|
||||||
async function startNfcReader() {
|
async function startNfcReader() {
|
||||||
|
debugLog("startNfcReader: NDEFReader in window = " + ("NDEFReader" in window));
|
||||||
if (!("NDEFReader" in window)) {
|
if (!("NDEFReader" in window)) {
|
||||||
throw new Error("Web NFC not supported on this browser/device. Use Chrome on Android.");
|
throw new Error("Web NFC not supported on this browser/device. Use Chrome on Android.");
|
||||||
}
|
}
|
||||||
ndefReader = new NDEFReader();
|
ndefReader = new NDEFReader();
|
||||||
|
debugLog("startNfcReader: ndefReader created, calling scan()...");
|
||||||
await ndefReader.scan();
|
await ndefReader.scan();
|
||||||
|
debugLog("startNfcReader: scan() resolved ✓");
|
||||||
ndefReader.addEventListener("reading", onNfcReading);
|
ndefReader.addEventListener("reading", onNfcReading);
|
||||||
ndefReader.addEventListener("readingerror", () => {
|
ndefReader.addEventListener("readingerror", (ev) => {
|
||||||
|
debugLog("readingerror event fired");
|
||||||
console.warn("[nfc-kiosk] reading error; reader still active");
|
console.warn("[nfc-kiosk] reading error; reader still active");
|
||||||
});
|
});
|
||||||
nfcReady = true;
|
nfcReady = true;
|
||||||
|
debugLog("startNfcReader: listeners attached, nfcReady=true");
|
||||||
}
|
}
|
||||||
|
|
||||||
function onNfcReading(event) {
|
function onNfcReading(event) {
|
||||||
// event.serialNumber is the card UID — works for raw MIFARE access cards
|
// event.serialNumber is the card UID — works for raw MIFARE access cards
|
||||||
const uid = (event.serialNumber || "").toUpperCase();
|
const rawSerial = event.serialNumber || "";
|
||||||
if (!uid) return;
|
const uid = rawSerial.toUpperCase();
|
||||||
|
const recCount = (event.message && event.message.records) ? event.message.records.length : 0;
|
||||||
|
debugLog("reading event: serialNumber=" + JSON.stringify(rawSerial) + " (len=" + rawSerial.length + ") records=" + recCount + " state=" + currentState);
|
||||||
|
if (!uid) {
|
||||||
|
debugLog(" → IGNORED: empty serialNumber");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (currentState === STATE.ENROLL) {
|
if (currentState === STATE.ENROLL) {
|
||||||
// Enroll Mode handles taps differently (wired up in Task 18)
|
debugLog(" → routing to _onEnrollTap");
|
||||||
window.__nfcKiosk._onEnrollTap && window.__nfcKiosk._onEnrollTap(uid);
|
window.__nfcKiosk._onEnrollTap && window.__nfcKiosk._onEnrollTap(uid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentState !== STATE.IDLE) return; // ignore taps mid-result
|
if (currentState !== STATE.IDLE) {
|
||||||
|
debugLog(" → IGNORED: not in IDLE (state=" + currentState + ")");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugLog(" → calling handleTap(" + uid + ")");
|
||||||
handleTap(uid);
|
handleTap(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTap(uid) {
|
async function handleTap(uid) {
|
||||||
|
debugLog("handleTap: uid=" + uid);
|
||||||
setState(STATE.PROCESSING);
|
setState(STATE.PROCESSING);
|
||||||
let photoB64 = "";
|
let photoB64 = "";
|
||||||
try {
|
try {
|
||||||
photoB64 = await capturePhoto();
|
photoB64 = await capturePhoto();
|
||||||
|
debugLog("handleTap: photo captured, size=" + photoB64.length);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
debugLog("handleTap: photo capture failed: " + e.message);
|
||||||
console.warn("[nfc-kiosk] camera capture failed", e);
|
console.warn("[nfc-kiosk] camera capture failed", e);
|
||||||
// Server enforces photo_required if needed
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
debugLog("handleTap: POST /fusion_clock/kiosk/nfc/tap...");
|
||||||
const result = await postJson("/fusion_clock/kiosk/nfc/tap", { card_uid: uid, photo_b64: photoB64 });
|
const result = await postJson("/fusion_clock/kiosk/nfc/tap", { card_uid: uid, photo_b64: photoB64 });
|
||||||
|
debugLog("handleTap: response = " + JSON.stringify(result).slice(0, 200));
|
||||||
if (result.error === "debounce") {
|
if (result.error === "debounce") {
|
||||||
// silent — back to IDLE
|
|
||||||
setState(STATE.IDLE);
|
setState(STATE.IDLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(STATE.RESULT, result);
|
setState(STATE.RESULT, result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
debugLog("handleTap: POST failed: " + e.message);
|
||||||
setState(STATE.RESULT, { error: "network", message: "No connection. Please try again." });
|
setState(STATE.RESULT, { error: "network", message: "No connection. Please try again." });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,11 +416,15 @@
|
|||||||
const setupBtn = document.getElementById("nfc_setup_start");
|
const setupBtn = document.getElementById("nfc_setup_start");
|
||||||
if (setupBtn) {
|
if (setupBtn) {
|
||||||
setupBtn.addEventListener("click", async () => {
|
setupBtn.addEventListener("click", async () => {
|
||||||
|
debugLog("setup button clicked");
|
||||||
try {
|
try {
|
||||||
await startNfcReader();
|
await startNfcReader();
|
||||||
|
debugLog("setup: NFC ready, starting camera...");
|
||||||
try {
|
try {
|
||||||
await startCamera();
|
await startCamera();
|
||||||
|
debugLog("setup: camera ready ✓");
|
||||||
} catch (camErr) {
|
} catch (camErr) {
|
||||||
|
debugLog("setup: camera failed: " + camErr.message);
|
||||||
if (photoRequired) throw camErr;
|
if (photoRequired) throw camErr;
|
||||||
console.warn("[nfc-kiosk] camera unavailable, continuing (photo not required)", camErr);
|
console.warn("[nfc-kiosk] camera unavailable, continuing (photo not required)", camErr);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,7 +263,7 @@
|
|||||||
<field name="fclk_nfc_enroll_password" password="True"/>
|
<field name="fclk_nfc_enroll_password" password="True"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt8">
|
<div class="row mt8">
|
||||||
<label for="fclk_nfc_kiosk_debug" string="Mock-Tap Debug" class="col-lg-5 o_light_label"/>
|
<label for="fclk_nfc_kiosk_debug" string="Debug Overlay" class="col-lg-5 o_light_label"/>
|
||||||
<field name="fclk_nfc_kiosk_debug"/>
|
<field name="fclk_nfc_kiosk_debug"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user