diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 76e831a..a0577cc 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -1,3 +1,10 @@ +## Update - 2026-03-22 16:00 + +- Live frontend source of truth identified as /var/www/monitor. +- Working deployment workflow established: edit repo copy in /home/def/monitor/frontend and deploy to /var/www/monitor via rsync script. +- Quote calculator behavior corrected so the amount field no longer snaps back to the default during timed refresh cycles. +- Monitor page now refreshes market/oracle data without forcibly rebuilding the active quote panel state. + # PROJECT_STATE.md Project: monitor diff --git a/README.md b/README.md index dbd4f75..f359341 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +## v1.0.1 - 2026-03-22 + +- Fixed live quote calculator so the entered CAD value no longer resets to the default during timed page refreshes. +- Changed monitor refresh behavior so the quote panel remains stable until the user explicitly updates it. +- Confirmed live frontend is served from /var/www/monitor and synced working files back into repo frontend for future deployments. + # Monitor Monitor is the pricing and oracle dashboard for `monitor.outsidethebox.top`. diff --git a/VERSION b/VERSION index 1474d00..b18d465 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.2.0 +v1.0.1 diff --git a/deploy-monitor.sh b/deploy-monitor.sh new file mode 100755 index 0000000..bab297d --- /dev/null +++ b/deploy-monitor.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +SRC="/home/def/monitor/frontend/" +DST="/var/www/monitor/" + +echo "Deploying monitor frontend..." +rsync -rlptDv --delete --no-owner --no-group "$SRC" "$DST" + +echo +echo "Deploy complete:" +ls -lah "$DST" diff --git a/frontend/app.js b/frontend/app.js index 851b263..08afabe 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -1,7 +1,12 @@ // /home/def/monitor/frontend/app.js function el(tag, cls) { - const n = document.createElement(tag || "div"); + const n = document.createElement("div"); + if (tag && tag !== "div") { + const real = document.createElement(tag); + if (cls) real.className = cls; + return real; + } if (cls) n.className = cls; return n; } @@ -86,14 +91,28 @@ function ageTextFromIso(iso) { return `${Math.floor(s / 86400)}d ago`; } +function fmtCad(v) { + const n = Number(v); + if (!Number.isFinite(n)) return "—"; + return new Intl.NumberFormat(undefined, { + style: "currency", + currency: "CAD", + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }).format(n); +} + /* ---------------- Tooltip (singleton) ---------------- */ let tipEl = null; function ensureTip() { if (tipEl) return tipEl; - tipEl = el("div", "spark-tip"); - const date = el("div", "spark-tip-date"); - const val = el("div", "spark-tip-val"); + tipEl = document.createElement("div"); + tipEl.className = "spark-tip"; + const date = document.createElement("div"); + date.className = "spark-tip-date"; + const val = document.createElement("div"); + val.className = "spark-tip-val"; tipEl.appendChild(date); tipEl.appendChild(val); document.body.appendChild(tipEl); @@ -255,16 +274,22 @@ function attachSparkHover(canvas, line) { /* ---------------- DOM builders ---------------- */ function makeRow(line) { - const row = el("div", "row"); + const row = document.createElement("div"); + row.className = "row"; - const left = el("div", "left"); - const ic = el("div", "ic"); + const left = document.createElement("div"); + left.className = "left"; + const ic = document.createElement("div"); + ic.className = "ic"; ic.textContent = iconText(line.icon); - const mid = el("div", "mid"); - const sym = el("div", "val"); + const mid = document.createElement("div"); + mid.className = "mid"; + const sym = document.createElement("div"); + sym.className = "val"; sym.textContent = line.key || line.symbol || ""; - const name = el("div", "subline"); + const name = document.createElement("div"); + name.className = "subline"; name.textContent = line.name || ""; left.appendChild(ic); @@ -272,17 +297,21 @@ function makeRow(line) { mid.appendChild(name); left.appendChild(mid); - const center = el("div", "mid"); + const center = document.createElement("div"); + center.className = "mid"; center.style.textAlign = "right"; - const value = el("div", "val"); + const value = document.createElement("div"); + value.className = "val"; value.textContent = line.display != null ? String(line.display) : fmtNumber(line.value); - const vs = el("div", "subline"); + const vs = document.createElement("div"); + vs.className = "subline"; vs.textContent = line.vs ? `in ${line.vs}` : ""; center.appendChild(value); center.appendChild(vs); - const right = el("div", "right"); + const right = document.createElement("div"); + right.className = "right"; const spark = document.createElement("canvas"); spark.className = "spark"; right.appendChild(spark); @@ -297,35 +326,48 @@ function makeRow(line) { } function makeOracleAssetRow(asset) { - const row = el("div", "oracle-asset-row"); + const row = document.createElement("div"); + row.className = "oracle-asset-row"; + + const left = document.createElement("div"); + left.className = "oracle-asset-left"; - const left = el("div", "oracle-asset-left"); - const title = el("div", "oracle-asset-key"); + const title = document.createElement("div"); + title.className = "oracle-asset-key"; title.textContent = `${asset.symbol} • ${asset.chain}`; - const sub = el("div", "oracle-asset-sub"); + + const sub = document.createElement("div"); + sub.className = "oracle-asset-sub"; const source = asset.source || "—"; const sourceStatus = asset.source_status || "—"; const freshness = asset.freshness || "unknown"; sub.textContent = `source: ${source} (${sourceStatus}) • ${freshness}`; + left.appendChild(title); left.appendChild(sub); - const right = el("div", "oracle-asset-right"); - const badges = el("div", "oracle-badges"); + const right = document.createElement("div"); + right.className = "oracle-asset-right"; - const freshBadge = el("span", `oracle-badge ${asset.stale ? "badge-stale" : "badge-fresh"}`); + const badges = document.createElement("div"); + badges.className = "oracle-badges"; + + const freshBadge = document.createElement("span"); + freshBadge.className = `oracle-badge ${asset.stale ? "badge-stale" : "badge-fresh"}`; freshBadge.textContent = asset.stale ? "stale" : "fresh"; badges.appendChild(freshBadge); if (asset.billing_enabled) { - const billingBadge = el("span", "oracle-badge badge-billing"); + const billingBadge = document.createElement("span"); + billingBadge.className = "oracle-badge badge-billing"; billingBadge.textContent = "billing"; badges.appendChild(billingBadge); } right.appendChild(badges); - const price = el("div", "oracle-price"); + const price = document.createElement("div"); + price.className = "oracle-price"; if (asset.price_cad != null && isFinite(asset.price_cad)) { price.textContent = `${fmtNumber(asset.price_cad)} CAD`; } else { @@ -339,23 +381,31 @@ function makeOracleAssetRow(asset) { } function makeOraclePanel(pricesData) { - const wrap = el("div", "oracle-panel"); + const wrap = document.createElement("div"); + wrap.className = "oracle-panel"; + + const top = document.createElement("div"); + top.className = "oracle-top"; - const top = el("div", "oracle-top"); + const left = document.createElement("div"); - const left = el("div"); - const title = el("div", "section-title"); + const title = document.createElement("div"); + title.className = "section-title"; title.textContent = "Oracle Status"; title.style.margin = "0 0 8px 0"; - const updated = el("div", "oracle-updated"); + const updated = document.createElement("div"); + updated.className = "oracle-updated"; updated.textContent = `Last update: ${fmtDateTime(pricesData.updated_at)} • ${ageTextFromIso(pricesData.updated_at)}`; left.appendChild(title); left.appendChild(updated); - const right = el("div", "oracle-summary"); - const overall = el("span", `oracle-badge ${pricesData.status === "fresh" ? "badge-fresh" : "badge-stale"}`); + const right = document.createElement("div"); + right.className = "oracle-summary"; + + const overall = document.createElement("span"); + overall.className = `oracle-badge ${pricesData.status === "fresh" ? "badge-fresh" : "badge-stale"}`; overall.textContent = pricesData.status || "unknown"; right.appendChild(overall); @@ -369,127 +419,139 @@ function makeOraclePanel(pricesData) { const billingEnabled = assets.filter(a => a.billing_enabled).length; const freshCount = assets.filter(a => !a.stale).length; - const meta = el("div", "oracle-meta"); + const meta = document.createElement("div"); + meta.className = "oracle-meta"; meta.innerHTML = `
`; wrap.appendChild(meta); - const quoteSection = el("div", "oracle-quote-wrap"); - const quoteTitle = el("div", "oracle-quote-title"); - quoteTitle.textContent = "Live Quote Calculator"; - - const quoteForm = el("div", "oracle-quote-form"); - - const amountWrap = el("div", "oracle-quote-field"); - const amountLabel = el("label", "oracle-quote-label"); - amountLabel.setAttribute("for", "oracleQuoteAmount"); - amountLabel.textContent = "Amount (CAD)"; - const amountInput = document.createElement("input"); - amountInput.id = "oracleQuoteAmount"; - amountInput.className = "oracle-quote-input"; - amountInput.type = "number"; - amountInput.min = "0.01"; - amountInput.step = "0.01"; - amountInput.value = "100.00"; - amountWrap.appendChild(amountLabel); - amountWrap.appendChild(amountInput); - - const buttonWrap = el("div", "oracle-quote-actions"); - const quoteBtn = document.createElement("button"); - quoteBtn.className = "oracle-quote-button"; - quoteBtn.type = "button"; - quoteBtn.textContent = "Get Quote"; - buttonWrap.appendChild(quoteBtn); - - quoteForm.appendChild(amountWrap); - quoteForm.appendChild(buttonWrap); - - const quoteResults = el("div", "oracle-quote-results"); - quoteResults.id = "oracleQuoteResults"; - quoteResults.innerHTML = `