Prepare Soundbox Ops deployment
This commit is contained in:
@ -15,34 +15,38 @@
|
||||
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 999px; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen bg-slate-50 text-slate-950">
|
||||
<aside class="fixed inset-y-0 left-0 z-40 hidden w-64 border-r border-slate-200 bg-white px-4 py-6 lg:flex lg:flex-col">
|
||||
<body id="top" class="min-h-screen bg-slate-50 text-slate-950">
|
||||
<aside class="fixed inset-y-0 left-0 z-50 hidden w-64 border-r border-slate-200 bg-white px-4 py-6 lg:flex lg:flex-col">
|
||||
<div class="px-2">
|
||||
<h1 class="text-xl font-extrabold text-blue-700">Soundbox Ops</h1>
|
||||
<p class="mt-1 text-xs font-semibold uppercase text-slate-500">Monitoring Console</p>
|
||||
<h1 class="text-[22px] font-extrabold leading-tight text-blue-700">Soundbox Ops</h1>
|
||||
<p class="mt-1 text-[12px] font-bold uppercase leading-none text-slate-500">Monitoring Console</p>
|
||||
</div>
|
||||
<nav class="mt-8 flex flex-1 flex-col gap-1">
|
||||
<a class="flex items-center gap-3 rounded-lg bg-blue-50 px-3 py-2 font-bold text-blue-700" href="/ui/soundbox-ops">
|
||||
<span class="material-symbols-outlined">monitor_heart</span>
|
||||
Soundbox Monitoring
|
||||
<a class="flex h-11 items-center gap-3 rounded-lg bg-blue-50 px-3 text-[15px] font-semibold leading-none text-blue-700 transition-colors" href="/ui/soundbox-ops">
|
||||
<span class="material-symbols-outlined shrink-0 text-[22px]">monitor_heart</span>
|
||||
<span class="truncate">Monitoring</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 rounded-lg px-3 py-2 text-slate-600 hover:bg-slate-100" href="/ui/device-registry-monitoring">
|
||||
<span class="material-symbols-outlined">speaker_group</span>
|
||||
Device Registry
|
||||
<a class="flex h-11 items-center gap-3 rounded-lg px-3 text-[15px] font-semibold leading-none text-slate-600 transition-colors hover:bg-slate-100 hover:text-blue-700" href="/ui/device-registry-monitoring">
|
||||
<span class="material-symbols-outlined shrink-0 text-[22px]">speaker_group</span>
|
||||
<span class="truncate">Registry</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 rounded-lg px-3 py-2 text-slate-600 hover:bg-slate-100" href="/ui/transaction-history-monitoring">
|
||||
<span class="material-symbols-outlined">receipt_long</span>
|
||||
Transactions
|
||||
<a class="flex h-11 items-center gap-3 rounded-lg px-3 text-[15px] font-semibold leading-none text-slate-600 transition-colors hover:bg-slate-100 hover:text-blue-700" href="#mqtt-trace">
|
||||
<span class="material-symbols-outlined shrink-0 text-[22px]">lan</span>
|
||||
<span class="truncate">MQTT Trace</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 rounded-lg px-3 py-2 text-slate-600 hover:bg-slate-100" href="/ui/admin-dashboard-overview">
|
||||
<span class="material-symbols-outlined">dashboard</span>
|
||||
Admin Overview
|
||||
<a class="flex h-11 items-center gap-3 rounded-lg px-3 text-[15px] font-semibold leading-none text-slate-600 transition-colors hover:bg-slate-100 hover:text-blue-700" href="#config-commands">
|
||||
<span class="material-symbols-outlined shrink-0 text-[22px]">settings_remote</span>
|
||||
<span class="truncate">Config & Commands</span>
|
||||
</a>
|
||||
<a class="flex h-11 items-center gap-3 rounded-lg px-3 text-[15px] font-semibold leading-none text-slate-600 transition-colors hover:bg-slate-100 hover:text-blue-700" href="/ui/soundbox-catalog">
|
||||
<span class="material-symbols-outlined shrink-0 text-[22px]">category</span>
|
||||
<span class="truncate">Catalog</span>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="border-t border-slate-200 pt-4">
|
||||
<button id="logout-button" class="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-left text-slate-600 hover:bg-slate-100">
|
||||
<span class="material-symbols-outlined">logout</span>
|
||||
Logout
|
||||
<button id="logout-button" class="flex h-11 w-full items-center gap-3 rounded-lg px-3 text-left text-[15px] font-semibold leading-none text-slate-600 transition-colors hover:bg-slate-100 hover:text-blue-700">
|
||||
<span class="material-symbols-outlined shrink-0 text-[22px]">logout</span>
|
||||
<span class="truncate">Logout</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
@ -124,7 +128,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid gap-6 xl:grid-cols-[minmax(0,1.5fr)_minmax(360px,0.8fr)]">
|
||||
<section class="rounded-lg border border-slate-200 bg-white">
|
||||
<section id="fleet-status" class="rounded-lg border border-slate-200 bg-white">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-slate-200 px-5 py-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-extrabold">Fleet Status</h3>
|
||||
@ -155,10 +159,10 @@
|
||||
</section>
|
||||
|
||||
<aside class="space-y-6">
|
||||
<section class="rounded-lg border border-slate-200 bg-white">
|
||||
<section id="config-commands" class="rounded-lg border border-slate-200 bg-white transition-shadow">
|
||||
<div class="border-b border-slate-200 px-5 py-4">
|
||||
<h3 class="text-lg font-extrabold">Operations Health</h3>
|
||||
<p id="ops-generated" class="mt-1 text-sm text-slate-500">Waiting for summary</p>
|
||||
<h3 class="text-lg font-extrabold">Config & Commands</h3>
|
||||
<p id="ops-generated" class="mt-1 text-sm text-slate-500">Broker, config worker, and notification state</p>
|
||||
</div>
|
||||
<div class="space-y-3 p-5">
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50 px-4 py-3">
|
||||
@ -180,7 +184,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-lg border border-slate-200 bg-white">
|
||||
<section id="mqtt-trace" class="rounded-lg border border-slate-200 bg-white transition-shadow">
|
||||
<div class="border-b border-slate-200 px-5 py-4">
|
||||
<h3 class="text-lg font-extrabold">Recent MQTT Trace</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">latest uplink and downlink records</p>
|
||||
@ -199,6 +203,9 @@
|
||||
(function () {
|
||||
const api = window.AdminUIAPI;
|
||||
const state = { devices: [], merchants: new Map(), mqtt: null, observability: null };
|
||||
const isPreviewMode =
|
||||
new URLSearchParams(window.location.search).get("preview") === "1" ||
|
||||
((window.location.hostname === "127.0.0.1" || window.location.hostname === "localhost") && window.location.port === "4173");
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const normalize = (value) => String(value || "").toLowerCase().trim();
|
||||
@ -369,9 +376,124 @@
|
||||
renderMqtt();
|
||||
}
|
||||
|
||||
function focusSection(id) {
|
||||
const target = document.getElementById(id);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
target.classList.add("ring-2", "ring-blue-500", "ring-offset-2");
|
||||
window.setTimeout(() => {
|
||||
target.classList.remove("ring-2", "ring-blue-500", "ring-offset-2");
|
||||
}, 1400);
|
||||
}
|
||||
|
||||
function loadPreviewData() {
|
||||
const now = Date.now();
|
||||
state.merchants = new Map([
|
||||
["merchant_mbiz", "MBiz Jakarta"],
|
||||
["merchant_demo", "Demo Mart"],
|
||||
["merchant_qf100", "QF100 Pilot Store"]
|
||||
]);
|
||||
state.devices = [
|
||||
{
|
||||
id: "dev_qf100_static_01",
|
||||
device_code: "QF100-STATIC-01",
|
||||
serial_number: "SN-QF100-0001",
|
||||
vendor: "QF100",
|
||||
model: "QF100 Static",
|
||||
communication_mode: "mqtt",
|
||||
derived_status: "online",
|
||||
latest_heartbeat: new Date(now - 90 * 1000).toISOString(),
|
||||
health_summary: { score: 98 },
|
||||
binding_summary: { merchant_id: "merchant_qf100" }
|
||||
},
|
||||
{
|
||||
id: "dev_qf100_dynamic_01",
|
||||
device_code: "QF100-DYN-01",
|
||||
serial_number: "SN-QF100-0002",
|
||||
vendor: "QF100",
|
||||
model: "QF100 Dynamic",
|
||||
communication_mode: "mqtt",
|
||||
derived_status: "stale",
|
||||
latest_heartbeat: new Date(now - 42 * 60 * 1000).toISOString(),
|
||||
health_summary: { score: 64 },
|
||||
binding_summary: { merchant_id: "merchant_mbiz" }
|
||||
},
|
||||
{
|
||||
id: "dev_counter_03",
|
||||
device_code: "SND-COUNTER-03",
|
||||
serial_number: "SN-DEMO-0003",
|
||||
vendor: "Generic",
|
||||
model: "Soundbox V2",
|
||||
communication_mode: "api",
|
||||
derived_status: "degraded",
|
||||
latest_heartbeat: new Date(now - 12 * 60 * 1000).toISOString(),
|
||||
health_summary: { score: 72 },
|
||||
binding_summary: { merchant_id: "merchant_demo" }
|
||||
},
|
||||
{
|
||||
id: "dev_stock_04",
|
||||
device_code: "SND-STOCK-04",
|
||||
serial_number: "SN-DEMO-0004",
|
||||
vendor: "Generic",
|
||||
model: "Unassigned Stock",
|
||||
communication_mode: "static",
|
||||
derived_status: "offline",
|
||||
latest_heartbeat: null,
|
||||
health_summary: { score: 0 },
|
||||
binding_summary: null
|
||||
}
|
||||
];
|
||||
state.mqtt = {
|
||||
publisher: {
|
||||
mode: "broker",
|
||||
connected: true,
|
||||
broker_url: "mqtts://broker.bizone.id:8883"
|
||||
},
|
||||
subscriber: {
|
||||
connected: true
|
||||
},
|
||||
last_messages: [
|
||||
{
|
||||
direction: "downlink",
|
||||
topic: "devices/dev_qf100_static_01/downlink/qf100",
|
||||
message_type: "payment_success",
|
||||
publish_status: "sent",
|
||||
created_at: new Date(now - 75 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
direction: "uplink",
|
||||
topic: "devices/dev_qf100_dynamic_01/uplink/dynamic-qr/request",
|
||||
message_type: "dynamic_qr_request",
|
||||
publish_status: "recorded",
|
||||
created_at: new Date(now - 7 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
direction: "downlink",
|
||||
topic: "devices/dev_counter_03/downlink/config/push",
|
||||
message_type: "config_push",
|
||||
publish_status: "sent",
|
||||
created_at: new Date(now - 18 * 60 * 1000).toISOString()
|
||||
}
|
||||
]
|
||||
};
|
||||
state.observability = {
|
||||
generated_at: new Date().toISOString(),
|
||||
database: { status: "ok" },
|
||||
notifications: { pending_count: 1, failed_count: 0 },
|
||||
export_jobs: { worker: { enabled: true } }
|
||||
};
|
||||
renderAll();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const error = $("error-banner");
|
||||
error.classList.add("hidden");
|
||||
if (isPreviewMode && !api.getToken()) {
|
||||
loadPreviewData();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
api.requireToken();
|
||||
const [devices, merchants, mqtt, observability] = await Promise.all([
|
||||
@ -402,8 +524,22 @@
|
||||
api.clearToken();
|
||||
window.location.href = "/ui/admin-login";
|
||||
});
|
||||
document.querySelectorAll("a[href^='#']").forEach((link) => {
|
||||
link.addEventListener("click", (event) => {
|
||||
const id = link.getAttribute("href").slice(1);
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
history.replaceState(null, "", `#${id}`);
|
||||
focusSection(id);
|
||||
});
|
||||
});
|
||||
|
||||
refresh();
|
||||
if (window.location.hash) {
|
||||
window.setTimeout(() => focusSection(window.location.hash.slice(1)), 250);
|
||||
}
|
||||
window.setInterval(refresh, 30000);
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user