diff --git a/CODEX_HANDOFF.md b/CODEX_HANDOFF.md index 72d9c84..57e1438 100644 --- a/CODEX_HANDOFF.md +++ b/CODEX_HANDOFF.md @@ -1,9 +1,69 @@ # Codex Handoff - QRIS Soundbox Platform -Tanggal update: 2026-06-06, Asia/Jakarta. +Tanggal update: 2026-06-07, Asia/Jakarta. Dokumen ini adalah snapshot kerja terakhir untuk melanjutkan project tanpa perlu membaca ulang seluruh chat. +## Update Terbaru - 2026-06-07 + +- Production saat ini fokus ke portal Soundbox Ops di `sms.bizone.id`, dengan MQTT broker `broker.bizone.id`. +- Commit terakhir yang sudah dipush sebelum update handoff ini: + - `1e0f36f Split MQTT trace and commands pages` + - `ef23b09 Parse QF100 heartbeat time as WIB` + - `e3d7e60 Complete QF100 ops commands and detail UI` +- Perubahan UI/ops terbaru: + - menu `MQTT Trace` sekarang halaman sendiri: `/ui/mqtt-trace`; + - menu `Config & Commands` sekarang halaman sendiri: `/ui/config-commands`; + - sidebar tidak lagi lompat ke anchor dashboard untuk dua menu tersebut; + - dashboard tetap ada summary ringkas, tetapi menu operasional utama pindah ke screen masing-masing. +- Halaman `/ui/mqtt-trace`: + - menampilkan trail uplink/downlink ala audit trail; + - filter direction `uplink/downlink`; + - filter message type; + - search topic/device/type/payload; + - limit 100/250/500; + - auto refresh 10 detik; + - klik event untuk lihat full payload JSON. +- Endpoint `/admin/mqtt/status` sekarang mendukung: + - `limit` sampai 500; + - `direction`; + - `message_type`; + - `device_id`. +- Halaman `/ui/config-commands`: + - menampilkan status database, pending/failed notification, export worker; + - menampilkan MQTT publisher/subscriber runtime; + - dropdown device dengan SN, model, connection, last seen; + - tombol `Reboot Device`; + - tombol `Open Device Detail`. +- Fix dashboard terbaru: + - kolom `Last Seen` di `/ui/soundbox-ops` tidak lagi menampilkan `[object Object]`; + - formatter sekarang membaca `latest_heartbeat.received_at`, `latest_heartbeat.timestamp`, lalu fallback `last_seen_at`. +- QF100 firmware categories yang sudah disiapkan: + - category `1`: payment sound, payload `data.pay-amount`; + - category `3`: heartbeat dari device; + - category `4`: dynamic QR display, payload `data.qr-url`, `data.amount`, `data.expire-seconds`; + - category `5`: reboot command, payload `data.command = "reboot"`. +- Dynamic QR: + - modal preview di Device Technical Detail sudah generate QR dari `data["qr-url"]`; + - tombol `Send Test QR` mengirim command `dynamic_qr.display`; + - backend menerima payload nested `header/data` dan publish category `4` ke `soundbox/{serial_number}/down`. +- Reboot: + - command `device.reboot` publish category `5` ke `soundbox/{serial_number}/down`; + - bisa dikirim dari Device Technical Detail, Device Registry drawer, dan `/ui/config-commands`. +- Heartbeat: + - QF100 mengirim `data.time` sebagai WIB/UTC+7; + - backend sekarang parse waktu itu sebagai WIB lalu simpan UTC; + - contoh `20260607023400` disimpan menjadi `2026-06-06T19:34:00.000Z`. +- Device Registry dan Device Detail: + - SN ditampilkan di tabel Device Registry; + - SN ditampilkan di drawer Device Detail; + - SN ditampilkan di header Device Technical Detail. +- Verifikasi lokal terakhir: + - `npm run typecheck`: pass; + - `node scripts/ui-qa-check.mjs`: pass; + - `node --check scripts/smoke-qf100-adapter.mjs`: pass pada patch QF100 sebelumnya; + - `git diff --check`: pass. + ## Update Terbaru - 2026-06-06 - Fokus produk sekarang: `sms.bizone.id` menjadi portal utama Soundbox Ops / Monitoring, bukan katalog UI atau dashboard admin campuran. diff --git a/ui/soundbox-ops/index.html b/ui/soundbox-ops/index.html index 33ddad7..582a8c4 100644 --- a/ui/soundbox-ops/index.html +++ b/ui/soundbox-ops/index.html @@ -233,6 +233,9 @@ function lastSeen(value) { if (!value) return "No heartbeat"; + if (typeof value === "object") { + return lastSeen(value.received_at || value.timestamp || value.created_at || value.updated_at); + } const date = new Date(value); if (Number.isNaN(date.getTime())) return value; const minutes = Math.floor((Date.now() - date.getTime()) / 60000); @@ -242,6 +245,13 @@ return `${Math.floor(minutes / 1440)}d ago`; } + function deviceLastSeen(device) { + return device?.latest_heartbeat?.received_at || + device?.latest_heartbeat?.timestamp || + device?.last_seen_at || + null; + } + function statusMeta(value) { const status = normalize(value); if (status === "online") return { label: "Online", badge: "bg-emerald-50 text-emerald-700 border-emerald-200", dot: "bg-emerald-500" }; @@ -342,7 +352,7 @@
Health ${healthLabel(device)}
- ${lastSeen(device.latest_heartbeat)} + ${lastSeen(deviceLastSeen(device))} Detail open_in_new