Polish device registry identity displays
This commit is contained in:
@ -16,14 +16,17 @@ Dokumen ini adalah snapshot kerja terakhir untuk melanjutkan project tanpa perlu
|
||||
- KPI warning sekarang menunjukkan breakdown stale vs degraded;
|
||||
- KPI card bisa dipakai sebagai quick filter;
|
||||
- tabel Fleet Status menampilkan health bar, reason, signal, dan battery;
|
||||
- Device ID menjadi link langsung ke technical detail;
|
||||
- kolom utama Fleet Status sekarang memakai Serial Number sebagai identitas utama, dengan device code hanya sebagai detail sekunder;
|
||||
- Remote Actions menambahkan tombol `Power Off Device`;
|
||||
- layout mobile header/filter dibuat full-width agar tidak overflow.
|
||||
- Registry `/ui/device-registry-monitoring` sekarang mendukung koreksi device metadata:
|
||||
- tabel dan detail mengutamakan Serial Number, bukan internal device id;
|
||||
- menu row punya `Edit Device`;
|
||||
- menu row baris bawah otomatis membuka ke atas agar tidak tertutup pagination;
|
||||
- modal edit bisa koreksi `serial_number/dev-sn`, vendor, model, communication mode, status, dan firmware version;
|
||||
- perubahan model ikut memperbarui `capability_profile_json` dari katalog model aktif;
|
||||
- backend menolak duplicate `serial_number` supaya config pull/MQTT lookup tidak ambigu.
|
||||
- Device Technical Detail mengutamakan Serial Number di breadcrumb/title, menaruh device code sebagai detail, dan remote action menambahkan `Power Off Device`.
|
||||
- Search UI yang sebelumnya dekoratif sudah mulai difungsikan:
|
||||
- Admin Dashboard global search route ke Device Registry, Merchant List, atau Transaction History dengan `?q=`;
|
||||
- Transaction History dan Merchant List membaca `?q=` sebagai initial search;
|
||||
|
||||
@ -1062,9 +1062,21 @@ Changing SN affects config pull lookup and MQTT topic routing. Use the physical
|
||||
button.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
const rowId = event.currentTarget.getAttribute("data-id");
|
||||
const activeMenu = Array.from(tableBody.querySelectorAll("[data-device-menu]"))
|
||||
.find((menu) => menu.getAttribute("data-device-menu") === rowId);
|
||||
const shouldOpen = Boolean(activeMenu?.classList.contains("hidden"));
|
||||
tableBody.querySelectorAll("[data-device-menu]").forEach((menu) => {
|
||||
menu.classList.toggle("hidden", menu.getAttribute("data-device-menu") !== rowId || !menu.classList.contains("hidden"));
|
||||
menu.classList.add("hidden", "top-12");
|
||||
menu.classList.remove("bottom-12");
|
||||
});
|
||||
if (!activeMenu || !shouldOpen) {
|
||||
return;
|
||||
}
|
||||
const buttonRect = event.currentTarget.getBoundingClientRect();
|
||||
const availableBelow = window.innerHeight - buttonRect.bottom;
|
||||
activeMenu.classList.toggle("top-12", availableBelow >= 190);
|
||||
activeMenu.classList.toggle("bottom-12", availableBelow < 190);
|
||||
activeMenu.classList.remove("hidden");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -212,7 +212,7 @@
|
||||
</p>
|
||||
<p class="text-body-md text-on-surface-variant flex items-center gap-1.5">
|
||||
<span class="material-symbols-outlined text-sm">tag</span>
|
||||
<span id="device-serial-number">SN: -</span>
|
||||
<span id="device-serial-number">Code: -</span>
|
||||
</p>
|
||||
<p class="text-body-md text-on-surface-variant flex items-center gap-1.5">
|
||||
<span class="material-symbols-outlined text-sm">schedule</span>
|
||||
@ -405,6 +405,13 @@ Loading
|
||||
</div>
|
||||
<span class="material-symbols-outlined text-slate-300">chevron_right</span>
|
||||
</button>
|
||||
<button id="poweroff-device" class="w-full flex items-center justify-between p-3 border border-danger/20 bg-danger/5 rounded-lg hover:bg-danger/10 transition-all group">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-danger">power_settings_new</span>
|
||||
<span class="font-body-md font-bold text-danger">Power Off Device</span>
|
||||
</div>
|
||||
<span class="material-symbols-outlined text-danger/50">chevron_right</span>
|
||||
</button>
|
||||
<button id="update-device-firmware" class="w-full flex items-center justify-between p-3 border border-slate-200 rounded-lg hover:bg-slate-50 transition-all group">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-on-surface-variant group-hover:text-primary">system_update</span>
|
||||
@ -596,6 +603,7 @@ Copy Command
|
||||
const calendarButton = document.getElementById("detail-calendar-button");
|
||||
const copyPayloadButton = document.getElementById("copy-payload-stream");
|
||||
const rebootButton = document.getElementById("reboot-device");
|
||||
const poweroffButton = document.getElementById("poweroff-device");
|
||||
const updateFirmwareButton = document.getElementById("update-device-firmware");
|
||||
const unbindButton = document.getElementById("unbind-device");
|
||||
const decommissionButton = document.getElementById("decommission-device");
|
||||
@ -1327,11 +1335,12 @@ Copy Command
|
||||
showingAllEvents = false;
|
||||
}
|
||||
|
||||
const modelCode = device.device_code || device.code || device.serial_number || device.id || "Unknown Device";
|
||||
setText(els.breadcrumbCode, modelCode);
|
||||
setText(els.title, modelCode);
|
||||
const serialNumber = device.serial_number || device.device_code || device.code || device.id || "Unknown Device";
|
||||
const deviceCode = device.device_code || device.code || device.id || "-";
|
||||
setText(els.breadcrumbCode, serialNumber);
|
||||
setText(els.title, serialNumber);
|
||||
setText(els.model, device.model || device.device_model || "Unknown model");
|
||||
setText(els.serialNumber, `SN: ${device.serial_number || "-"}`);
|
||||
setText(els.serialNumber, `Code: ${deviceCode}`);
|
||||
setText(els.location, device.location || device.last_known_city || "Unknown");
|
||||
|
||||
const latest = Array.isArray(heartbeats) && heartbeats.length ? heartbeats[0] : null;
|
||||
@ -1440,6 +1449,17 @@ Copy Command
|
||||
viewAllEventsBtn.textContent = showingAllEvents ? "Show Recent Events" : "View All Events";
|
||||
});
|
||||
rebootButton?.addEventListener("click", () => sendDeviceCommand("device.reboot", { requested_from: "device_detail" }, rebootButton));
|
||||
poweroffButton?.addEventListener("click", async () => {
|
||||
const confirmed = await confirmAction({
|
||||
title: "Power off device",
|
||||
message: `This will send a power-off command to ${currentDevice?.serial_number || activeDeviceId || "this device"}. The device may stop reporting until manually powered on again.`,
|
||||
buttonLabel: "Power Off"
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
await sendDeviceCommand("device.poweroff", { requested_from: "device_detail" }, poweroffButton);
|
||||
});
|
||||
updateFirmwareButton?.addEventListener("click", () => sendDeviceCommand("firmware.update", {
|
||||
requested_from: "device_detail",
|
||||
target_version: currentDevice?.firmware_version || "latest"
|
||||
|
||||
@ -144,7 +144,7 @@
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead class="bg-slate-50 text-left text-xs font-bold uppercase text-slate-500">
|
||||
<tr>
|
||||
<th class="px-5 py-3">Soundbox</th>
|
||||
<th class="px-5 py-3">Serial Number</th>
|
||||
<th class="px-5 py-3">Merchant</th>
|
||||
<th class="px-5 py-3">Mode</th>
|
||||
<th class="px-5 py-3">Health</th>
|
||||
@ -373,6 +373,7 @@
|
||||
const mode = modeMeta(device.communication_mode);
|
||||
const score = device.health_summary?.score;
|
||||
const code = device.device_code || device.id || "-";
|
||||
const serial = device.serial_number || device.device_code || device.id || "-";
|
||||
const heartbeat = device.latest_heartbeat || {};
|
||||
const signal = heartbeat.network_strength;
|
||||
const battery = heartbeat.battery_level;
|
||||
@ -381,8 +382,8 @@
|
||||
return `
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="px-5 py-4">
|
||||
<a class="font-bold text-slate-950 hover:text-blue-700" href="${detailUrl}">${esc(code)}</a>
|
||||
<div class="mono mt-1 text-xs text-slate-500">${esc(device.serial_number || device.id || "-")}</div>
|
||||
<a class="mono font-bold text-slate-950 hover:text-blue-700" href="${detailUrl}">${esc(serial)}</a>
|
||||
<div class="mono mt-1 text-xs text-slate-500">Code: ${esc(code)}</div>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="font-semibold">${esc(merchantName(device))}</div>
|
||||
@ -438,7 +439,7 @@
|
||||
state.devices.forEach((device) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = device.id;
|
||||
option.textContent = `${device.device_code || device.serial_number || device.id} · ${device.serial_number || device.model || "soundbox"}`;
|
||||
option.textContent = `${device.serial_number || device.device_code || device.id} · ${device.model || device.device_code || "soundbox"}`;
|
||||
select.appendChild(option);
|
||||
});
|
||||
if (current && state.devices.some((device) => device.id === current)) {
|
||||
@ -644,7 +645,7 @@
|
||||
|
||||
button.disabled = true;
|
||||
button.classList.add("opacity-60");
|
||||
status.textContent = `Sending ${actionLabel.toLowerCase()} to ${device?.device_code || device?.serial_number || deviceId}...`;
|
||||
status.textContent = `Sending ${actionLabel.toLowerCase()} to ${device?.serial_number || device?.device_code || deviceId}...`;
|
||||
status.className = "mt-2 min-h-5 text-xs font-semibold text-slate-500";
|
||||
try {
|
||||
const result = await api.createDeviceCommand(deviceId, {
|
||||
|
||||
Reference in New Issue
Block a user