Spaces:
Running
chore(sync): mirror front-door files to Space (hf-sync)
Browse filesAutomated front-door sync from szl-holdings/a11oy main via hf-sync.
Added/updated: cathedral.html, cathedral_genius.html, console/docs.html, console/index.html, console/pricing.html, console/throne-room.html, console/throne-room.js, live_wires.html, pages/api-keys.html, pages/assurance.html, pages/audit.html, pages/ayni.html, pages/brain-dual.html, pages/brain-jack.html, pages/brain.html, pages/chaski.html, pages/codex-kernel.html, pages/company.html, pages/compliance.html, pages/console.html, pages/counter-uas.html, pages/cued-engagement.html, pages/docs.html, pages/energy-ops.html, pages/evidence.html, pages/fabric.html, pages/gap-report.html, pages/harvest.html, pages/hatun-mcp.html, pages/hub.html, pages/integrations.html, pages/landing.html, pages/mesh.html, pages/observability.html, pages/operator_organ.html, pages/pinn.html, pages/pnt.html, pages/pricing.html, pages/run-all.html, pages/sdk.html, pages/security.html, pages/status.html, pages/substrate.html, pages/superpowers.html, pages/throne-room.html, pages/throne-room.js, pages/uds.html, pages/upgrades.html, pages/verify.html, pages/wallpa.html, pages/warhacker.html, pages/wasi-rikuq.html, pages/wires.html, static/a11oy_cathedral.js, static/cathedral_app.js, static/shared/szl_codename_sanitizer.js, static/shared/szl_holo3d.js, static/shared/szl_label_engine.js, static/shared/szl_receipt_cosign.js, web/agent-loop.html, web/agentic-gpu.html, web/autoreview.html, web/console.html, web/console_index.html, web/constitution.html, web/defense-readiness.html, web/determinacy.html, web/dns.html, web/elite_console.html, web/energy-holographic.html, web/energy.html, web/estate-hologram.html, web/fleet-c2.html, web/formulas.html, web/governance.html, web/holo.html, web/hologram.html, web/immune.html, web/index.html, web/living-anatomy.html, web/materials.html, web/nemo.html, web/operator.html, web/quant.html, web/restraint-bench.html, web/restraint.html, web/sda.html, web/signature-is-not-proof.html, web/v4_fleet_panel.html
Deleted (gone from GitHub main): (none)
Keeps the served front-door (pages/*.html, console/*.html) identical
to GitHub main so an HF factory rebuild never drops a GitHub edit or
keeps serving a page that was deleted on GitHub.
- pages/energy-ops.html +475 -305
|
@@ -1,72 +1,139 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<!--
|
| 3 |
-
SZL Energy —
|
| 4 |
-
Doctrine v11: every value carries
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
GET /api/a11oy/v1/energy/operator/status (node status, running flag)
|
| 13 |
GET /api/a11oy/v1/energy/ledger (signed JouleCharge receipts)
|
| 14 |
GET /api/a11oy/v1/energy/projection?window=running (1-day MODELED headline)
|
| 15 |
GET /api/a11oy/v1/harvest/posture (grid price + negative window)
|
|
|
|
|
|
|
| 16 |
-->
|
| 17 |
<html lang="en">
|
| 18 |
<head>
|
| 19 |
<meta charset="utf-8" />
|
| 20 |
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 21 |
-
<title>SZL Energy —
|
|
|
|
| 22 |
<style>
|
| 23 |
/* System fonts only — 0 CDN, 0 webfont. */
|
| 24 |
:root {
|
| 25 |
--bg:#070b12; --panel:#0d141f; --panel2:#111b29; --line:#1c2a3c;
|
| 26 |
--ink:#e8eef7; --dim:#8aa0bd; --gold:#d4a444; --green:#34d399;
|
| 27 |
--red:#f87171; --amber:#fbbf24; --blue:#60a5fa; --mag:#c084fc;
|
|
|
|
| 28 |
--mono: ui-monospace, "SF Mono", "Cascadia Mono", "Segoe UI Mono", Menlo, Consolas, monospace;
|
| 29 |
--sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
| 30 |
}
|
| 31 |
* { box-sizing:border-box; }
|
| 32 |
html,body { margin:0; background:var(--bg); color:var(--ink); font-family:var(--sans); }
|
| 33 |
body { padding:18px clamp(12px,3vw,34px) 60px; }
|
| 34 |
-
a { color:var(--
|
| 35 |
-
header.top { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; margin-bottom:14px;
|
|
|
|
| 36 |
.brand { display:flex; align-items:baseline; gap:12px; }
|
| 37 |
.brand h1 { font-size:clamp(18px,2.4vw,26px); margin:0; letter-spacing:.5px; }
|
|
|
|
| 38 |
.brand .sub { color:var(--dim); font-size:13px; }
|
|
|
|
|
|
|
|
|
|
| 39 |
.conn { font-family:var(--mono); font-size:12px; color:var(--dim); }
|
| 40 |
-
.conn b { color:var(--
|
| 41 |
.conn.degraded b { color:var(--amber); }
|
| 42 |
|
| 43 |
-
/* honesty chip —
|
| 44 |
.chip {
|
| 45 |
display:inline-block; font-family:var(--mono); font-size:10px; font-weight:700;
|
| 46 |
letter-spacing:.6px; padding:2px 6px; border-radius:4px; vertical-align:middle;
|
| 47 |
border:1px solid currentColor; line-height:1.2; text-transform:uppercase;
|
| 48 |
}
|
| 49 |
-
.chip-
|
| 50 |
-
.chip-
|
| 51 |
-
.chip-
|
| 52 |
-
.chip-
|
| 53 |
-
.chip-
|
| 54 |
-
.chip-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
.grid { display:grid; gap:14px; grid-template-columns:repeat(12,1fr); }
|
| 57 |
-
.card { background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:16px 18px; }
|
| 58 |
.card h2 { margin:0 0 10px; font-size:12px; letter-spacing:1.4px; text-transform:uppercase; color:var(--gold); font-weight:700; }
|
| 59 |
.span12{grid-column:span 12} .span8{grid-column:span 8} .span6{grid-column:span 6}
|
| 60 |
.span5{grid-column:span 5} .span4{grid-column:span 4} .span7{grid-column:span 7}
|
| 61 |
@media (max-width:880px){ .span4,.span5,.span6,.span7,.span8{grid-column:span 12} }
|
| 62 |
|
| 63 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
.playwrap { display:flex; align-items:center; gap:18px; flex-wrap:wrap; }
|
| 65 |
.playbtn {
|
| 66 |
appearance:none; border:none; cursor:pointer; font-family:var(--sans);
|
| 67 |
font-weight:800; font-size:22px; letter-spacing:1px; color:#06210f;
|
| 68 |
-
background:linear-gradient(180deg,#
|
| 69 |
-
padding:20px 34px; box-shadow:0 0 0 1px #
|
| 70 |
transition:transform .08s ease, filter .15s ease; min-width:230px;
|
| 71 |
}
|
| 72 |
.playbtn:hover{ filter:brightness(1.06); }
|
|
@@ -74,11 +141,10 @@
|
|
| 74 |
.playbtn.stop { background:linear-gradient(180deg,#ff8b8b,#e2554f); color:#2a0606; box-shadow:0 0 0 1px #5a1414, 0 8px 26px rgba(248,113,113,.28); }
|
| 75 |
.playbtn[disabled]{ opacity:.55; cursor:wait; }
|
| 76 |
.playstate { font-family:var(--mono); font-size:13px; color:var(--dim); }
|
| 77 |
-
.
|
| 78 |
-
.
|
| 79 |
-
.
|
| 80 |
|
| 81 |
-
/* THE BIG NUMBER */
|
| 82 |
.bignum { font-family:var(--mono); font-weight:800; line-height:1; }
|
| 83 |
.bignum .val { font-size:clamp(34px,7vw,76px); color:var(--green); letter-spacing:-1px; }
|
| 84 |
.bignum .unit { font-size:18px; color:var(--dim); margin-left:8px; }
|
|
@@ -110,7 +176,6 @@
|
|
| 110 |
.neg-banner.show{ display:inline-flex; animation:pulse 1.6s ease-in-out infinite; }
|
| 111 |
@keyframes pulse{ 0%,100%{filter:brightness(1)} 50%{filter:brightness(1.35)} }
|
| 112 |
|
| 113 |
-
/* RECEIPT FEED */
|
| 114 |
.feed { max-height:340px; overflow:auto; border:1px solid var(--line); border-radius:10px; }
|
| 115 |
table.feed-t { width:100%; border-collapse:collapse; font-family:var(--mono); font-size:12px; }
|
| 116 |
table.feed-t th { position:sticky; top:0; background:var(--panel2); color:var(--dim); text-align:left; padding:8px 10px; font-weight:700; letter-spacing:.5px; border-bottom:1px solid var(--line); }
|
|
@@ -120,48 +185,106 @@
|
|
| 120 |
.digest { color:var(--dim); }
|
| 121 |
.cents { color:var(--amber); font-weight:700; }
|
| 122 |
|
| 123 |
-
.empty { color:var(--dim); font-family:var(--mono); font-size:13px; padding:14px; }
|
| 124 |
.foot { margin-top:22px; color:var(--dim); font-size:11px; font-family:var(--mono); line-height:1.7; }
|
| 125 |
.foot b{ color:var(--ink); }
|
| 126 |
|
| 127 |
-
/*
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
.chart-empty { position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
|
| 132 |
-
color:var(--dim); font-family:var(--mono); font-size:12px; pointer-events:none; text-align:center; padding:0 12px; }
|
| 133 |
-
.chart-empty.hide { display:none; }
|
| 134 |
-
.chart-read { position:absolute; top:6px; right:12px; font-family:var(--mono); font-weight:800;
|
| 135 |
-
font-size:22px; color:var(--green); text-shadow:0 0 14px rgba(52,211,153,.35); }
|
| 136 |
-
.chart-read .cu { font-size:12px; color:var(--dim); margin-left:3px; font-weight:700; }
|
| 137 |
-
.chart-axis { display:flex; justify-content:space-between; align-items:center; margin-top:7px;
|
| 138 |
-
font-family:var(--mono); font-size:10px; color:var(--dim); letter-spacing:.4px; }
|
| 139 |
-
.chart-axis .mid { color:#6b809c; }
|
| 140 |
-
.gaugewrap { position:relative; width:100%; height:210px; }
|
| 141 |
-
.gauge-read { position:absolute; left:0; right:0; bottom:30px; text-align:center;
|
| 142 |
-
font-family:var(--mono); font-weight:800; font-size:30px; color:var(--green); }
|
| 143 |
-
.gauge-read .cu { font-size:13px; color:var(--dim); margin-left:4px; font-weight:700; }
|
| 144 |
-
.chart-sub { text-align:center; color:var(--dim); font-family:var(--mono); font-size:11px; margin-top:6px; }
|
| 145 |
</style>
|
| 146 |
</head>
|
| 147 |
<body>
|
| 148 |
<header class="top">
|
| 149 |
<div class="brand">
|
| 150 |
-
<h1>SZL ENERGY · <span
|
| 151 |
-
<span class="sub">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
</div>
|
| 153 |
-
<div class="conn" id="conn">link <b id="conn-state">…</b> · <span id="conn-ts">—</span></div>
|
| 154 |
</header>
|
| 155 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
<div class="grid">
|
| 157 |
|
| 158 |
-
<!-- PRESS PLAY + THE BIG NUMBER -->
|
| 159 |
<section class="card span7">
|
| 160 |
<h2>Operator</h2>
|
| 161 |
<div class="playwrap">
|
| 162 |
<button class="playbtn" id="play" aria-pressed="false">PRESS PLAY</button>
|
| 163 |
<div class="playstate">
|
| 164 |
-
<div><span class="
|
| 165 |
<div id="run-detail" style="margin-top:6px;color:var(--dim)">—</div>
|
| 166 |
</div>
|
| 167 |
</div>
|
|
@@ -179,45 +302,6 @@
|
|
| 179 |
</div>
|
| 180 |
</section>
|
| 181 |
|
| 182 |
-
<!-- ===== LIVE GRAPHS (founder #1: measured joules climbing) ===== -->
|
| 183 |
-
<section class="card span8">
|
| 184 |
-
<h2>Live · measured joules climbing <span class="chip chip-measured">MEASURED</span></h2>
|
| 185 |
-
<div class="chartwrap">
|
| 186 |
-
<canvas id="chart-joules"></canvas>
|
| 187 |
-
<div class="chart-empty" id="chart-joules-empty">awaiting first live sample…</div>
|
| 188 |
-
<div class="chart-read"><span id="chart-joules-val">—</span><span class="cu">J</span></div>
|
| 189 |
-
</div>
|
| 190 |
-
<div class="chart-axis"><span id="chart-joules-lo">—</span><span class="mid">cumulative on-box NVML energy · live every 3s</span><span id="chart-joules-hi">—</span></div>
|
| 191 |
-
</section>
|
| 192 |
-
|
| 193 |
-
<section class="card span4">
|
| 194 |
-
<h2>GPU power now <span class="chip chip-measured">MEASURED</span></h2>
|
| 195 |
-
<div class="gaugewrap">
|
| 196 |
-
<canvas id="gauge-power"></canvas>
|
| 197 |
-
<div class="gauge-read"><span id="gauge-power-val">—</span><span class="cu">W</span></div>
|
| 198 |
-
</div>
|
| 199 |
-
<div class="chart-sub" id="gauge-power-sub">computing node —</div>
|
| 200 |
-
</section>
|
| 201 |
-
|
| 202 |
-
<section class="card span6">
|
| 203 |
-
<h2>GPU power trend <span class="chip chip-measured">MEASURED</span></h2>
|
| 204 |
-
<div class="chartwrap sm">
|
| 205 |
-
<canvas id="chart-power"></canvas>
|
| 206 |
-
<div class="chart-empty" id="chart-power-empty">awaiting first live sample…</div>
|
| 207 |
-
<div class="chart-read"><span id="chart-power-val">—</span><span class="cu">W</span></div>
|
| 208 |
-
</div>
|
| 209 |
-
</section>
|
| 210 |
-
|
| 211 |
-
<section class="card span6">
|
| 212 |
-
<h2>Throughput · tokens / sec <span class="chip chip-measured">MEASURED</span></h2>
|
| 213 |
-
<div class="chartwrap sm">
|
| 214 |
-
<canvas id="chart-tps"></canvas>
|
| 215 |
-
<div class="chart-empty" id="chart-tps-empty">measuring rate from live deltas…</div>
|
| 216 |
-
<div class="chart-read"><span id="chart-tps-val">—</span><span class="cu">tok/s</span></div>
|
| 217 |
-
</div>
|
| 218 |
-
</section>
|
| 219 |
-
|
| 220 |
-
<!-- TODAY: compute done + earnings -->
|
| 221 |
<section class="card span7">
|
| 222 |
<h2>Today · compute done & earnings</h2>
|
| 223 |
<div class="metrics">
|
|
@@ -252,16 +336,14 @@
|
|
| 252 |
<div class="lbl"><span class="chip chip-zero" id="m-real-chip">ZERO</span></div>
|
| 253 |
</div>
|
| 254 |
</div>
|
| 255 |
-
<div style="margin-top:10px;color:var(--dim);font-size:11px;font-family:var(--mono)" id="earn-formula">earnings = MODELED dry-run ledger total + projection
|
| 256 |
</section>
|
| 257 |
|
| 258 |
-
<!-- NODE STATUS -->
|
| 259 |
<section class="card span5">
|
| 260 |
<h2>Nodes computing now</h2>
|
| 261 |
<div class="nodes" id="nodes"><div class="empty">querying operator status…</div></div>
|
| 262 |
</section>
|
| 263 |
|
| 264 |
-
<!-- GRID STRIP -->
|
| 265 |
<section class="card span12">
|
| 266 |
<h2>Grid</h2>
|
| 267 |
<div class="grid-strip">
|
|
@@ -275,7 +357,6 @@
|
|
| 275 |
</div>
|
| 276 |
</section>
|
| 277 |
|
| 278 |
-
<!-- RECEIPT FEED -->
|
| 279 |
<section class="card span12">
|
| 280 |
<h2>Live receipt feed · SZL.Energy.JouleCharge.v1 <span class="chip chip-modeled" id="feed-mode">DRY-RUN</span></h2>
|
| 281 |
<div class="feed">
|
|
@@ -289,47 +370,306 @@
|
|
| 289 |
</div>
|
| 290 |
|
| 291 |
<div class="foot">
|
| 292 |
-
<div><b>Honesty (Doctrine v11):</b>
|
| 293 |
-
<div>No live data → panels show <span class="chip chip-degraded">DEGRADED</span> / NO-LIVE-DATA.
|
| 294 |
</div>
|
| 295 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
<script>
|
| 297 |
"use strict";
|
|
|
|
|
|
|
|
|
|
| 298 |
(function () {
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
if (!el) return;
|
| 306 |
-
var key = String(label || "DEGRADED").toUpperCase();
|
| 307 |
-
var cls = CHIP[key] || "chip-degraded";
|
| 308 |
-
el.className = "chip " + cls;
|
| 309 |
-
el.textContent = key;
|
| 310 |
}
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
}
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
}
|
| 330 |
-
var poll = getPoller();
|
| 331 |
|
| 332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
function Counter(el, opts) {
|
| 334 |
opts = opts || {};
|
| 335 |
this.el = el; this.cur = 0; this.target = 0; this.decimals = opts.decimals || 0;
|
|
@@ -338,44 +678,25 @@
|
|
| 338 |
Counter.prototype.set = function (v) {
|
| 339 |
if (typeof v !== "number" || !isFinite(v)) return;
|
| 340 |
this.target = v;
|
| 341 |
-
if (!this.started) { this.cur = v; this.started = true; this.render(); }
|
| 342 |
};
|
| 343 |
Counter.prototype.tick = function () {
|
| 344 |
if (!this.started) return;
|
| 345 |
var d = this.target - this.cur;
|
| 346 |
-
if (Math.abs(d) < Math.pow(10, -this.decimals) / 2) { this.cur = this.target; }
|
| 347 |
else { this.cur += d * 0.18; }
|
| 348 |
this.render();
|
| 349 |
};
|
| 350 |
-
Counter.prototype.render = function () {
|
| 351 |
-
this.el.textContent = fmt(this.cur, this.decimals);
|
| 352 |
-
};
|
| 353 |
-
function fmt(n, dec) {
|
| 354 |
-
if (typeof n !== "number" || !isFinite(n)) return "—";
|
| 355 |
-
return n.toLocaleString("en-US", { minimumFractionDigits: dec, maximumFractionDigits: dec });
|
| 356 |
-
}
|
| 357 |
|
| 358 |
var bigJoules = new Counter(document.getElementById("big-joules"), { decimals: 3 });
|
| 359 |
var mTokens = new Counter(document.getElementById("m-tokens"), { decimals: 0 });
|
| 360 |
var mJobs = new Counter(document.getElementById("m-jobs"), { decimals: 0 });
|
| 361 |
var mJoules = new Counter(document.getElementById("m-joules"), { decimals: 1 });
|
| 362 |
-
setInterval(function () { bigJoules.tick(); mTokens.tick(); mJobs.tick(); mJoules.tick(); }, 33);
|
| 363 |
-
|
| 364 |
-
// ---- connection banner -------------------------------------------------
|
| 365 |
-
var anyLive = false;
|
| 366 |
-
function markLive(ok) {
|
| 367 |
-
anyLive = anyLive || ok;
|
| 368 |
-
var conn = document.getElementById("conn");
|
| 369 |
-
var st = document.getElementById("conn-state");
|
| 370 |
-
if (ok) { conn.classList.remove("degraded"); st.textContent = "LIVE"; }
|
| 371 |
-
else if (!anyLive) { conn.classList.add("degraded"); st.textContent = "NO-LIVE-DATA"; }
|
| 372 |
-
document.getElementById("conn-ts").textContent = new Date().toLocaleTimeString();
|
| 373 |
-
}
|
| 374 |
-
|
| 375 |
-
function isErr(d) { return !d || d.__error === true; }
|
| 376 |
|
| 377 |
// ===== OPERATOR: PLAY / STOP + status ==================================
|
| 378 |
-
var running = null;
|
| 379 |
var playBtn = document.getElementById("play");
|
| 380 |
var busy = false;
|
| 381 |
|
|
@@ -384,7 +705,7 @@
|
|
| 384 |
var txt = document.getElementById("run-text");
|
| 385 |
var det = document.getElementById("run-detail");
|
| 386 |
if (isErr(status)) {
|
| 387 |
-
running = null; dot.className = "
|
| 388 |
txt.textContent = "operator DEGRADED — NO-LIVE-DATA";
|
| 389 |
det.innerHTML = '<span class="chip chip-degraded">DEGRADED</span> operator status unreachable';
|
| 390 |
playBtn.textContent = "PRESS PLAY"; playBtn.classList.remove("stop");
|
|
@@ -392,7 +713,7 @@
|
|
| 392 |
return;
|
| 393 |
}
|
| 394 |
running = (status.running === true) || (status.state === "running") || (status.status === "running");
|
| 395 |
-
dot.className = "
|
| 396 |
if (running) {
|
| 397 |
txt.textContent = "RUNNING — computing non-stop";
|
| 398 |
playBtn.textContent = "STOP"; playBtn.classList.add("stop");
|
|
@@ -415,7 +736,7 @@
|
|
| 415 |
}
|
| 416 |
|
| 417 |
playBtn.addEventListener("click", function () {
|
| 418 |
-
if (busy
|
| 419 |
var action = running ? "stop" : "start";
|
| 420 |
busy = true; playBtn.disabled = true;
|
| 421 |
poll(API + "/energy/operator/" + action, { method: "POST", body: "{}" })
|
|
@@ -426,7 +747,6 @@
|
|
| 426 |
'<span class="chip chip-degraded">DEGRADED</span> ' + action + " failed (operator unreachable)";
|
| 427 |
return;
|
| 428 |
}
|
| 429 |
-
// optimistic; status poll will confirm
|
| 430 |
running = (action === "start");
|
| 431 |
refreshStatus();
|
| 432 |
});
|
|
@@ -444,8 +764,6 @@
|
|
| 444 |
box.innerHTML = '<div class="empty">no node telemetry — <span class="chip chip-degraded">DEGRADED</span></div>';
|
| 445 |
return;
|
| 446 |
}
|
| 447 |
-
// operator/status contract: node_status {name:state}, by_node {name:{jobs,joules,tokens}},
|
| 448 |
-
// plus nodes_computing / nodes_standby / nodes_degraded arrays.
|
| 449 |
var ns = (status.node_status && typeof status.node_status === "object") ? status.node_status : null;
|
| 450 |
var byNode = status.by_node || {};
|
| 451 |
var names = ns ? Object.keys(ns) : [];
|
|
@@ -494,13 +812,11 @@
|
|
| 494 |
}
|
| 495 |
var receipts = d.receipts || d.entries || d.ledger || [];
|
| 496 |
var dryRunTotal = 0, anyCharged = false;
|
| 497 |
-
// dry-run mode chip
|
| 498 |
var mode = (String(d.stripe_mode || d.mode || "").toLowerCase() === "live") ? "LIVE" : "DRY-RUN";
|
| 499 |
var feedMode = document.getElementById("feed-mode");
|
| 500 |
feedMode.textContent = mode;
|
| 501 |
feedMode.className = "chip " + (mode === "LIVE" ? "chip-measured" : "chip-modeled");
|
| 502 |
|
| 503 |
-
// render newest first
|
| 504 |
var rows = receipts.slice().reverse();
|
| 505 |
if (rows.length && body.querySelector(".empty")) body.innerHTML = "";
|
| 506 |
rows.forEach(function (r) {
|
|
@@ -533,9 +849,6 @@
|
|
| 533 |
mReal.textContent = "$" + (realCents / 100).toFixed(2);
|
| 534 |
setChip(document.getElementById("m-real-chip"), realCents > 0 ? "MEASURED" : "ZERO");
|
| 535 |
mReal.style.color = realCents > 0 ? "var(--green)" : "var(--dim)";
|
| 536 |
-
|
| 537 |
-
// chain integrity note (honest)
|
| 538 |
-
// (display omitted from UI chrome; integrity reflected by DEGRADED if absent)
|
| 539 |
});
|
| 540 |
}
|
| 541 |
|
|
@@ -558,7 +871,6 @@
|
|
| 558 |
if (tokens != null) mTokens.set(tokens);
|
| 559 |
if (jobs != null) mJobs.set(jobs);
|
| 560 |
if (joules != null) { mJoules.set(joules); bigJoules.set(joules); }
|
| 561 |
-
pushEnergySamples(d);
|
| 562 |
});
|
| 563 |
}
|
| 564 |
|
|
@@ -574,7 +886,6 @@
|
|
| 574 |
var joules = num((cd.joules && cd.joules.value != null) ? cd.joules.value
|
| 575 |
: (p.joules && p.joules.value != null ? p.joules.value
|
| 576 |
: (p.joules != null ? p.joules : (p.joules_projected != null ? p.joules_projected : p.joules_1d))));
|
| 577 |
-
// headline dollar = compute_resale (ESTIMATE: assumed ¢/kWh), per API honesty contract
|
| 578 |
var earn = p.earnings || {};
|
| 579 |
var resale = earn.compute_resale_usd || {};
|
| 580 |
var earnDollars = num(resale.value != null ? resale.value
|
|
@@ -611,7 +922,6 @@
|
|
| 611 |
else {
|
| 612 |
priceEl.textContent = price.toFixed(2) + " EUR/MWh";
|
| 613 |
priceEl.style.color = price < 0 ? "var(--green)" : "var(--ink)";
|
| 614 |
-
// grid price reading off-box is SAMPLE per posture contract
|
| 615 |
setChip(chip, (d.measured_any ? "MEASURED" : "SAMPLE"));
|
| 616 |
banner.classList.toggle("show", price < 0);
|
| 617 |
}
|
|
@@ -636,146 +946,6 @@
|
|
| 636 |
return null;
|
| 637 |
}
|
| 638 |
|
| 639 |
-
// ---- small utils -------------------------------------------------------
|
| 640 |
-
function num(v) { var n = typeof v === "number" ? v : parseFloat(v); return isFinite(n) ? n : null; }
|
| 641 |
-
function esc(s) { return String(s == null ? "" : s).replace(/[&<>"]/g, function (c) {
|
| 642 |
-
return { "&":"&","<":"<",">":">",'"':""" }[c]; }); }
|
| 643 |
-
function shortDigest(d) { if (!d) return "—"; var h = String(d).replace(/^sha256:/, ""); return h.slice(0, 12) + "…"; }
|
| 644 |
-
function shortTime(t) { if (!t) return "—"; try { return new Date(t).toLocaleTimeString(); } catch (e) { return String(t); } }
|
| 645 |
-
|
| 646 |
-
// ===== LIVE CHARTS (vanilla canvas, DPR-aware, 0 CDN) =================
|
| 647 |
-
function LiveChart(id, opts) {
|
| 648 |
-
opts = opts || {};
|
| 649 |
-
this.c = document.getElementById(id);
|
| 650 |
-
this.ctx = this.c ? this.c.getContext("2d") : null;
|
| 651 |
-
this.empty = opts.empty ? document.getElementById(opts.empty) : null;
|
| 652 |
-
this.valEl = opts.val ? document.getElementById(opts.val) : null;
|
| 653 |
-
this.loEl = opts.lo ? document.getElementById(opts.lo) : null;
|
| 654 |
-
this.hiEl = opts.hi ? document.getElementById(opts.hi) : null;
|
| 655 |
-
this.pts = [];
|
| 656 |
-
this.max = opts.max || 180;
|
| 657 |
-
this.color = opts.color || "#34d399";
|
| 658 |
-
this.decimals = opts.decimals != null ? opts.decimals : 0;
|
| 659 |
-
this.minSpan = opts.minSpan || 0;
|
| 660 |
-
this.fixedMin = opts.fixedMin;
|
| 661 |
-
}
|
| 662 |
-
LiveChart.prototype.push = function (v) {
|
| 663 |
-
if (typeof v !== "number" || !isFinite(v)) return;
|
| 664 |
-
this.pts.push(v);
|
| 665 |
-
if (this.pts.length > this.max) this.pts.shift();
|
| 666 |
-
if (this.empty) this.empty.classList.add("hide");
|
| 667 |
-
if (this.valEl) this.valEl.textContent = fmt(v, this.decimals);
|
| 668 |
-
this.draw();
|
| 669 |
-
};
|
| 670 |
-
LiveChart.prototype.draw = function () {
|
| 671 |
-
var c = this.c, ctx = this.ctx;
|
| 672 |
-
if (!ctx) return;
|
| 673 |
-
var dpr = window.devicePixelRatio || 1;
|
| 674 |
-
var w = c.clientWidth || 600, h = c.clientHeight || 200;
|
| 675 |
-
if (c.width !== Math.round(w * dpr) || c.height !== Math.round(h * dpr)) {
|
| 676 |
-
c.width = Math.round(w * dpr); c.height = Math.round(h * dpr);
|
| 677 |
-
}
|
| 678 |
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
| 679 |
-
ctx.clearRect(0, 0, w, h);
|
| 680 |
-
var padL = 8, padR = 8, padT = 14, padB = 10;
|
| 681 |
-
var plotW = w - padL - padR, plotH = h - padT - padB;
|
| 682 |
-
ctx.strokeStyle = "rgba(255,255,255,0.05)"; ctx.lineWidth = 1;
|
| 683 |
-
for (var g = 0; g <= 4; g++) {
|
| 684 |
-
var gy = padT + plotH * g / 4;
|
| 685 |
-
ctx.beginPath(); ctx.moveTo(padL, gy); ctx.lineTo(w - padR, gy); ctx.stroke();
|
| 686 |
-
}
|
| 687 |
-
var pts = this.pts;
|
| 688 |
-
if (pts.length < 2) return;
|
| 689 |
-
var mn = Math.min.apply(null, pts), mx = Math.max.apply(null, pts);
|
| 690 |
-
if (this.fixedMin != null) mn = Math.min(mn, this.fixedMin);
|
| 691 |
-
if (mx - mn < this.minSpan) mx = mn + this.minSpan;
|
| 692 |
-
if (mx === mn) mx = mn + 1;
|
| 693 |
-
var n = pts.length;
|
| 694 |
-
function X(i) { return padL + plotW * (i / (n - 1)); }
|
| 695 |
-
function Y(v) { return padT + plotH * (1 - (v - mn) / (mx - mn)); }
|
| 696 |
-
var grad = ctx.createLinearGradient(0, padT, 0, padT + plotH);
|
| 697 |
-
grad.addColorStop(0, this.color + "55");
|
| 698 |
-
grad.addColorStop(1, this.color + "08");
|
| 699 |
-
ctx.beginPath();
|
| 700 |
-
ctx.moveTo(X(0), Y(pts[0]));
|
| 701 |
-
for (var i = 1; i < n; i++) ctx.lineTo(X(i), Y(pts[i]));
|
| 702 |
-
ctx.lineTo(X(n - 1), padT + plotH);
|
| 703 |
-
ctx.lineTo(X(0), padT + plotH);
|
| 704 |
-
ctx.closePath(); ctx.fillStyle = grad; ctx.fill();
|
| 705 |
-
ctx.beginPath();
|
| 706 |
-
ctx.moveTo(X(0), Y(pts[0]));
|
| 707 |
-
for (var j = 1; j < n; j++) ctx.lineTo(X(j), Y(pts[j]));
|
| 708 |
-
ctx.strokeStyle = this.color; ctx.lineWidth = 2;
|
| 709 |
-
ctx.lineJoin = "round"; ctx.stroke();
|
| 710 |
-
ctx.beginPath();
|
| 711 |
-
ctx.arc(X(n - 1), Y(pts[n - 1]), 3, 0, Math.PI * 2);
|
| 712 |
-
ctx.fillStyle = this.color; ctx.fill();
|
| 713 |
-
if (this.loEl) this.loEl.textContent = fmt(mn, this.decimals);
|
| 714 |
-
if (this.hiEl) this.hiEl.textContent = fmt(mx, this.decimals);
|
| 715 |
-
};
|
| 716 |
-
|
| 717 |
-
function Gauge(id, opts) {
|
| 718 |
-
opts = opts || {};
|
| 719 |
-
this.c = document.getElementById(id);
|
| 720 |
-
this.ctx = this.c ? this.c.getContext("2d") : null;
|
| 721 |
-
this.valEl = opts.val ? document.getElementById(opts.val) : null;
|
| 722 |
-
this.max = opts.max || 120; this.cur = 0;
|
| 723 |
-
}
|
| 724 |
-
Gauge.prototype.set = function (v) {
|
| 725 |
-
if (typeof v !== "number" || !isFinite(v)) return;
|
| 726 |
-
this.cur = v; if (this.valEl) this.valEl.textContent = fmt(v, 1);
|
| 727 |
-
this.draw();
|
| 728 |
-
};
|
| 729 |
-
Gauge.prototype.draw = function () {
|
| 730 |
-
var c = this.c, ctx = this.ctx; if (!ctx) return;
|
| 731 |
-
var dpr = window.devicePixelRatio || 1;
|
| 732 |
-
var w = c.clientWidth || 240, h = c.clientHeight || 200;
|
| 733 |
-
if (c.width !== Math.round(w * dpr) || c.height !== Math.round(h * dpr)) {
|
| 734 |
-
c.width = Math.round(w * dpr); c.height = Math.round(h * dpr);
|
| 735 |
-
}
|
| 736 |
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
| 737 |
-
ctx.clearRect(0, 0, w, h);
|
| 738 |
-
var cx = w / 2, cy = h * 0.70, r = Math.min(w * 0.40, h * 0.56);
|
| 739 |
-
var a0 = Math.PI * 0.85, a1 = Math.PI * 2.15;
|
| 740 |
-
ctx.lineWidth = 14; ctx.lineCap = "round";
|
| 741 |
-
ctx.strokeStyle = "#16202e";
|
| 742 |
-
ctx.beginPath(); ctx.arc(cx, cy, r, a0, a1); ctx.stroke();
|
| 743 |
-
var frac = Math.max(0, Math.min(1, this.cur / this.max));
|
| 744 |
-
var grad = ctx.createLinearGradient(cx - r, 0, cx + r, 0);
|
| 745 |
-
grad.addColorStop(0, "#34d399"); grad.addColorStop(1, "#fbbf24");
|
| 746 |
-
ctx.strokeStyle = grad;
|
| 747 |
-
ctx.beginPath(); ctx.arc(cx, cy, r, a0, a0 + (a1 - a0) * frac); ctx.stroke();
|
| 748 |
-
};
|
| 749 |
-
|
| 750 |
-
var chJoules = new LiveChart("chart-joules", { empty:"chart-joules-empty", val:"chart-joules-val", lo:"chart-joules-lo", hi:"chart-joules-hi", color:"#34d399", decimals:0, minSpan:1 });
|
| 751 |
-
var chPower = new LiveChart("chart-power", { empty:"chart-power-empty", val:"chart-power-val", color:"#60a5fa", decimals:1, fixedMin:0, minSpan:5 });
|
| 752 |
-
var chTps = new LiveChart("chart-tps", { empty:"chart-tps-empty", val:"chart-tps-val", color:"#c084fc", decimals:1, fixedMin:0, minSpan:1 });
|
| 753 |
-
var gPower = new Gauge("gauge-power", { val:"gauge-power-val", max:120 });
|
| 754 |
-
var tpsState = { lastTokens:null, lastTs:null };
|
| 755 |
-
|
| 756 |
-
function pushEnergySamples(d) {
|
| 757 |
-
if (isErr(d)) return;
|
| 758 |
-
var j = num(d.joules_measured_total != null ? d.joules_measured_total : d.joules_measured);
|
| 759 |
-
if (j != null) chJoules.push(j);
|
| 760 |
-
var pw = num(d.power_w_sample);
|
| 761 |
-
if (pw != null) { chPower.push(pw); gPower.set(pw); }
|
| 762 |
-
var nodeName = (d.nodes_computing && d.nodes_computing[0]) || "—";
|
| 763 |
-
var sub = document.getElementById("gauge-power-sub");
|
| 764 |
-
if (sub) sub.textContent = "computing node " + nodeName;
|
| 765 |
-
var tk = num(d.tokens_total != null ? d.tokens_total : d.tokens_measured);
|
| 766 |
-
var now = Date.now();
|
| 767 |
-
if (tk != null) {
|
| 768 |
-
if (tpsState.lastTokens != null && tpsState.lastTs != null && now > tpsState.lastTs) {
|
| 769 |
-
var rate = (tk - tpsState.lastTokens) / ((now - tpsState.lastTs) / 1000);
|
| 770 |
-
if (rate >= 0 && isFinite(rate)) chTps.push(rate);
|
| 771 |
-
}
|
| 772 |
-
tpsState.lastTokens = tk; tpsState.lastTs = now;
|
| 773 |
-
}
|
| 774 |
-
}
|
| 775 |
-
window.addEventListener("resize", function () {
|
| 776 |
-
chJoules.draw(); chPower.draw(); chTps.draw(); gPower.draw();
|
| 777 |
-
});
|
| 778 |
-
|
| 779 |
// ---- poll loop ---------------------------------------------------------
|
| 780 |
function tickFast() { refreshStatus(); refreshToday(); refreshLedger(); }
|
| 781 |
function tickSlow() { refreshProjection(); refreshGrid(); }
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<!--
|
| 3 |
+
SZL Energy — Live Energy Dashboard (/energy-ops), leader-grade & bound to REAL data.
|
| 4 |
+
Doctrine v11: every value carries an honest label chip
|
| 5 |
+
(LIVE / MEASURED / SAMPLE / ESTIMATE / MODELED / UNAVAILABLE / ZERO / DEGRADED).
|
| 6 |
+
Joules are MEASURED only from a reachable on-box NVML exporter — never fabricated.
|
| 7 |
+
Revenue is NEVER shown MEASURED until a real charge clears. 0 runtime CDN,
|
| 8 |
+
system fonts only. Degrades honestly to NO-LIVE-DATA / UNAVAILABLE on 404/error.
|
| 9 |
+
|
| 10 |
+
Tri-row live surface (DCGM / Kepler / Scaphandre patterns, reimplemented):
|
| 11 |
+
(1) live power gauge + per-node cards GET /api/a11oy/v1/energy/live
|
| 12 |
+
(2) streaming watts line chart (uPlot) GET /api/a11oy/v1/energy/live (poll 2s)
|
| 13 |
+
(3) Bekenstein budget ledger table GET /api/a11oy/v1/energy/budget (F19/TH6)
|
| 14 |
+
mesh node health GET /api/a11oy/v1/govern/health
|
| 15 |
+
|
| 16 |
+
Press-play operator console (live operator control + signed receipt feed):
|
| 17 |
+
POST /api/a11oy/v1/energy/operator/start (press play)
|
| 18 |
+
POST /api/a11oy/v1/energy/operator/stop (stop)
|
| 19 |
GET /api/a11oy/v1/energy/operator/status (node status, running flag)
|
| 20 |
GET /api/a11oy/v1/energy/ledger (signed JouleCharge receipts)
|
| 21 |
GET /api/a11oy/v1/energy/projection?window=running (1-day MODELED headline)
|
| 22 |
GET /api/a11oy/v1/harvest/posture (grid price + negative window)
|
| 23 |
+
|
| 24 |
+
uPlot v1.6.32 (MIT, Leon Sorokin) vendored 0-CDN at /vendor/uPlot.* .
|
| 25 |
-->
|
| 26 |
<html lang="en">
|
| 27 |
<head>
|
| 28 |
<meta charset="utf-8" />
|
| 29 |
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 30 |
+
<title>SZL Energy — Live Energy Dashboard</title>
|
| 31 |
+
<link rel="stylesheet" href="/vendor/uPlot.min.css" />
|
| 32 |
<style>
|
| 33 |
/* System fonts only — 0 CDN, 0 webfont. */
|
| 34 |
:root {
|
| 35 |
--bg:#070b12; --panel:#0d141f; --panel2:#111b29; --line:#1c2a3c;
|
| 36 |
--ink:#e8eef7; --dim:#8aa0bd; --gold:#d4a444; --green:#34d399;
|
| 37 |
--red:#f87171; --amber:#fbbf24; --blue:#60a5fa; --mag:#c084fc;
|
| 38 |
+
--teal:#39d8c8; /* holo-teal — the energy surface signature */
|
| 39 |
--mono: ui-monospace, "SF Mono", "Cascadia Mono", "Segoe UI Mono", Menlo, Consolas, monospace;
|
| 40 |
--sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
| 41 |
}
|
| 42 |
* { box-sizing:border-box; }
|
| 43 |
html,body { margin:0; background:var(--bg); color:var(--ink); font-family:var(--sans); }
|
| 44 |
body { padding:18px clamp(12px,3vw,34px) 60px; }
|
| 45 |
+
a { color:var(--teal); }
|
| 46 |
+
header.top { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; margin-bottom:14px;
|
| 47 |
+
border-bottom:1px solid var(--line); padding-bottom:14px; }
|
| 48 |
.brand { display:flex; align-items:baseline; gap:12px; }
|
| 49 |
.brand h1 { font-size:clamp(18px,2.4vw,26px); margin:0; letter-spacing:.5px; }
|
| 50 |
+
.brand .tl { color:var(--teal); text-shadow:0 0 18px rgba(57,216,200,.35); }
|
| 51 |
.brand .sub { color:var(--dim); font-size:13px; }
|
| 52 |
+
.topright { display:flex; align-items:center; gap:14px; flex-wrap:wrap; }
|
| 53 |
+
.mesh-pill { font-family:var(--mono); font-size:12px; color:var(--dim); }
|
| 54 |
+
.mesh-pill b.up { color:var(--green); } .mesh-pill b.down { color:var(--red); }
|
| 55 |
.conn { font-family:var(--mono); font-size:12px; color:var(--dim); }
|
| 56 |
+
.conn b { color:var(--teal); }
|
| 57 |
.conn.degraded b { color:var(--amber); }
|
| 58 |
|
| 59 |
+
/* honesty chip — multi-state (replicates szl3d_label) */
|
| 60 |
.chip {
|
| 61 |
display:inline-block; font-family:var(--mono); font-size:10px; font-weight:700;
|
| 62 |
letter-spacing:.6px; padding:2px 6px; border-radius:4px; vertical-align:middle;
|
| 63 |
border:1px solid currentColor; line-height:1.2; text-transform:uppercase;
|
| 64 |
}
|
| 65 |
+
.chip-live { color:var(--teal); }
|
| 66 |
+
.chip-measured { color:var(--green); }
|
| 67 |
+
.chip-modeled { color:var(--amber); }
|
| 68 |
+
.chip-sample { color:var(--blue); }
|
| 69 |
+
.chip-estimate { color:var(--mag); }
|
| 70 |
+
.chip-zero { color:var(--dim); }
|
| 71 |
+
.chip-degraded { color:var(--red); }
|
| 72 |
+
.chip-unavailable { color:var(--red); }
|
| 73 |
+
|
| 74 |
+
.secthead { display:flex; align-items:center; gap:10px; margin:22px 0 12px; }
|
| 75 |
+
.secthead .n { font-family:var(--mono); font-size:11px; color:var(--teal); border:1px solid var(--teal);
|
| 76 |
+
border-radius:5px; padding:2px 7px; opacity:.85; }
|
| 77 |
+
.secthead h2 { margin:0; font-size:13px; letter-spacing:1.6px; text-transform:uppercase; color:var(--ink); font-weight:700; }
|
| 78 |
+
.secthead .sub { color:var(--dim); font-size:12px; font-family:var(--mono); }
|
| 79 |
|
| 80 |
.grid { display:grid; gap:14px; grid-template-columns:repeat(12,1fr); }
|
| 81 |
+
.card { background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:16px 18px; min-width:0; }
|
| 82 |
.card h2 { margin:0 0 10px; font-size:12px; letter-spacing:1.4px; text-transform:uppercase; color:var(--gold); font-weight:700; }
|
| 83 |
.span12{grid-column:span 12} .span8{grid-column:span 8} .span6{grid-column:span 6}
|
| 84 |
.span5{grid-column:span 5} .span4{grid-column:span 4} .span7{grid-column:span 7}
|
| 85 |
@media (max-width:880px){ .span4,.span5,.span6,.span7,.span8{grid-column:span 12} }
|
| 86 |
|
| 87 |
+
/* total-power gauge */
|
| 88 |
+
.gaugewrap { position:relative; width:100%; height:210px; }
|
| 89 |
+
.gaugewrap canvas { width:100%; height:100%; display:block; }
|
| 90 |
+
.gauge-read { position:absolute; left:0; right:0; bottom:30px; text-align:center;
|
| 91 |
+
font-family:var(--mono); font-weight:800; font-size:34px; color:var(--teal); text-shadow:0 0 16px rgba(57,216,200,.35); }
|
| 92 |
+
.gauge-read.off { color:var(--red); text-shadow:none; }
|
| 93 |
+
.gauge-read .cu { font-size:13px; color:var(--dim); margin-left:4px; font-weight:700; }
|
| 94 |
+
.gauge-sub { text-align:center; color:var(--dim); font-family:var(--mono); font-size:11px; margin-top:6px; }
|
| 95 |
+
|
| 96 |
+
/* per-node cards */
|
| 97 |
+
.nodegrid { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:12px; }
|
| 98 |
+
.ncard { background:var(--panel2); border:1px solid var(--line); border-radius:10px; padding:13px 14px; position:relative; overflow:hidden; }
|
| 99 |
+
.ncard.live { border-color:#1f5e54; box-shadow:0 0 0 1px #14463f inset; }
|
| 100 |
+
.ncard.down { border-color:#5a2222; opacity:.86; }
|
| 101 |
+
.ncard .nm { font-family:var(--mono); font-weight:700; font-size:13px; display:flex; align-items:center; gap:7px; word-break:break-word; }
|
| 102 |
+
.ncard .watt { font-family:var(--mono); font-weight:800; font-size:30px; margin-top:8px; color:var(--teal); }
|
| 103 |
+
.ncard.down .watt { color:var(--dim); }
|
| 104 |
+
.ncard .watt .u { font-size:13px; color:var(--dim); margin-left:3px; font-weight:700; }
|
| 105 |
+
.ncard .meta { color:var(--dim); font-family:var(--mono); font-size:11px; margin-top:6px; line-height:1.6; }
|
| 106 |
+
.dot { display:inline-block; width:9px; height:9px; border-radius:50%; background:var(--dim); flex:0 0 auto; }
|
| 107 |
+
.dot.on { background:var(--teal); box-shadow:0 0 8px var(--teal); }
|
| 108 |
+
.dot.off{ background:var(--red); }
|
| 109 |
+
|
| 110 |
+
/* streaming uPlot chart */
|
| 111 |
+
#watts-chart { width:100%; }
|
| 112 |
+
.u-legend { color:var(--dim); font-family:var(--mono); font-size:11px; }
|
| 113 |
+
.chart-note { display:flex; align-items:center; gap:10px; margin-top:8px; font-family:var(--mono); font-size:11px; color:var(--dim); }
|
| 114 |
+
.offline-tag { display:none; align-items:center; gap:7px; color:var(--red); font-weight:700; }
|
| 115 |
+
.offline-tag.show { display:inline-flex; }
|
| 116 |
+
.chart-empty { color:var(--dim); font-family:var(--mono); font-size:12px; padding:18px 4px; }
|
| 117 |
+
|
| 118 |
+
/* budget ledger table */
|
| 119 |
+
.budgetbar { display:flex; align-items:center; gap:14px; flex-wrap:wrap; margin-bottom:10px; font-family:var(--mono); font-size:12px; color:var(--dim); }
|
| 120 |
+
.budgetbar .k { color:var(--dim); } .budgetbar .v { color:var(--ink); font-weight:700; }
|
| 121 |
+
.gate-ok { color:var(--green); font-weight:700; } .gate-bad { color:var(--red); font-weight:700; }
|
| 122 |
+
.tbl-wrap { max-height:340px; overflow:auto; border:1px solid var(--line); border-radius:10px; }
|
| 123 |
+
table.tbl { width:100%; border-collapse:collapse; font-family:var(--mono); font-size:12px; }
|
| 124 |
+
table.tbl th { position:sticky; top:0; background:var(--panel2); color:var(--dim); text-align:left; padding:8px 10px; font-weight:700; letter-spacing:.5px; border-bottom:1px solid var(--line); }
|
| 125 |
+
table.tbl td { padding:7px 10px; border-bottom:1px solid #16202e; }
|
| 126 |
+
table.tbl tr:hover td { background:#0f1a28; }
|
| 127 |
+
.within-y { color:var(--green); } .within-n { color:var(--red); }
|
| 128 |
+
.empty { color:var(--dim); font-family:var(--mono); font-size:13px; padding:16px; }
|
| 129 |
+
|
| 130 |
+
/* ===== PRESS-PLAY OPERATOR CONSOLE ===== */
|
| 131 |
.playwrap { display:flex; align-items:center; gap:18px; flex-wrap:wrap; }
|
| 132 |
.playbtn {
|
| 133 |
appearance:none; border:none; cursor:pointer; font-family:var(--sans);
|
| 134 |
font-weight:800; font-size:22px; letter-spacing:1px; color:#06210f;
|
| 135 |
+
background:linear-gradient(180deg,#5fe6cf,#2bb8a4); border-radius:14px;
|
| 136 |
+
padding:20px 34px; box-shadow:0 0 0 1px #0a3b34, 0 8px 26px rgba(57,216,200,.28);
|
| 137 |
transition:transform .08s ease, filter .15s ease; min-width:230px;
|
| 138 |
}
|
| 139 |
.playbtn:hover{ filter:brightness(1.06); }
|
|
|
|
| 141 |
.playbtn.stop { background:linear-gradient(180deg,#ff8b8b,#e2554f); color:#2a0606; box-shadow:0 0 0 1px #5a1414, 0 8px 26px rgba(248,113,113,.28); }
|
| 142 |
.playbtn[disabled]{ opacity:.55; cursor:wait; }
|
| 143 |
.playstate { font-family:var(--mono); font-size:13px; color:var(--dim); }
|
| 144 |
+
.rdot { display:inline-block; width:10px; height:10px; border-radius:50%; margin-right:6px; vertical-align:middle; background:var(--dim); }
|
| 145 |
+
.rdot.on { background:var(--green); box-shadow:0 0 8px var(--green); }
|
| 146 |
+
.rdot.off{ background:var(--red); }
|
| 147 |
|
|
|
|
| 148 |
.bignum { font-family:var(--mono); font-weight:800; line-height:1; }
|
| 149 |
.bignum .val { font-size:clamp(34px,7vw,76px); color:var(--green); letter-spacing:-1px; }
|
| 150 |
.bignum .unit { font-size:18px; color:var(--dim); margin-left:8px; }
|
|
|
|
| 176 |
.neg-banner.show{ display:inline-flex; animation:pulse 1.6s ease-in-out infinite; }
|
| 177 |
@keyframes pulse{ 0%,100%{filter:brightness(1)} 50%{filter:brightness(1.35)} }
|
| 178 |
|
|
|
|
| 179 |
.feed { max-height:340px; overflow:auto; border:1px solid var(--line); border-radius:10px; }
|
| 180 |
table.feed-t { width:100%; border-collapse:collapse; font-family:var(--mono); font-size:12px; }
|
| 181 |
table.feed-t th { position:sticky; top:0; background:var(--panel2); color:var(--dim); text-align:left; padding:8px 10px; font-weight:700; letter-spacing:.5px; border-bottom:1px solid var(--line); }
|
|
|
|
| 185 |
.digest { color:var(--dim); }
|
| 186 |
.cents { color:var(--amber); font-weight:700; }
|
| 187 |
|
|
|
|
| 188 |
.foot { margin-top:22px; color:var(--dim); font-size:11px; font-family:var(--mono); line-height:1.7; }
|
| 189 |
.foot b{ color:var(--ink); }
|
| 190 |
|
| 191 |
+
/* Respect reduced-motion: kill pulses, flashes, transitions. */
|
| 192 |
+
@media (prefers-reduced-motion: reduce) {
|
| 193 |
+
*, *::before, *::after { animation:none !important; transition:none !important; }
|
| 194 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
</style>
|
| 196 |
</head>
|
| 197 |
<body>
|
| 198 |
<header class="top">
|
| 199 |
<div class="brand">
|
| 200 |
+
<h1>SZL ENERGY · <span class="tl">LIVE ENERGY DASHBOARD</span></h1>
|
| 201 |
+
<span class="sub">real-time sovereign-mesh telemetry — every number labeled, nothing faked</span>
|
| 202 |
+
</div>
|
| 203 |
+
<div class="topright">
|
| 204 |
+
<div class="mesh-pill" id="mesh-pill">mesh <b id="mesh-up" class="up">…</b>/<span id="mesh-total">…</span> live</div>
|
| 205 |
+
<div class="conn" id="conn">link <b id="conn-state">…</b> · <span id="conn-ts">—</span></div>
|
| 206 |
+
<span class="chip chip-sample" id="global-chip">SAMPLE</span>
|
| 207 |
</div>
|
|
|
|
| 208 |
</header>
|
| 209 |
|
| 210 |
+
<!-- ============================== ROW 1 ============================== -->
|
| 211 |
+
<div class="secthead">
|
| 212 |
+
<span class="n">ROW 1</span>
|
| 213 |
+
<h2>Live Power</h2>
|
| 214 |
+
<span class="sub">on-box NVML watts · per sovereign-mesh node</span>
|
| 215 |
+
</div>
|
| 216 |
+
<div class="grid">
|
| 217 |
+
<section class="card span4">
|
| 218 |
+
<h2>Total live power <span class="chip chip-live" id="total-chip">LIVE</span></h2>
|
| 219 |
+
<div class="gaugewrap">
|
| 220 |
+
<canvas id="gauge-total"></canvas>
|
| 221 |
+
<div class="gauge-read" id="gauge-total-read"><span id="gauge-total-val">—</span><span class="cu">W</span></div>
|
| 222 |
+
</div>
|
| 223 |
+
<div class="gauge-sub" id="gauge-total-sub">awaiting first live sample…</div>
|
| 224 |
+
</section>
|
| 225 |
+
|
| 226 |
+
<section class="card span8">
|
| 227 |
+
<h2>Nodes <span class="chip chip-live" id="nodes-chip">LIVE</span></h2>
|
| 228 |
+
<div class="nodegrid" id="live-nodes"><div class="empty">querying /energy/live …</div></div>
|
| 229 |
+
</section>
|
| 230 |
+
</div>
|
| 231 |
+
|
| 232 |
+
<!-- ============================== ROW 2 ============================== -->
|
| 233 |
+
<div class="secthead">
|
| 234 |
+
<span class="n">ROW 2</span>
|
| 235 |
+
<h2>Streaming Power</h2>
|
| 236 |
+
<span class="sub">total watts · uPlot ring-buffer (~120 pts) · poll every 2s</span>
|
| 237 |
+
</div>
|
| 238 |
+
<div class="grid">
|
| 239 |
+
<section class="card span12">
|
| 240 |
+
<h2>Watts over time <span class="chip chip-measured" id="stream-chip">MEASURED</span></h2>
|
| 241 |
+
<div id="watts-chart"></div>
|
| 242 |
+
<div class="chart-empty" id="watts-empty">awaiting first live sample…</div>
|
| 243 |
+
<div class="chart-note">
|
| 244 |
+
<span id="stream-note">cumulative on-box NVML power, live</span>
|
| 245 |
+
<span class="offline-tag" id="offline-tag">⚠ meter offline — joules NOT fabricated</span>
|
| 246 |
+
</div>
|
| 247 |
+
</section>
|
| 248 |
+
</div>
|
| 249 |
+
|
| 250 |
+
<!-- ============================== ROW 3 ============================== -->
|
| 251 |
+
<div class="secthead">
|
| 252 |
+
<span class="n">ROW 3</span>
|
| 253 |
+
<h2>Bekenstein Budget Ledger</h2>
|
| 254 |
+
<span class="sub">joules_est per task · F19/TH6 proven-inequality gate</span>
|
| 255 |
+
</div>
|
| 256 |
+
<div class="grid">
|
| 257 |
+
<section class="card span12">
|
| 258 |
+
<h2>Energy + information budget <span class="chip chip-estimate" id="budget-chip">ESTIMATE</span></h2>
|
| 259 |
+
<div class="budgetbar">
|
| 260 |
+
<span><span class="k">tasks</span> <span class="v" id="b-tasks">—</span></span>
|
| 261 |
+
<span><span class="k">Σ joules_est</span> <span class="v" id="b-joules">—</span> J</span>
|
| 262 |
+
<span><span class="k">all_within_bound (F19/TH6)</span> <span id="b-gate">—</span></span>
|
| 263 |
+
<span class="k" id="b-gatedef">F19 Bekenstein: proven inequality, not assertion (locked-8)</span>
|
| 264 |
+
</div>
|
| 265 |
+
<div class="tbl-wrap">
|
| 266 |
+
<table class="tbl">
|
| 267 |
+
<thead><tr><th>#</th><th>time</th><th>joules_est</th><th>cumulative J</th><th>shannon bits</th><th>Bekenstein bound</th><th>within bound</th></tr></thead>
|
| 268 |
+
<tbody id="budget-body"><tr><td colspan="7" class="empty">querying /energy/budget …</td></tr></tbody>
|
| 269 |
+
</table>
|
| 270 |
+
</div>
|
| 271 |
+
</section>
|
| 272 |
+
</div>
|
| 273 |
+
|
| 274 |
+
<!-- ====================== PRESS-PLAY OPERATOR CONSOLE ====================== -->
|
| 275 |
+
<div class="secthead">
|
| 276 |
+
<span class="n">OPS</span>
|
| 277 |
+
<h2>Operator Console</h2>
|
| 278 |
+
<span class="sub">press-play control · signed JouleCharge receipts · nothing faked</span>
|
| 279 |
+
</div>
|
| 280 |
<div class="grid">
|
| 281 |
|
|
|
|
| 282 |
<section class="card span7">
|
| 283 |
<h2>Operator</h2>
|
| 284 |
<div class="playwrap">
|
| 285 |
<button class="playbtn" id="play" aria-pressed="false">PRESS PLAY</button>
|
| 286 |
<div class="playstate">
|
| 287 |
+
<div><span class="rdot" id="run-dot"></span><span id="run-text">querying operator…</span></div>
|
| 288 |
<div id="run-detail" style="margin-top:6px;color:var(--dim)">—</div>
|
| 289 |
</div>
|
| 290 |
</div>
|
|
|
|
| 302 |
</div>
|
| 303 |
</section>
|
| 304 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
<section class="card span7">
|
| 306 |
<h2>Today · compute done & earnings</h2>
|
| 307 |
<div class="metrics">
|
|
|
|
| 336 |
<div class="lbl"><span class="chip chip-zero" id="m-real-chip">ZERO</span></div>
|
| 337 |
</div>
|
| 338 |
</div>
|
| 339 |
+
<div style="margin-top:10px;color:var(--dim);font-size:11px;font-family:var(--mono)" id="earn-formula">earnings = MODELED dry-run ledger total + projection — never MEASURED until a real charge clears</div>
|
| 340 |
</section>
|
| 341 |
|
|
|
|
| 342 |
<section class="card span5">
|
| 343 |
<h2>Nodes computing now</h2>
|
| 344 |
<div class="nodes" id="nodes"><div class="empty">querying operator status…</div></div>
|
| 345 |
</section>
|
| 346 |
|
|
|
|
| 347 |
<section class="card span12">
|
| 348 |
<h2>Grid</h2>
|
| 349 |
<div class="grid-strip">
|
|
|
|
| 357 |
</div>
|
| 358 |
</section>
|
| 359 |
|
|
|
|
| 360 |
<section class="card span12">
|
| 361 |
<h2>Live receipt feed · SZL.Energy.JouleCharge.v1 <span class="chip chip-modeled" id="feed-mode">DRY-RUN</span></h2>
|
| 362 |
<div class="feed">
|
|
|
|
| 370 |
</div>
|
| 371 |
|
| 372 |
<div class="foot">
|
| 373 |
+
<div><b>Honesty (Doctrine v11):</b> watts & joules are <span class="chip chip-measured">MEASURED</span> from the on-box NVML exporter only when it is reachable; when the meter is offline the stream goes dashed and labels flip to <span class="chip chip-unavailable">UNAVAILABLE</span> — joules are NOT fabricated. The Bekenstein budget series is <span class="chip chip-estimate">ESTIMATE</span> / <span class="chip chip-sample">SAMPLE</span> (no on-box meter behind the budget layer); F19 Bekenstein is a proven inequality (locked-8), Λ is Conjecture 1.</div>
|
| 374 |
+
<div>Dollars are <span class="chip chip-modeled">MODELED</span> / <span class="chip chip-estimate">ESTIMATE</span> — revenue is <span class="chip chip-zero">ZERO</span> until a real charge clears, and only then <span class="chip chip-measured">MEASURED</span>. No live data → panels show <span class="chip chip-degraded">DEGRADED</span> / NO-LIVE-DATA. 0 runtime CDN · system fonts · uPlot v1.6.32 (MIT) vendored locally.</div>
|
| 375 |
</div>
|
| 376 |
|
| 377 |
+
<script src="/vendor/uPlot.iife.min.js"></script>
|
| 378 |
+
<script>
|
| 379 |
+
"use strict";
|
| 380 |
+
/* ===================================================================
|
| 381 |
+
Shared honesty + fetch helpers (single source of truth on the page).
|
| 382 |
+
=================================================================== */
|
| 383 |
+
var API = "/api/a11oy/v1";
|
| 384 |
+
var REDUCED = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
| 385 |
+
|
| 386 |
+
var CHIP = { LIVE:"chip-live", MEASURED:"chip-measured", MODELED:"chip-modeled",
|
| 387 |
+
SAMPLE:"chip-sample", ESTIMATE:"chip-estimate", ZERO:"chip-zero",
|
| 388 |
+
DEGRADED:"chip-degraded", UNAVAILABLE:"chip-unavailable" };
|
| 389 |
+
function setChip(el, label) {
|
| 390 |
+
if (!el) return;
|
| 391 |
+
var key = String(label || "DEGRADED").toUpperCase();
|
| 392 |
+
var cls = CHIP[key] || "chip-degraded";
|
| 393 |
+
el.className = "chip " + cls;
|
| 394 |
+
el.textContent = key;
|
| 395 |
+
}
|
| 396 |
+
function isErr(d) { return !d || d.__error === true; }
|
| 397 |
+
function num(v) { var n = typeof v === "number" ? v : parseFloat(v); return isFinite(n) ? n : null; }
|
| 398 |
+
function fmt(n, dec) {
|
| 399 |
+
if (typeof n !== "number" || !isFinite(n)) return "—";
|
| 400 |
+
return n.toLocaleString("en-US", { minimumFractionDigits: dec, maximumFractionDigits: dec });
|
| 401 |
+
}
|
| 402 |
+
function esc(s) { return String(s == null ? "" : s).replace(/[&<>"]/g, function (c) {
|
| 403 |
+
return { "&":"&","<":"<",">":">",'"':""" }[c]; }); }
|
| 404 |
+
function shortDigest(d) { if (!d) return "—"; var h = String(d).replace(/^sha256:/, ""); return h.slice(0, 12) + "…"; }
|
| 405 |
+
function shortTime(t) { if (!t) return "—"; try { return new Date(t).toLocaleTimeString(); } catch (e) { return String(t); } }
|
| 406 |
+
|
| 407 |
+
function poll(url, opts) {
|
| 408 |
+
opts = opts || {};
|
| 409 |
+
return fetch(url, { method: opts.method || "GET",
|
| 410 |
+
headers: opts.body ? {"content-type":"application/json"} : undefined,
|
| 411 |
+
body: opts.body })
|
| 412 |
+
.then(function (r) {
|
| 413 |
+
if (!r.ok) return { __error: true, status: r.status };
|
| 414 |
+
return r.json().catch(function () { return { __error: true, status: r.status }; });
|
| 415 |
+
})
|
| 416 |
+
.catch(function () { return { __error: true, status: 0 }; });
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
/* connection banner (any live endpoint flips it green) */
|
| 420 |
+
var anyLive = false;
|
| 421 |
+
function markLive(ok) {
|
| 422 |
+
anyLive = anyLive || ok;
|
| 423 |
+
var conn = document.getElementById("conn");
|
| 424 |
+
var st = document.getElementById("conn-state");
|
| 425 |
+
if (ok) { conn.classList.remove("degraded"); st.textContent = "LIVE"; }
|
| 426 |
+
else if (!anyLive) { conn.classList.add("degraded"); st.textContent = "NO-LIVE-DATA"; }
|
| 427 |
+
document.getElementById("conn-ts").textContent = new Date().toLocaleTimeString();
|
| 428 |
+
}
|
| 429 |
+
</script>
|
| 430 |
+
|
| 431 |
<script>
|
| 432 |
"use strict";
|
| 433 |
+
/* ===================================================================
|
| 434 |
+
TRI-ROW LIVE DASHBOARD — bound to the REAL live energy endpoints.
|
| 435 |
+
=================================================================== */
|
| 436 |
(function () {
|
| 437 |
+
// ---- canvas gauge (vanilla, DPR-aware, 0 CDN) ------------------------
|
| 438 |
+
function Gauge(id, opts) {
|
| 439 |
+
opts = opts || {};
|
| 440 |
+
this.c = document.getElementById(id);
|
| 441 |
+
this.ctx = this.c ? this.c.getContext("2d") : null;
|
| 442 |
+
this.max = opts.max || 600; this.cur = 0; this.live = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
}
|
| 444 |
+
Gauge.prototype.set = function (v, live) {
|
| 445 |
+
this.live = live !== false;
|
| 446 |
+
this.cur = (typeof v === "number" && isFinite(v)) ? v : 0;
|
| 447 |
+
this.draw();
|
| 448 |
+
};
|
| 449 |
+
Gauge.prototype.draw = function () {
|
| 450 |
+
var c = this.c, ctx = this.ctx; if (!ctx) return;
|
| 451 |
+
var dpr = window.devicePixelRatio || 1;
|
| 452 |
+
var w = c.clientWidth || 240, h = c.clientHeight || 210;
|
| 453 |
+
if (c.width !== Math.round(w * dpr) || c.height !== Math.round(h * dpr)) {
|
| 454 |
+
c.width = Math.round(w * dpr); c.height = Math.round(h * dpr);
|
| 455 |
}
|
| 456 |
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
| 457 |
+
ctx.clearRect(0, 0, w, h);
|
| 458 |
+
var cx = w / 2, cy = h * 0.70, r = Math.min(w * 0.40, h * 0.56);
|
| 459 |
+
var a0 = Math.PI * 0.85, a1 = Math.PI * 2.15;
|
| 460 |
+
ctx.lineWidth = 14; ctx.lineCap = "round";
|
| 461 |
+
ctx.strokeStyle = "#16202e";
|
| 462 |
+
ctx.beginPath(); ctx.arc(cx, cy, r, a0, a1); ctx.stroke();
|
| 463 |
+
if (!this.live) {
|
| 464 |
+
ctx.setLineDash([5, 7]); ctx.strokeStyle = "#5a2222";
|
| 465 |
+
ctx.beginPath(); ctx.arc(cx, cy, r, a0, a1); ctx.stroke();
|
| 466 |
+
ctx.setLineDash([]); return;
|
| 467 |
+
}
|
| 468 |
+
var frac = Math.max(0, Math.min(1, this.cur / this.max));
|
| 469 |
+
var grad = ctx.createLinearGradient(cx - r, 0, cx + r, 0);
|
| 470 |
+
grad.addColorStop(0, "#39d8c8"); grad.addColorStop(0.7, "#34d399"); grad.addColorStop(1, "#fbbf24");
|
| 471 |
+
ctx.strokeStyle = grad;
|
| 472 |
+
ctx.beginPath(); ctx.arc(cx, cy, r, a0, a0 + (a1 - a0) * frac); ctx.stroke();
|
| 473 |
+
};
|
| 474 |
+
var gTotal = new Gauge("gauge-total", { max: 600 });
|
| 475 |
+
|
| 476 |
+
// ---- uPlot streaming watts chart (ring buffer ~120 pts) --------------
|
| 477 |
+
var RING = 120;
|
| 478 |
+
var xs = [], ys = [];
|
| 479 |
+
var t0 = Date.now();
|
| 480 |
+
var chartEl = document.getElementById("watts-chart");
|
| 481 |
+
var emptyEl = document.getElementById("watts-empty");
|
| 482 |
+
var u = null, lastOffline = null;
|
| 483 |
+
|
| 484 |
+
function chartWidth() { return Math.max(320, chartEl.clientWidth || 800); }
|
| 485 |
+
|
| 486 |
+
function buildChart(offline) {
|
| 487 |
+
if (u) { u.destroy(); u = null; }
|
| 488 |
+
var opts = {
|
| 489 |
+
width: chartWidth(),
|
| 490 |
+
height: 260,
|
| 491 |
+
cursor: { y: false },
|
| 492 |
+
legend: { show: false },
|
| 493 |
+
scales: { x: { time: false }, y: { range: function (self, lo, hi) {
|
| 494 |
+
if (hi == null) return [0, 1]; return [0, Math.max(hi * 1.15, 1)]; } } },
|
| 495 |
+
axes: [
|
| 496 |
+
{ stroke: "#8aa0bd", grid: { stroke: "#16202e" }, ticks: { stroke: "#16202e" },
|
| 497 |
+
font: "11px ui-monospace, monospace",
|
| 498 |
+
values: function (self, splits) { return splits.map(function (s) { return s + "s"; }); } },
|
| 499 |
+
{ stroke: "#8aa0bd", grid: { stroke: "#16202e" }, ticks: { stroke: "#16202e" },
|
| 500 |
+
font: "11px ui-monospace, monospace",
|
| 501 |
+
values: function (self, splits) { return splits.map(function (s) { return s + " W"; }); } }
|
| 502 |
+
],
|
| 503 |
+
series: [
|
| 504 |
+
{},
|
| 505 |
+
{ label: "watts", stroke: offline ? "#f87171" : "#39d8c8", width: 2,
|
| 506 |
+
dash: offline ? [6, 5] : [],
|
| 507 |
+
fill: offline ? "rgba(248,113,113,0.05)" : "rgba(57,216,200,0.12)",
|
| 508 |
+
points: { show: false } }
|
| 509 |
+
]
|
| 510 |
};
|
| 511 |
+
u = new uPlot(opts, [xs, ys], chartEl);
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
function ensureChart(offline) {
|
| 515 |
+
if (u == null || lastOffline !== offline) { buildChart(offline); lastOffline = offline; }
|
| 516 |
}
|
|
|
|
| 517 |
|
| 518 |
+
function pushSample(watts, offline) {
|
| 519 |
+
var t = Math.round((Date.now() - t0) / 1000);
|
| 520 |
+
xs.push(t);
|
| 521 |
+
ys.push(offline ? null : watts); // honest gap on offline — never a fabricated zero
|
| 522 |
+
if (xs.length > RING) { xs.shift(); ys.shift(); }
|
| 523 |
+
ensureChart(offline);
|
| 524 |
+
if (emptyEl && xs.length >= 1) emptyEl.style.display = "none";
|
| 525 |
+
u.setData([xs, ys]);
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
window.addEventListener("resize", function () { if (u) u.setSize({ width: chartWidth(), height: 260 }); });
|
| 529 |
+
|
| 530 |
+
// ---- ROW 1 + ROW 2: /energy/live -------------------------------------
|
| 531 |
+
function renderLive(d) {
|
| 532 |
+
markLive(!isErr(d));
|
| 533 |
+
var offline = isErr(d) || d.label === "UNAVAILABLE" ||
|
| 534 |
+
(d.meter_status && d.meter_status !== 200) || d.total_watts == null;
|
| 535 |
+
|
| 536 |
+
// global + per-section chips
|
| 537 |
+
setChip(document.getElementById("global-chip"), offline ? "UNAVAILABLE" : "LIVE");
|
| 538 |
+
setChip(document.getElementById("total-chip"), offline ? "UNAVAILABLE" : "LIVE");
|
| 539 |
+
setChip(document.getElementById("nodes-chip"), offline ? "UNAVAILABLE" : "LIVE");
|
| 540 |
+
setChip(document.getElementById("stream-chip"), offline ? "UNAVAILABLE" : (d.joules_label || "MEASURED"));
|
| 541 |
+
|
| 542 |
+
// total power gauge
|
| 543 |
+
var read = document.getElementById("gauge-total-read");
|
| 544 |
+
var valEl = document.getElementById("gauge-total-val");
|
| 545 |
+
var subEl = document.getElementById("gauge-total-sub");
|
| 546 |
+
var tw = offline ? null : num(d.total_watts);
|
| 547 |
+
if (tw == null) {
|
| 548 |
+
valEl.textContent = "—"; read.classList.add("off");
|
| 549 |
+
gTotal.set(0, false);
|
| 550 |
+
subEl.textContent = offline ? (d && d.note ? d.note : "meter offline — joules NOT fabricated") : "no live watts";
|
| 551 |
+
} else {
|
| 552 |
+
valEl.textContent = fmt(tw, 1); read.classList.remove("off");
|
| 553 |
+
gTotal.set(tw, true);
|
| 554 |
+
var tj = num(d.total_joules);
|
| 555 |
+
subEl.textContent = "Σ joules " + (tj != null ? fmt(tj, 1) + " J" : "—") + " · " + esc(d.exporter || "NVML");
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
// streaming chart
|
| 559 |
+
document.getElementById("offline-tag").classList.toggle("show", offline);
|
| 560 |
+
document.getElementById("stream-note").textContent = offline
|
| 561 |
+
? "stream paused — gaps, not fabricated values"
|
| 562 |
+
: "cumulative on-box NVML power, live";
|
| 563 |
+
pushSample(tw, offline);
|
| 564 |
+
|
| 565 |
+
// per-node cards
|
| 566 |
+
renderNodeCards(d, offline);
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
function renderNodeCards(d, offline) {
|
| 570 |
+
var box = document.getElementById("live-nodes");
|
| 571 |
+
var nodes = (d && !isErr(d) && Array.isArray(d.nodes)) ? d.nodes : [];
|
| 572 |
+
if (!nodes.length) {
|
| 573 |
+
box.innerHTML = '<div class="empty"><span class="chip chip-unavailable">UNAVAILABLE</span> no node telemetry — meter offline, nothing fabricated</div>';
|
| 574 |
+
return;
|
| 575 |
+
}
|
| 576 |
+
box.innerHTML = "";
|
| 577 |
+
nodes.forEach(function (n) {
|
| 578 |
+
var live = n.live === true && !offline;
|
| 579 |
+
var watts = num(n.watts);
|
| 580 |
+
var div = document.createElement("div");
|
| 581 |
+
div.className = "ncard " + (live ? "live" : "down");
|
| 582 |
+
var lbl = (watts != null && live) ? "MEASURED" : "UNAVAILABLE";
|
| 583 |
+
var joules = num(n.joules);
|
| 584 |
+
var meta = "src " + esc(n.source || "—");
|
| 585 |
+
if (joules != null) meta += " · " + fmt(joules, 1) + " J";
|
| 586 |
+
div.innerHTML =
|
| 587 |
+
'<div class="nm"><span class="dot ' + (live ? "on" : "off") + '"></span>' + esc(n.name || "—") +
|
| 588 |
+
' <span class="chip ' + (CHIP[lbl] || "chip-unavailable") + '">' + lbl + '</span></div>' +
|
| 589 |
+
'<div class="watt">' + (watts != null && live ? fmt(watts, 1) : "—") + '<span class="u">W</span></div>' +
|
| 590 |
+
'<div class="meta">' + meta + ' · ' + (live ? "live" : "down") + '</div>';
|
| 591 |
+
box.appendChild(div);
|
| 592 |
+
});
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
// ---- mesh health: /govern/health -------------------------------------
|
| 596 |
+
function renderMesh(d) {
|
| 597 |
+
var up = document.getElementById("mesh-up");
|
| 598 |
+
var tot = document.getElementById("mesh-total");
|
| 599 |
+
if (isErr(d)) { up.textContent = "0"; up.className = "down"; tot.textContent = "?"; return; }
|
| 600 |
+
var live = num(d.engines_live); var total = num(d.engines_total);
|
| 601 |
+
up.textContent = (live != null ? live : "?");
|
| 602 |
+
tot.textContent = (total != null ? total : "?");
|
| 603 |
+
up.className = (live != null && total != null && live >= total) ? "up" : (live ? "up" : "down");
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
// ---- ROW 3: /energy/budget (Bekenstein ledger) -----------------------
|
| 607 |
+
function renderBudget(d) {
|
| 608 |
+
markLive(!isErr(d));
|
| 609 |
+
var body = document.getElementById("budget-body");
|
| 610 |
+
var chip = document.getElementById("budget-chip");
|
| 611 |
+
if (isErr(d)) {
|
| 612 |
+
setChip(chip, "DEGRADED");
|
| 613 |
+
body.innerHTML = '<tr><td colspan="7" class="empty"><span class="chip chip-degraded">DEGRADED</span> budget unreachable — NO-LIVE-DATA</td></tr>';
|
| 614 |
+
document.getElementById("b-gate").innerHTML = '<span class="gate-bad">—</span>';
|
| 615 |
+
return;
|
| 616 |
+
}
|
| 617 |
+
// honest label from the budget layer (no on-box meter -> SAMPLE/ESTIMATE)
|
| 618 |
+
var lbl = String(d.joules_label || d.total_joules_est_label || "ESTIMATE").toUpperCase();
|
| 619 |
+
setChip(chip, (CHIP[lbl] ? lbl : "ESTIMATE"));
|
| 620 |
+
|
| 621 |
+
document.getElementById("b-tasks").textContent = (num(d.task_count) != null ? fmt(d.task_count, 0) : "0");
|
| 622 |
+
document.getElementById("b-joules").textContent = (num(d.total_joules_est) != null ? fmt(d.total_joules_est, 3) : "0");
|
| 623 |
+
if (d.gate) document.getElementById("b-gatedef").textContent = String(d.gate);
|
| 624 |
+
var gateEl = document.getElementById("b-gate");
|
| 625 |
+
if (d.all_within_bound === true) gateEl.innerHTML = '<span class="gate-ok">✓ all within bound</span>';
|
| 626 |
+
else if (d.all_within_bound === false) gateEl.innerHTML = '<span class="gate-bad">✗ breach</span>';
|
| 627 |
+
else gateEl.innerHTML = '<span class="within-y">—</span>';
|
| 628 |
+
|
| 629 |
+
var series = Array.isArray(d.series) ? d.series : [];
|
| 630 |
+
if (!series.length) {
|
| 631 |
+
// honest EMPTY state — never fabricate a row
|
| 632 |
+
body.innerHTML = '<tr><td colspan="7" class="empty">budget EMPTY — no tasks metered yet (gate VERIFIED on first task). Nothing fabricated.</td></tr>';
|
| 633 |
+
return;
|
| 634 |
+
}
|
| 635 |
+
var rows = series.slice(-50).reverse();
|
| 636 |
+
body.innerHTML = "";
|
| 637 |
+
var base = series.length;
|
| 638 |
+
rows.forEach(function (r, i) {
|
| 639 |
+
var within = r.within_bound === true;
|
| 640 |
+
var tr = document.createElement("tr");
|
| 641 |
+
tr.innerHTML =
|
| 642 |
+
"<td>" + (base - i) + "</td>" +
|
| 643 |
+
"<td>" + esc(shortTime(r.ts)) + "</td>" +
|
| 644 |
+
"<td>" + fmt(num(r.joules_est), 3) + "</td>" +
|
| 645 |
+
"<td>" + fmt(num(r.cumulative_joules_est), 3) + "</td>" +
|
| 646 |
+
"<td>" + (num(r.shannon_bits) != null ? fmt(r.shannon_bits, 2) : "—") + "</td>" +
|
| 647 |
+
"<td>" + (num(r.bekenstein_bound_bits) != null ? fmt(r.bekenstein_bound_bits, 0) : "—") + "</td>" +
|
| 648 |
+
'<td class="' + (within ? "within-y" : "within-n") + '">' + (within ? "✓ within" : "✗ breach") + "</td>";
|
| 649 |
+
body.appendChild(tr);
|
| 650 |
+
});
|
| 651 |
+
}
|
| 652 |
+
|
| 653 |
+
// ---- poll loops ------------------------------------------------------
|
| 654 |
+
function tickLive() { poll(API + "/energy/live").then(renderLive); }
|
| 655 |
+
function tickMesh() { poll(API + "/govern/health").then(renderMesh); }
|
| 656 |
+
function tickBudget() { poll(API + "/energy/budget").then(renderBudget); }
|
| 657 |
+
|
| 658 |
+
tickLive(); tickMesh(); tickBudget();
|
| 659 |
+
setInterval(tickLive, 2000); // streaming cadence (1-5s window)
|
| 660 |
+
setInterval(tickMesh, 5000);
|
| 661 |
+
setInterval(tickBudget, 5000);
|
| 662 |
+
})();
|
| 663 |
+
</script>
|
| 664 |
+
|
| 665 |
+
<script>
|
| 666 |
+
"use strict";
|
| 667 |
+
/* ===================================================================
|
| 668 |
+
PRESS-PLAY OPERATOR CONSOLE — live operator control + signed receipts.
|
| 669 |
+
Reuses the page-level helpers (setChip / isErr / poll / markLive).
|
| 670 |
+
=================================================================== */
|
| 671 |
+
(function () {
|
| 672 |
+
// ---- smooth counter (eases displayed value toward a target) ----------
|
| 673 |
function Counter(el, opts) {
|
| 674 |
opts = opts || {};
|
| 675 |
this.el = el; this.cur = 0; this.target = 0; this.decimals = opts.decimals || 0;
|
|
|
|
| 678 |
Counter.prototype.set = function (v) {
|
| 679 |
if (typeof v !== "number" || !isFinite(v)) return;
|
| 680 |
this.target = v;
|
| 681 |
+
if (!this.started || REDUCED) { this.cur = v; this.started = true; this.render(); }
|
| 682 |
};
|
| 683 |
Counter.prototype.tick = function () {
|
| 684 |
if (!this.started) return;
|
| 685 |
var d = this.target - this.cur;
|
| 686 |
+
if (REDUCED || Math.abs(d) < Math.pow(10, -this.decimals) / 2) { this.cur = this.target; }
|
| 687 |
else { this.cur += d * 0.18; }
|
| 688 |
this.render();
|
| 689 |
};
|
| 690 |
+
Counter.prototype.render = function () { this.el.textContent = fmt(this.cur, this.decimals); };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 691 |
|
| 692 |
var bigJoules = new Counter(document.getElementById("big-joules"), { decimals: 3 });
|
| 693 |
var mTokens = new Counter(document.getElementById("m-tokens"), { decimals: 0 });
|
| 694 |
var mJobs = new Counter(document.getElementById("m-jobs"), { decimals: 0 });
|
| 695 |
var mJoules = new Counter(document.getElementById("m-joules"), { decimals: 1 });
|
| 696 |
+
if (!REDUCED) setInterval(function () { bigJoules.tick(); mTokens.tick(); mJobs.tick(); mJoules.tick(); }, 33);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 697 |
|
| 698 |
// ===== OPERATOR: PLAY / STOP + status ==================================
|
| 699 |
+
var running = null;
|
| 700 |
var playBtn = document.getElementById("play");
|
| 701 |
var busy = false;
|
| 702 |
|
|
|
|
| 705 |
var txt = document.getElementById("run-text");
|
| 706 |
var det = document.getElementById("run-detail");
|
| 707 |
if (isErr(status)) {
|
| 708 |
+
running = null; dot.className = "rdot off";
|
| 709 |
txt.textContent = "operator DEGRADED — NO-LIVE-DATA";
|
| 710 |
det.innerHTML = '<span class="chip chip-degraded">DEGRADED</span> operator status unreachable';
|
| 711 |
playBtn.textContent = "PRESS PLAY"; playBtn.classList.remove("stop");
|
|
|
|
| 713 |
return;
|
| 714 |
}
|
| 715 |
running = (status.running === true) || (status.state === "running") || (status.status === "running");
|
| 716 |
+
dot.className = "rdot " + (running ? "on" : "off");
|
| 717 |
if (running) {
|
| 718 |
txt.textContent = "RUNNING — computing non-stop";
|
| 719 |
playBtn.textContent = "STOP"; playBtn.classList.add("stop");
|
|
|
|
| 736 |
}
|
| 737 |
|
| 738 |
playBtn.addEventListener("click", function () {
|
| 739 |
+
if (busy) return;
|
| 740 |
var action = running ? "stop" : "start";
|
| 741 |
busy = true; playBtn.disabled = true;
|
| 742 |
poll(API + "/energy/operator/" + action, { method: "POST", body: "{}" })
|
|
|
|
| 747 |
'<span class="chip chip-degraded">DEGRADED</span> ' + action + " failed (operator unreachable)";
|
| 748 |
return;
|
| 749 |
}
|
|
|
|
| 750 |
running = (action === "start");
|
| 751 |
refreshStatus();
|
| 752 |
});
|
|
|
|
| 764 |
box.innerHTML = '<div class="empty">no node telemetry — <span class="chip chip-degraded">DEGRADED</span></div>';
|
| 765 |
return;
|
| 766 |
}
|
|
|
|
|
|
|
| 767 |
var ns = (status.node_status && typeof status.node_status === "object") ? status.node_status : null;
|
| 768 |
var byNode = status.by_node || {};
|
| 769 |
var names = ns ? Object.keys(ns) : [];
|
|
|
|
| 812 |
}
|
| 813 |
var receipts = d.receipts || d.entries || d.ledger || [];
|
| 814 |
var dryRunTotal = 0, anyCharged = false;
|
|
|
|
| 815 |
var mode = (String(d.stripe_mode || d.mode || "").toLowerCase() === "live") ? "LIVE" : "DRY-RUN";
|
| 816 |
var feedMode = document.getElementById("feed-mode");
|
| 817 |
feedMode.textContent = mode;
|
| 818 |
feedMode.className = "chip " + (mode === "LIVE" ? "chip-measured" : "chip-modeled");
|
| 819 |
|
|
|
|
| 820 |
var rows = receipts.slice().reverse();
|
| 821 |
if (rows.length && body.querySelector(".empty")) body.innerHTML = "";
|
| 822 |
rows.forEach(function (r) {
|
|
|
|
| 849 |
mReal.textContent = "$" + (realCents / 100).toFixed(2);
|
| 850 |
setChip(document.getElementById("m-real-chip"), realCents > 0 ? "MEASURED" : "ZERO");
|
| 851 |
mReal.style.color = realCents > 0 ? "var(--green)" : "var(--dim)";
|
|
|
|
|
|
|
|
|
|
| 852 |
});
|
| 853 |
}
|
| 854 |
|
|
|
|
| 871 |
if (tokens != null) mTokens.set(tokens);
|
| 872 |
if (jobs != null) mJobs.set(jobs);
|
| 873 |
if (joules != null) { mJoules.set(joules); bigJoules.set(joules); }
|
|
|
|
| 874 |
});
|
| 875 |
}
|
| 876 |
|
|
|
|
| 886 |
var joules = num((cd.joules && cd.joules.value != null) ? cd.joules.value
|
| 887 |
: (p.joules && p.joules.value != null ? p.joules.value
|
| 888 |
: (p.joules != null ? p.joules : (p.joules_projected != null ? p.joules_projected : p.joules_1d))));
|
|
|
|
| 889 |
var earn = p.earnings || {};
|
| 890 |
var resale = earn.compute_resale_usd || {};
|
| 891 |
var earnDollars = num(resale.value != null ? resale.value
|
|
|
|
| 922 |
else {
|
| 923 |
priceEl.textContent = price.toFixed(2) + " EUR/MWh";
|
| 924 |
priceEl.style.color = price < 0 ? "var(--green)" : "var(--ink)";
|
|
|
|
| 925 |
setChip(chip, (d.measured_any ? "MEASURED" : "SAMPLE"));
|
| 926 |
banner.classList.toggle("show", price < 0);
|
| 927 |
}
|
|
|
|
| 946 |
return null;
|
| 947 |
}
|
| 948 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 949 |
// ---- poll loop ---------------------------------------------------------
|
| 950 |
function tickFast() { refreshStatus(); refreshToday(); refreshLedger(); }
|
| 951 |
function tickSlow() { refreshProjection(); refreshGrid(); }
|