fix: brought back postgress that we removed in the last commit, and added persistent folder for it
This commit is contained in:
@@ -0,0 +1,564 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Provisioning Wizard — Verify · Bellsystems Console</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="wizard.css">
|
||||
<style>
|
||||
/* Page-specific: Verify */
|
||||
.verify-main{
|
||||
display:grid;
|
||||
grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
|
||||
gap:20px;
|
||||
}
|
||||
|
||||
/* Left (serial logs) reuses console styles but a few local tweaks */
|
||||
.serial-console-card{
|
||||
display:flex;flex-direction:column;min-height:0;
|
||||
background:linear-gradient(180deg, #080b0e, #05070a);
|
||||
}
|
||||
.serial-filter-bar{
|
||||
display:flex;align-items:center;gap:6px;
|
||||
padding:8px 14px;
|
||||
border-bottom:1px solid var(--line);
|
||||
background:rgba(0,0,0,.25);
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
.serial-filter-bar .lvl{
|
||||
display:inline-flex;align-items:center;gap:6px;
|
||||
padding:4px 9px;border-radius:6px;
|
||||
font-family:var(--mono);font-size:10px;letter-spacing:.08em;
|
||||
background:var(--bg-3);border:1px solid var(--line);
|
||||
cursor:pointer;transition:.12s;text-transform:uppercase;
|
||||
color:var(--text-3);
|
||||
}
|
||||
.serial-filter-bar .lvl:hover{color:var(--text-1);border-color:var(--line-strong)}
|
||||
.serial-filter-bar .lvl.on{color:var(--text-0);background:var(--bg-2);border-color:var(--line-strong)}
|
||||
.serial-filter-bar .lvl .dt{width:6px;height:6px;border-radius:50%;background:currentColor}
|
||||
.serial-filter-bar .lvl.info .dt{color:#6aa9ff}
|
||||
.serial-filter-bar .lvl.warn .dt{color:var(--warn)}
|
||||
.serial-filter-bar .lvl.err .dt{color:var(--danger)}
|
||||
.serial-filter-bar .lvl.dbg .dt{color:#8a97a4}
|
||||
.serial-filter-bar .lvl.vrb .dt{color:#b48bff}
|
||||
.serial-filter-bar .lvl.ok .dt{color:var(--accent)}
|
||||
.serial-filter-bar .spacer{flex:1}
|
||||
.serial-filter-bar .auto{
|
||||
display:inline-flex;align-items:center;gap:8px;
|
||||
color:var(--text-3);font-family:var(--mono);font-size:10.5px;
|
||||
letter-spacing:.05em;text-transform:uppercase;
|
||||
padding:4px 10px;border-radius:6px;
|
||||
border:1px solid var(--line);background:var(--bg-3);cursor:pointer;
|
||||
}
|
||||
.serial-filter-bar .auto.on{color:var(--accent);border-color:rgba(34,224,122,.3);background:rgba(34,224,122,.06)}
|
||||
|
||||
/* Right panel */
|
||||
.verify-card{
|
||||
display:flex;flex-direction:column;
|
||||
background:linear-gradient(180deg, var(--bg-1), var(--bg-2) 60%);
|
||||
}
|
||||
.verify-body{padding:20px 22px;display:flex;flex-direction:column;gap:20px}
|
||||
|
||||
.sig-panel{
|
||||
padding:18px 20px;border:1px solid var(--line);border-radius:10px;
|
||||
background:rgba(255,255,255,.012);
|
||||
display:grid;grid-template-columns: 48px 1fr auto;align-items:center;gap:16px;
|
||||
position:relative;overflow:hidden;
|
||||
}
|
||||
.sig-panel.online{
|
||||
border-color:rgba(34,224,122,.32);
|
||||
background:
|
||||
radial-gradient(400px 120px at 20% 0%, rgba(34,224,122,.07), transparent 70%),
|
||||
rgba(34,224,122,.03);
|
||||
}
|
||||
.sig-panel .halo{
|
||||
width:48px;height:48px;border-radius:50%;
|
||||
background:var(--bg-3);border:1px solid var(--line);
|
||||
display:grid;place-items:center;color:var(--text-3);
|
||||
position:relative;
|
||||
}
|
||||
.sig-panel.online .halo{
|
||||
background:rgba(34,224,122,.08);border-color:rgba(34,224,122,.35);color:var(--accent);
|
||||
}
|
||||
.sig-panel.online .halo::before,
|
||||
.sig-panel.online .halo::after{
|
||||
content:"";position:absolute;inset:-4px;border-radius:50%;
|
||||
border:1px solid rgba(34,224,122,.4);
|
||||
animation:radar 2.2s infinite ease-out;
|
||||
}
|
||||
.sig-panel.online .halo::after{animation-delay:1.1s}
|
||||
@keyframes radar{
|
||||
0%{transform:scale(.6);opacity:.9}
|
||||
100%{transform:scale(1.6);opacity:0}
|
||||
}
|
||||
.sig-panel .halo svg{width:22px;height:22px}
|
||||
|
||||
.sig-panel .meta .k{
|
||||
font-size:10.5px;letter-spacing:.14em;color:var(--text-3);
|
||||
text-transform:uppercase;font-weight:600;
|
||||
}
|
||||
.sig-panel .meta .v{
|
||||
font-family:var(--mono);font-size:15px;color:var(--text-0);
|
||||
margin-top:4px;font-weight:500;
|
||||
}
|
||||
.sig-panel.online .meta .v{color:var(--accent)}
|
||||
.sig-panel .meta .sub{
|
||||
font-family:var(--mono);font-size:11px;color:var(--text-3);margin-top:3px;
|
||||
}
|
||||
|
||||
.sig-panel .status-pill{
|
||||
display:inline-flex;align-items:center;gap:8px;
|
||||
padding:6px 12px;border-radius:999px;
|
||||
font-family:var(--mono);font-size:10.5px;font-weight:600;
|
||||
letter-spacing:.1em;text-transform:uppercase;
|
||||
background:var(--bg-3);color:var(--text-2);border:1px solid var(--line);
|
||||
}
|
||||
.sig-panel .status-pill .dt{width:6px;height:6px;border-radius:50%;background:var(--text-3);animation:pulse 1.5s infinite}
|
||||
.sig-panel.online .status-pill{
|
||||
background:rgba(34,224,122,.1);color:var(--accent);border-color:rgba(34,224,122,.3);
|
||||
}
|
||||
.sig-panel.online .status-pill .dt{background:var(--accent);box-shadow:0 0 10px var(--accent)}
|
||||
|
||||
/* Checklist */
|
||||
.check-list{display:flex;flex-direction:column;gap:8px}
|
||||
.chk{
|
||||
display:grid;grid-template-columns: 28px 1fr auto;align-items:center;gap:12px;
|
||||
padding:12px 14px;border:1px solid var(--line);border-radius:10px;
|
||||
background:rgba(255,255,255,.012);
|
||||
}
|
||||
.chk .icon{
|
||||
width:24px;height:24px;border-radius:50%;
|
||||
border:1.5px solid var(--line-strong);background:var(--bg-3);
|
||||
display:grid;place-items:center;color:var(--text-3);
|
||||
}
|
||||
.chk.pass{border-color:rgba(34,224,122,.22);background:rgba(34,224,122,.04)}
|
||||
.chk.pass .icon{background:var(--accent);border-color:transparent;color:var(--accent-ink)}
|
||||
.chk.pend .icon{border-style:dashed}
|
||||
.chk.pend.active .icon{
|
||||
border-color:var(--warn);
|
||||
background:rgba(255,179,71,.1);
|
||||
color:var(--warn);
|
||||
animation:pulse 1.6s infinite;
|
||||
}
|
||||
.chk .label{font-size:13px;color:var(--text-0);font-weight:500}
|
||||
.chk .sub{font-size:11px;color:var(--text-3);font-family:var(--mono);margin-top:2px;letter-spacing:.03em}
|
||||
.chk.pend .label{color:var(--text-2)}
|
||||
.chk .time{
|
||||
font-family:var(--mono);font-size:10.5px;color:var(--text-3);
|
||||
letter-spacing:.05em;
|
||||
}
|
||||
.chk.pass .time{color:var(--accent)}
|
||||
|
||||
.mqtt-details{
|
||||
padding:12px 14px;border:1px solid var(--line);border-radius:10px;
|
||||
background:var(--bg-2);
|
||||
font-family:var(--mono);font-size:11.5px;line-height:1.7;
|
||||
color:var(--text-2);
|
||||
}
|
||||
.mqtt-details .row{display:flex;justify-content:space-between;gap:16px}
|
||||
.mqtt-details .row .k{color:var(--text-3);text-transform:uppercase;letter-spacing:.08em;font-size:10px}
|
||||
.mqtt-details .row .v{color:var(--text-0);font-weight:500}
|
||||
.mqtt-details .row .v.accent{color:var(--accent)}
|
||||
|
||||
.spinner{
|
||||
width:14px;height:14px;border-radius:50%;
|
||||
border:2px solid rgba(255,179,71,.2);
|
||||
border-top-color:var(--warn);
|
||||
animation:spin 0.8s linear infinite;
|
||||
}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
|
||||
@media (max-width:1100px){
|
||||
.verify-main{grid-template-columns:1fr}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="layout-split">
|
||||
|
||||
<div class="shell">
|
||||
|
||||
<!-- TOP BAR -->
|
||||
<div class="topbar">
|
||||
<div class="brand">
|
||||
<div class="logo" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="#22e07a" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M6 3h8l4 4v14H6z"></path>
|
||||
<path d="M14 3v4h4"></path>
|
||||
<path d="M10 12l-2 3h3l-1 3 3-4h-2l1-2z" fill="#22e07a" stroke="none"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="title">
|
||||
<h1>Provisioning Wizard</h1>
|
||||
<p>Verify the device is online and provisioned</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="top-actions" style="margin-top:2px">
|
||||
<a class="btn btn-ghost" href="Flashing.html">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5"></path><path d="M12 19l-7-7 7-7"></path></svg>
|
||||
Back to flash
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STEPPER -->
|
||||
<div class="stepper" role="list">
|
||||
<div class="step done" role="listitem">
|
||||
<div class="num"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg></div>
|
||||
<div class="meta"><span class="label">Mode</span><span class="sub">Provision</span></div>
|
||||
<div class="step-divider"></div>
|
||||
</div>
|
||||
<div class="step done" role="listitem">
|
||||
<div class="num"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg></div>
|
||||
<div class="meta"><span class="label">Device</span><span class="sub">BSVSPR-26D19J</span></div>
|
||||
<div class="step-divider"></div>
|
||||
</div>
|
||||
<div class="step done" role="listitem">
|
||||
<div class="num"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg></div>
|
||||
<div class="meta"><span class="label">Flash</span><span class="sub">complete</span></div>
|
||||
<div class="step-divider"></div>
|
||||
</div>
|
||||
<div class="step active" role="listitem">
|
||||
<div class="num">4</div>
|
||||
<div class="meta"><span class="label">Verify</span><span class="sub" id="verifySub">listening…</span></div>
|
||||
<div class="step-divider"></div>
|
||||
</div>
|
||||
<div class="step" role="listitem">
|
||||
<div class="num">5</div>
|
||||
<div class="meta"><span class="label">Done</span><span class="sub">pending</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MAIN (60 / 40) -->
|
||||
<div class="verify-main">
|
||||
|
||||
<!-- LEFT: Live Serial Logs -->
|
||||
<section class="card serial-console-card" aria-label="Live serial logs">
|
||||
<div class="console-head">
|
||||
<div class="tabs">
|
||||
<button class="active">Serial</button>
|
||||
<button>Timeline</button>
|
||||
<button>Raw</button>
|
||||
</div>
|
||||
<div class="tools">
|
||||
<button title="Pause/Resume" id="pauseBtn" aria-label="Pause">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>
|
||||
</button>
|
||||
<button title="Clear" id="clearBtn" aria-label="Clear">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"></path><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><path d="M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14"></path></svg>
|
||||
</button>
|
||||
<button title="Download logs" aria-label="Download">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter bar -->
|
||||
<div class="serial-filter-bar">
|
||||
<span class="lvl info on" data-lvl="info"><span class="dt"></span>Info</span>
|
||||
<span class="lvl ok on" data-lvl="ok"><span class="dt"></span>Ok</span>
|
||||
<span class="lvl warn on" data-lvl="warn"><span class="dt"></span>Warn</span>
|
||||
<span class="lvl err on" data-lvl="err"><span class="dt"></span>Error</span>
|
||||
<span class="lvl dbg" data-lvl="dbg"><span class="dt"></span>Debug</span>
|
||||
<span class="lvl vrb" data-lvl="vrb"><span class="dt"></span>Verbose</span>
|
||||
<div class="spacer"></div>
|
||||
<span class="auto on" id="autoscroll">
|
||||
<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"></path><path d="M19 12l-7 7-7-7"></path></svg>
|
||||
Auto-scroll
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="console-body" id="logBody"></div>
|
||||
|
||||
<div class="console-foot">
|
||||
<span><strong>/dev/cu.usbserial-0142A91B</strong> · 115200 8N1</span>
|
||||
<div class="stats">
|
||||
<span>RX <strong id="rxCount">0</strong> lines</span>
|
||||
<span>Uptime <strong id="uptime">00:00</strong></span>
|
||||
<span><span id="statusText">streaming…</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- RIGHT: Verification -->
|
||||
<section class="card verify-card" aria-label="Device verification">
|
||||
<div class="card-head">
|
||||
<h2><span class="dot"></span> Device Verification</h2>
|
||||
<span class="meta-inline" style="padding:0"><span class="k">STEP</span><span class="v">4 / 5</span></span>
|
||||
</div>
|
||||
|
||||
<div class="verify-body">
|
||||
|
||||
<!-- Signal panel (MQTT presence) -->
|
||||
<div class="sig-panel" id="sigPanel">
|
||||
<div class="halo">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M2 12a15 15 0 0 1 20 0"></path>
|
||||
<path d="M5 15.5a10 10 0 0 1 14 0"></path>
|
||||
<path d="M8.5 18.5a5 5 0 0 1 7 0"></path>
|
||||
<circle cx="12" cy="21" r="1" fill="currentColor"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<div class="k">MQTT Presence</div>
|
||||
<div class="v" id="sigLine">Waiting for device on mqtt.bellsystems.io…</div>
|
||||
<div class="sub" id="sigSub">client_id = BSVSPR-26D19J-STD10R-XSNBBP</div>
|
||||
</div>
|
||||
<span class="status-pill" id="sigStatus">
|
||||
<span class="dt"></span>
|
||||
<span id="sigStatusText">Awaiting</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Checklist -->
|
||||
<div class="check-list" id="checkList">
|
||||
<div class="chk pend" data-k="mqtt">
|
||||
<div class="icon">
|
||||
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"></circle></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Device connects to broker</div>
|
||||
<div class="sub">mqtt.bellsystems.io:8883 · TLS</div>
|
||||
</div>
|
||||
<span class="time" id="t-mqtt">—</span>
|
||||
</div>
|
||||
|
||||
<div class="chk pend" data-k="online">
|
||||
<div class="icon">
|
||||
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"></circle></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Publishes online message</div>
|
||||
<div class="sub">$bell/<serial>/status</div>
|
||||
</div>
|
||||
<span class="time" id="t-online">—</span>
|
||||
</div>
|
||||
|
||||
<div class="chk pend" data-k="prov">
|
||||
<div class="icon">
|
||||
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"></circle></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Inventory marked Provisioned</div>
|
||||
<div class="sub">api.bellsystems.io · PATCH /devices/<id></div>
|
||||
</div>
|
||||
<span class="time" id="t-prov">—</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MQTT details -->
|
||||
<div class="mqtt-details">
|
||||
<div class="row"><span class="k">Broker</span><span class="v">mqtt.bellsystems.io:8883</span></div>
|
||||
<div class="row"><span class="k">Client ID</span><span class="v">BSVSPR-26D19J-…XSNBBP</span></div>
|
||||
<div class="row"><span class="k">Last seen</span><span class="v" id="lastSeen">—</span></div>
|
||||
<div class="row"><span class="k">FW version</span><span class="v accent">v15.3.2</span></div>
|
||||
<div class="row"><span class="k">RSSI</span><span class="v" id="rssi">—</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action bar -->
|
||||
<div class="action-bar">
|
||||
<div class="meta-inline">
|
||||
<span class="k">STATE</span><span class="v" id="stateLabel">awaiting</span>
|
||||
<span class="sep">/</span>
|
||||
<span class="k">ELAPSED</span><span class="v" id="elapsed" style="font-variant-numeric:tabular-nums">0.0s</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:10px">
|
||||
<a class="btn btn-ghost" href="Flashing.html">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5"></path><path d="M12 19l-7-7 7-7"></path></svg>
|
||||
Back
|
||||
</a>
|
||||
<button class="btn btn-primary btn-lg" id="continueBtn" disabled style="opacity:.45;cursor:not-allowed">
|
||||
Continue
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"></path><path d="M12 5l7 7-7 7"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* -------- Log stream -------- */
|
||||
const SCRIPT = [
|
||||
['info','[boot]','ESP-IDF v5.1.2 · rst_reason=POWERON_RESET'],
|
||||
['dbg','[heap]','free=289212 largest_free=131072 min_free=288456'],
|
||||
['info','[main]','Starting Bell firmware v15.3.2 (build 4e29a1)'],
|
||||
['info','[nvs]','Opened namespace "factory" · 12 keys'],
|
||||
['dbg','[cfg]','Loaded model="vesper" rev="1.0" hw="ESP32-S3"'],
|
||||
['info','[wifi]','Connecting to SSID "bell-provision"…'],
|
||||
['dbg','[wifi]','Auth=WPA2 · channel=6 · bssid=a4:cf:12:xx:xx:xx'],
|
||||
['info','[wifi]','Got IP 10.24.100.87 · gw=10.24.100.1 · rssi=-54'],
|
||||
['ok','[wifi]','Associated · dhcp lease 24h'],
|
||||
['info','[tls]','Loading device cert from partition "cert_store"'],
|
||||
['dbg','[tls]','Cert CN=BSVSPR-26D19J-STD10R-XSNBBP · issuer=Bell-Root-R1'],
|
||||
['info','[mqtt]','Connecting mqtt.bellsystems.io:8883…'],
|
||||
['vrb','[tcp]','SYN sent · 35.209.44.12:8883'],
|
||||
['vrb','[tls]','Handshake: ECDHE-ECDSA-AES128-GCM-SHA256'],
|
||||
['ok','[mqtt]','CONNACK rc=0 · sessionPresent=false','__MARK_MQTT'],
|
||||
['info','[mqtt]','Subscribed $bell/+/cmd (QoS 1)'],
|
||||
['info','[pub]','PUBLISH $bell/<serial>/status {"state":"online","fw":"v15.3.2"}','__MARK_ONLINE'],
|
||||
['ok','[api]','POST /devices/<serial>/heartbeat → 200 OK'],
|
||||
['info','[api]','PATCH /devices/<serial> status=provisioned','__MARK_PROV'],
|
||||
['ok','[api]','Inventory: status ← provisioned · roleToken issued'],
|
||||
['dbg','[loop]','tick t=12.4s · rssi=-52 · heap=271340'],
|
||||
['info','[state]','Device ready · awaiting commissioning'],
|
||||
];
|
||||
|
||||
const body = document.getElementById('logBody');
|
||||
const filters = new Set(['info','ok','warn','err']); // default on
|
||||
let paused = false;
|
||||
let autoScroll = true;
|
||||
let rx = 0;
|
||||
const startTime = Date.now();
|
||||
|
||||
function fmtTime(ms){
|
||||
const t = (ms/1000);
|
||||
const mm = String(Math.floor(t/60)).padStart(2,'0');
|
||||
const ss = (t%60).toFixed(1).padStart(4,'0');
|
||||
return `${mm}:${ss}`;
|
||||
}
|
||||
|
||||
function colorize(msg){
|
||||
return msg
|
||||
.replace(/(\b0x[0-9a-f]+\b)/gi, '<span class="tok-hex">$1</span>')
|
||||
.replace(/(\b\d+\.\d+\.\d+\.\d+\b)/g, '<span class="tok-num">$1</span>')
|
||||
.replace(/(\b\d{2,}\b)/g, '<span class="tok-num">$1</span>')
|
||||
.replace(/"([^"]+)"/g, '"<span class="tok-em">$1</span>"')
|
||||
.replace(/\[([a-z]+)\]/g, '<span class="tok-mut">[</span><span class="tok-em">$1</span><span class="tok-mut">]</span>');
|
||||
}
|
||||
|
||||
function addLine(lvl, tag, msg){
|
||||
if (!filters.has(lvl)) return;
|
||||
const line = document.createElement('div');
|
||||
line.className = 'log-line';
|
||||
line.dataset.lvl = lvl;
|
||||
const now = new Date();
|
||||
const ts = `${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}:${String(now.getSeconds()).padStart(2,'0')}.${String(now.getMilliseconds()).padStart(3,'0')}`;
|
||||
line.innerHTML = `
|
||||
<span class="log-time">${ts}</span>
|
||||
<span class="log-level ${lvl}">${lvl.toUpperCase()}</span>
|
||||
<span class="log-msg"><span class="tok-mut">${tag}</span> ${colorize(msg)}</span>`;
|
||||
body.appendChild(line);
|
||||
rx++;
|
||||
document.getElementById('rxCount').textContent = rx;
|
||||
if (autoScroll) body.scrollTop = body.scrollHeight;
|
||||
}
|
||||
|
||||
/* Filter toggles */
|
||||
document.querySelectorAll('.serial-filter-bar .lvl').forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
el.classList.toggle('on');
|
||||
const k = el.dataset.lvl;
|
||||
if (el.classList.contains('on')) filters.add(k); else filters.delete(k);
|
||||
// Re-apply to existing lines
|
||||
body.querySelectorAll('.log-line').forEach(line => {
|
||||
line.style.display = filters.has(line.dataset.lvl) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* Autoscroll toggle */
|
||||
document.getElementById('autoscroll').addEventListener('click', () => {
|
||||
autoScroll = !autoScroll;
|
||||
document.getElementById('autoscroll').classList.toggle('on', autoScroll);
|
||||
});
|
||||
body.addEventListener('scroll', () => {
|
||||
// Turn off autoscroll if user scrolls up significantly
|
||||
const atBottom = body.scrollHeight - body.clientHeight - body.scrollTop < 20;
|
||||
if (!atBottom && autoScroll){
|
||||
autoScroll = false;
|
||||
document.getElementById('autoscroll').classList.remove('on');
|
||||
}
|
||||
});
|
||||
|
||||
/* Pause / clear */
|
||||
document.getElementById('pauseBtn').addEventListener('click', () => {
|
||||
paused = !paused;
|
||||
document.getElementById('statusText').textContent = paused ? 'paused' : 'streaming…';
|
||||
});
|
||||
document.getElementById('clearBtn').addEventListener('click', () => {
|
||||
body.innerHTML = ''; rx = 0; document.getElementById('rxCount').textContent = 0;
|
||||
});
|
||||
|
||||
/* -------- Verification state machine -------- */
|
||||
function markChk(k, ts){
|
||||
const el = document.querySelector(`.chk[data-k="${k}"]`);
|
||||
if (!el) return;
|
||||
el.classList.remove('pend','active');
|
||||
el.classList.add('pass');
|
||||
el.querySelector('.icon').innerHTML = '<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
|
||||
document.getElementById('t-'+k).textContent = ts;
|
||||
}
|
||||
function activateChk(k){
|
||||
const el = document.querySelector(`.chk[data-k="${k}"]`);
|
||||
if (!el) return;
|
||||
el.classList.add('active');
|
||||
el.querySelector('.icon').innerHTML = '<div class="spinner"></div>';
|
||||
}
|
||||
|
||||
activateChk('mqtt');
|
||||
document.getElementById('stateLabel').textContent = 'listening for broker connect';
|
||||
|
||||
/* -------- Play the script -------- */
|
||||
let idx = 0;
|
||||
function step(){
|
||||
if (paused){ setTimeout(step, 300); return; }
|
||||
if (idx >= SCRIPT.length){
|
||||
document.getElementById('statusText').textContent = 'idle';
|
||||
return;
|
||||
}
|
||||
const [lvl, tag, msg, mark] = SCRIPT[idx++];
|
||||
addLine(lvl, tag, msg);
|
||||
const elapsed = ((Date.now()-startTime)/1000).toFixed(1) + 's';
|
||||
|
||||
if (mark === '__MARK_MQTT'){
|
||||
markChk('mqtt', elapsed);
|
||||
activateChk('online');
|
||||
document.getElementById('stateLabel').textContent = 'broker ok · awaiting publish';
|
||||
// Signal panel flips online
|
||||
const sig = document.getElementById('sigPanel');
|
||||
sig.classList.add('online');
|
||||
document.getElementById('sigLine').textContent = 'Device online on mqtt.bellsystems.io';
|
||||
document.getElementById('sigStatusText').textContent = 'Online';
|
||||
document.getElementById('verifySub').textContent = 'online';
|
||||
document.getElementById('lastSeen').textContent = 'just now';
|
||||
document.getElementById('rssi').textContent = '-52 dBm';
|
||||
} else if (mark === '__MARK_ONLINE'){
|
||||
markChk('online', elapsed);
|
||||
activateChk('prov');
|
||||
document.getElementById('stateLabel').textContent = 'updating inventory';
|
||||
} else if (mark === '__MARK_PROV'){
|
||||
markChk('prov', elapsed);
|
||||
document.getElementById('stateLabel').textContent = 'provisioned';
|
||||
document.getElementById('sigStatusText').textContent = 'Provisioned';
|
||||
document.getElementById('verifySub').textContent = 'provisioned';
|
||||
const btn = document.getElementById('continueBtn');
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1'; btn.style.cursor = 'pointer';
|
||||
btn.classList.add('ok');
|
||||
}
|
||||
|
||||
setTimeout(step, 220 + Math.random()*320);
|
||||
}
|
||||
setTimeout(step, 400);
|
||||
|
||||
/* Elapsed & uptime tickers */
|
||||
setInterval(() => {
|
||||
const el = Date.now() - startTime;
|
||||
document.getElementById('elapsed').textContent = (el/1000).toFixed(1) + 's';
|
||||
document.getElementById('uptime').textContent = fmtTime(el);
|
||||
}, 100);
|
||||
|
||||
/* Continue → Done (not implemented page; go back to Flashing for now) */
|
||||
document.getElementById('continueBtn').addEventListener('click', () => {
|
||||
if (document.getElementById('continueBtn').disabled) return;
|
||||
alert('Verification complete → proceed to final confirmation (not implemented in this prototype).');
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user