Complete QF100 ops commands and detail UI
This commit is contained in:
@ -455,13 +455,14 @@ Rows
|
||||
<img alt="Soundbox V2 Product" class="w-20 h-20 rounded-xl bg-slate-100" data-alt="A clean professional studio product shot of a minimalist electronic soundbox speaker device with a small LCD screen and premium matte plastic finish. The lighting is soft and corporate with subtle blue reflections on the surface consistent with a high-end fintech hardware brand. The background is a clean neutral white studio setting." src="https://lh3.googleusercontent.com/aida-public/AB6AXuC-CSPTCnxQuDTN1XM0atRPM9hIcVzf3zpbuxEUGTIlC-c1BivDqPa9osmBscvoiUcJeMBwUaXbZ6Ut5FuG2a91sVtZjzWRTgLck34kJJJy3N2E9O3uVtZw6InOpX9Gkph2OJxu_Z-PkR_t3F56EVZY3u8o2iZO3iH8hj9_ajrku7g1r_l54uobcRoN3dRH3k_at6GTuGbMtSSD4ew24sX8nePUsVvILKJauQLcMKD14J6mtAGm0x5PfViQQKdJzf_pYMqKswr3Yz4"/>
|
||||
<div>
|
||||
<h4 class="font-bold text-headline-md mb-1" id="device-detail-title">-</h4>
|
||||
<p class="font-mono text-body-md text-slate-500" id="device-detail-serial">SN: -</p>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-bold bg-slate-100 text-slate-600" id="device-detail-model">Device</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6" id="device-detail-content"></div>
|
||||
</div>
|
||||
<div class="p-6 border-t border-slate-200 grid grid-cols-2 gap-4">
|
||||
<button class="w-full py-2.5 border border-slate-200 rounded-xl font-bold hover:bg-slate-50 transition-colors">Reboot Device</button>
|
||||
<button id="drawer-reboot-device" class="w-full py-2.5 border border-slate-200 rounded-xl font-bold hover:bg-slate-50 transition-colors">Reboot Device</button>
|
||||
<button class="w-full py-2.5 bg-danger/10 text-danger rounded-xl font-bold hover:bg-danger/20 transition-colors">Unbind Merchant</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -495,8 +496,10 @@ Rows
|
||||
const detailDrawer = document.getElementById("device-detail-drawer");
|
||||
const detailCloseButton = document.getElementById("device-detail-close");
|
||||
const detailTitle = document.getElementById("device-detail-title");
|
||||
const detailSerial = document.getElementById("device-detail-serial");
|
||||
const detailModel = document.getElementById("device-detail-model");
|
||||
const detailContent = document.getElementById("device-detail-content");
|
||||
const drawerRebootButton = document.getElementById("drawer-reboot-device");
|
||||
const registerModal = document.getElementById("device-register-modal");
|
||||
const registerForm = document.getElementById("device-register-form");
|
||||
const topbarRegisterOpenButton = document.getElementById("topbar-register-device-open");
|
||||
@ -612,6 +615,9 @@ Rows
|
||||
if (value === "applied") {
|
||||
return { label: "Applied", className: "bg-success/10 text-success border-success/20", icon: "check_circle" };
|
||||
}
|
||||
if (value === "pulled_not_pushed") {
|
||||
return { label: "Pulled by Device", className: "bg-success/10 text-success border-success/20", icon: "check_circle" };
|
||||
}
|
||||
if (value === "pending_ack") {
|
||||
return { label: "Pending ACK", className: "bg-warning/10 text-warning border-warning/20", icon: "pending" };
|
||||
}
|
||||
@ -834,6 +840,7 @@ Rows
|
||||
let currentPage = 1;
|
||||
let pageSize = Number(pageSizeSelect?.value || 10);
|
||||
let currentSearchQuery = "";
|
||||
let activeDrawerDevice = null;
|
||||
let merchants = [];
|
||||
let outlets = [];
|
||||
let terminals = [];
|
||||
@ -855,6 +862,7 @@ Rows
|
||||
tableBody.innerHTML = items
|
||||
.map((device) => {
|
||||
const id = device.device_code || device.id || "";
|
||||
const serialNumber = escapeHtml(device.serial_number || "-");
|
||||
const model = device.model || "Unknown";
|
||||
const binding = device.binding_summary || {};
|
||||
const merchantName = merchantMap.get(binding.merchant_id) || "Unassigned";
|
||||
@ -866,7 +874,10 @@ Rows
|
||||
return `
|
||||
<tr class="hover:bg-slate-50 transition-colors group">
|
||||
<td class="px-6 py-row-height">
|
||||
<span class="font-mono text-primary font-bold">${id || "-"}</span>
|
||||
<div class="space-y-1">
|
||||
<span class="block font-mono text-primary font-bold">${id || "-"}</span>
|
||||
<span class="block font-mono text-[12px] text-slate-500">SN: ${serialNumber}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-row-height">${model}</td>
|
||||
<td class="px-6 py-row-height">
|
||||
@ -1140,6 +1151,7 @@ Rows
|
||||
if (!detailDrawer || !detailOverlay || !detailTitle || !detailModel || !detailContent) {
|
||||
return;
|
||||
}
|
||||
activeDrawerDevice = device;
|
||||
|
||||
const binding = device.binding_summary || {};
|
||||
const connection = connectionMeta(device.communication_mode);
|
||||
@ -1152,7 +1164,11 @@ Rows
|
||||
: "No active warning";
|
||||
|
||||
const id = device.device_code || device.id || "-";
|
||||
const serialNumber = escapeHtml(device.serial_number || "-");
|
||||
detailTitle.textContent = id;
|
||||
if (detailSerial) {
|
||||
detailSerial.textContent = `SN: ${serialNumber}`;
|
||||
}
|
||||
detailModel.textContent = device.model || "Unknown";
|
||||
detailModel.className = `inline-flex items-center px-2 py-0.5 rounded-full text-xs font-bold ${status.className}`;
|
||||
|
||||
@ -1160,6 +1176,10 @@ Rows
|
||||
<section>
|
||||
<h5 class="text-label-md font-label-md text-slate-500 uppercase mb-3">Device Detail</h5>
|
||||
<div class="bg-slate-50 p-4 rounded-xl border border-slate-100">
|
||||
<div class="flex justify-between gap-4 mb-2">
|
||||
<span class="text-on-surface-variant">Serial Number</span>
|
||||
<span class="font-mono font-bold text-right">${serialNumber}</span>
|
||||
</div>
|
||||
<div class="flex justify-between mb-2">
|
||||
<span class="text-on-surface-variant">Model</span>
|
||||
<span class="font-bold">${device.model || "Unknown"}</span>
|
||||
@ -1233,6 +1253,33 @@ Rows
|
||||
loadDrawerConfig(device.id);
|
||||
};
|
||||
|
||||
const sendDrawerRebootCommand = async () => {
|
||||
if (!activeDrawerDevice || !drawerRebootButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const originalText = drawerRebootButton.textContent || "Reboot Device";
|
||||
drawerRebootButton.disabled = true;
|
||||
drawerRebootButton.textContent = "Sending...";
|
||||
try {
|
||||
const result = await api.createDeviceCommand(activeDrawerDevice.id, {
|
||||
command: "device.reboot",
|
||||
payload: { requested_from: "device_registry_drawer" }
|
||||
});
|
||||
drawerRebootButton.textContent = result.status === "delivered" ? "Reboot Sent" : "Reboot Queued";
|
||||
window.setTimeout(() => {
|
||||
drawerRebootButton.textContent = originalText;
|
||||
drawerRebootButton.disabled = false;
|
||||
}, 1800);
|
||||
} catch (error) {
|
||||
drawerRebootButton.textContent = "Reboot Failed";
|
||||
window.setTimeout(() => {
|
||||
drawerRebootButton.textContent = originalText;
|
||||
drawerRebootButton.disabled = false;
|
||||
}, 2200);
|
||||
}
|
||||
};
|
||||
|
||||
const loadDrawerConfig = async (deviceId) => {
|
||||
const box = document.getElementById("device-config-status-box");
|
||||
if (!box || !deviceId) {
|
||||
@ -1244,6 +1291,7 @@ Rows
|
||||
const meta = configStatusMeta(configStatus.drift_status);
|
||||
const latestPush = configStatus.latest_push;
|
||||
const latestAck = configStatus.latest_ack;
|
||||
const latestPull = configStatus.latest_config_pull;
|
||||
const canRetry = configStatus.retry_recommended;
|
||||
box.innerHTML = `
|
||||
<div class="flex items-center justify-between gap-3 mb-3">
|
||||
@ -1253,7 +1301,11 @@ Rows
|
||||
</span>
|
||||
<span class="font-mono text-slate-500">v${configStatus.desired_config_version || "-"}</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3 text-sm mb-4">
|
||||
<div class="grid grid-cols-3 gap-3 text-sm mb-4">
|
||||
<div>
|
||||
<p class="text-slate-500">Latest Pull</p>
|
||||
<p class="font-bold">${latestPull ? formatLastSeen(latestPull.received_at || latestPull.timestamp) : "-"}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-slate-500">Latest Push</p>
|
||||
<p class="font-bold">${latestPush ? formatLastSeen(latestPush.created_at) : "-"}</p>
|
||||
@ -1472,6 +1524,7 @@ Rows
|
||||
if (!detailDrawer || !detailOverlay) {
|
||||
return;
|
||||
}
|
||||
activeDrawerDevice = null;
|
||||
detailDrawer.classList.add("translate-x-full");
|
||||
detailOverlay.classList.remove("opacity-100");
|
||||
detailOverlay.classList.add("opacity-0", "pointer-events-none");
|
||||
@ -1578,6 +1631,7 @@ Rows
|
||||
});
|
||||
detailOverlay?.addEventListener("click", closeDrawer);
|
||||
detailCloseButton?.addEventListener("click", closeDrawer);
|
||||
drawerRebootButton?.addEventListener("click", sendDrawerRebootCommand);
|
||||
topbarRegisterOpenButton?.addEventListener("click", openRegisterModal);
|
||||
refreshButton?.addEventListener("click", refresh);
|
||||
registerCloseButton?.addEventListener("click", closeRegisterModal);
|
||||
|
||||
Reference in New Issue
Block a user