← back to AbramsEgo
add WPB marketplace-sales panel (proxy :9812 via /api/wpb)
25b2bd6f70c849d00bc79ee89c9abdfb7de0f68b · 2026-07-09 15:55:33 -0700 · Steve
Files touched
M data/local-fleet.jsonM public/index.htmlM server.js
Diff
commit 25b2bd6f70c849d00bc79ee89c9abdfb7de0f68b
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 9 15:55:33 2026 -0700
add WPB marketplace-sales panel (proxy :9812 via /api/wpb)
---
data/local-fleet.json | 2 +-
public/index.html | 36 +++++++++++++++++++++++++++++++++++-
server.js | 26 ++++++++++++++++++++++++++
3 files changed, 62 insertions(+), 2 deletions(-)
diff --git a/data/local-fleet.json b/data/local-fleet.json
index 86cd05a..7afd808 100644
--- a/data/local-fleet.json
+++ b/data/local-fleet.json
@@ -1 +1 @@
-{"fetchedAt":"2026-07-09T22:40:41.225Z","procs":[{"name":"pm2-logrotate","status":"online","cpu":0.3,"memMB":55,"restarts":0,"uptimeMs":294506034,"startedAt":"2026-07-06T12:52:15.191Z"},{"name":"abramsego","status":"online","cpu":0.8,"memMB":193,"restarts":6,"uptimeMs":90215060,"startedAt":"2026-07-08T21:37:06.165Z"},{"name":"pattern-vault","status":"online","cpu":0.2,"memMB":50,"restarts":1,"uptimeMs":239909337,"startedAt":"2026-07-07T04:02:11.888Z"},{"name":"cabinet-mindmap","status":"online","cpu":0.3,"memMB":45,"restarts":1,"uptimeMs":239909339,"startedAt":"2026-07-07T04:02:11.886Z"},{"name":"patterndesignlab","status":"online","cpu":0.2,"memMB":50,"restarts":3,"uptimeMs":239909337,"startedAt":"2026-07-07T04:02:11.888Z"},{"name":"japan-viewer","status":"online","cpu":0.4,"memMB":389,"restarts":11,"uptimeMs":239909325,"startedAt":"2026-07-07T04:02:11.900Z"},{"name":"reidwitlin-landing","status":"online","cpu":0.3,"memMB":51,"restarts":14,"uptimeMs":195698941,"startedAt":"2026-07-07T16:19:02.284Z"},{"name":"cncp","status":"online","cpu":2.6,"memMB":152,"restarts":0,"uptimeMs":203346666,"startedAt":"2026-07-07T14:11:34.559Z"},{"name":"abramsos","status":"online","cpu":0.2,"memMB":128,"restarts":11,"uptimeMs":85346570,"startedAt":"2026-07-08T22:58:14.655Z"},{"name":"dw-pitch-followup","status":"online","cpu":0.3,"memMB":79,"restarts":22,"uptimeMs":118423185,"startedAt":"2026-07-08T13:46:58.040Z"},{"name":"wpb-sales-dashboard","status":"online","cpu":0.2,"memMB":70,"restarts":1,"uptimeMs":87464645,"startedAt":"2026-07-08T22:22:56.580Z"},{"name":"crezana-internal","status":"online","cpu":0.2,"memMB":73,"restarts":1,"uptimeMs":20900453,"startedAt":"2026-07-09T16:52:20.772Z"},{"name":"fromental-internal","status":"online","cpu":0.2,"memMB":89,"restarts":1,"uptimeMs":7028347,"startedAt":"2026-07-09T20:43:32.878Z"},{"name":"gracie-internal","status":"online","cpu":0.2,"memMB":88,"restarts":2,"uptimeMs":7027978,"startedAt":"2026-07-09T20:43:33.247Z"},{"name":"zuber-internal","status":"online","cpu":0.2,"memMB":92,"restarts":3,"uptimeMs":7028732,"startedAt":"2026-07-09T20:43:32.493Z"}]}
\ No newline at end of file
+{"fetchedAt":"2026-07-09T22:55:32.014Z","procs":[{"name":"pm2-logrotate","status":"online","cpu":0,"memMB":54,"restarts":0,"uptimeMs":295396823,"startedAt":"2026-07-06T12:52:15.191Z"},{"name":"abramsego","status":"online","cpu":19,"memMB":86,"restarts":7,"uptimeMs":532,"startedAt":"2026-07-09T22:55:31.482Z"},{"name":"pattern-vault","status":"online","cpu":0,"memMB":50,"restarts":1,"uptimeMs":240800126,"startedAt":"2026-07-07T04:02:11.888Z"},{"name":"cabinet-mindmap","status":"online","cpu":0,"memMB":46,"restarts":1,"uptimeMs":240800128,"startedAt":"2026-07-07T04:02:11.886Z"},{"name":"patterndesignlab","status":"online","cpu":0,"memMB":50,"restarts":3,"uptimeMs":240800126,"startedAt":"2026-07-07T04:02:11.888Z"},{"name":"japan-viewer","status":"online","cpu":0,"memMB":389,"restarts":11,"uptimeMs":240800114,"startedAt":"2026-07-07T04:02:11.900Z"},{"name":"reidwitlin-landing","status":"online","cpu":0,"memMB":51,"restarts":14,"uptimeMs":196589730,"startedAt":"2026-07-07T16:19:02.284Z"},{"name":"cncp","status":"online","cpu":0,"memMB":153,"restarts":0,"uptimeMs":204237455,"startedAt":"2026-07-07T14:11:34.559Z"},{"name":"abramsos","status":"online","cpu":0,"memMB":127,"restarts":11,"uptimeMs":86237359,"startedAt":"2026-07-08T22:58:14.655Z"},{"name":"dw-pitch-followup","status":"online","cpu":0,"memMB":79,"restarts":22,"uptimeMs":119313974,"startedAt":"2026-07-08T13:46:58.040Z"},{"name":"wpb-sales-dashboard","status":"online","cpu":0,"memMB":72,"restarts":1,"uptimeMs":88355434,"startedAt":"2026-07-08T22:22:56.580Z"},{"name":"crezana-internal","status":"online","cpu":0,"memMB":73,"restarts":1,"uptimeMs":21791242,"startedAt":"2026-07-09T16:52:20.772Z"},{"name":"fromental-internal","status":"online","cpu":0,"memMB":79,"restarts":1,"uptimeMs":7919136,"startedAt":"2026-07-09T20:43:32.878Z"},{"name":"gracie-internal","status":"online","cpu":0,"memMB":78,"restarts":2,"uptimeMs":7918767,"startedAt":"2026-07-09T20:43:33.247Z"},{"name":"zuber-internal","status":"online","cpu":0,"memMB":85,"restarts":3,"uptimeMs":7919521,"startedAt":"2026-07-09T20:43:32.493Z"}]}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 0206911..1ff27a9 100644
--- a/public/index.html
+++ b/public/index.html
@@ -277,6 +277,13 @@
<div style="margin-top:12px" id="engines"></div>
</section>
+ <!-- WPB MARKETPLACE SALES — proxied from wpb-sales-dashboard (:9812) via /api/wpb -->
+ <section class="card span12" id="wpb-card">
+ <h2>🛍️ WPB Marketplace Sales <span class="muted" id="wpb-sub">· Wallpaper's Back design shops · <a href="http://127.0.0.1:9812/" target="_blank" rel="noopener" style="color:inherit">open :9812 ↗</a></span></h2>
+ <div class="stats" id="wpb-stats"><div class="muted">loading…</div></div>
+ <div style="margin-top:12px" id="wpb-markets"></div>
+ </section>
+
<!-- COST BREAKDOWN -->
<section class="card span4">
<h2>🔌 Cost to run · 30d</h2>
@@ -1006,7 +1013,34 @@ function playEntrance(){
}
let _booted=false;
-async function tick(){ if(document.hidden) return; await load(); if(!_booted){ _booted=true; playEntrance(); } }
+// WPB marketplace sales — fetched from this server's /api/wpb proxy (which pulls :9812).
+async function loadWpb(){
+ let w; try{ w = await (await fetch('/api/wpb')).json(); }catch(e){ w = null; }
+ const stats = document.getElementById('wpb-stats'), mk = document.getElementById('wpb-markets');
+ if(!stats) return;
+ if(!w || !w.ok){ stats.innerHTML = '<div class="muted">WPB dashboard (:9812) unreachable</div>'; if(mk) mk.innerHTML=''; return; }
+ const k = w.kpis || {};
+ const money = v => '$' + (v||0).toLocaleString(undefined,{maximumFractionDigits:2});
+ stats.innerHTML = [
+ ['Gross sales', money(k.salesGross), (k.ordersCount||0)+' orders · '+(k.unitsSold||0)+' units','gold'],
+ ['Net', money(k.salesNet), 'after fees',''],
+ ['Listings', (k.listingsTotal||0), (k.listingsLive||0)+' live · '+(k.listingsPrivate||0)+' private · '+(k.listingsPlanned||0)+' planned',''],
+ ['Designs', (k.designsTotal||0), 'inventory',''],
+ ['Marketplaces', (k.marketplacesLive||0)+'/'+(k.marketplacesTotal||0), 'live',''],
+ ].map(([kk,v,sub,cls])=>`<div class="stat"><div class="k">${kk}</div><div class="v ${cls}">${v}</div><div class="muted">${sub}</div></div>`).join('');
+ if(mk){
+ mk.innerHTML = (w.markets||[]).map(m=>{
+ const pill = m.status==='live' ? '#2ec4b6' : '#8a8f9c';
+ return `<span style="display:inline-flex;align-items:center;gap:6px;margin:3px 10px 3px 0;font-size:12px">
+ <span style="width:9px;height:9px;border-radius:2px;background:${m.color||'#555'}"></span>
+ <b>${m.name}</b>
+ <span style="color:${pill}">${m.status}</span>
+ <span class="muted">${m.listings} listings${m.orders?' · '+money(m.salesGross):''}</span></span>`;
+ }).join('');
+ }
+ if(!w.launched){ const sub=document.getElementById('wpb-sub'); if(sub && !sub.dataset.pre){ sub.dataset.pre='1'; sub.innerHTML += ' · <span style="color:var(--muted,#9aa0ad)">🌱 pre-launch ($0)</span>'; } }
+}
+async function tick(){ if(document.hidden) return; await load(); loadWpb(); if(!_booted){ _booted=true; playEntrance(); } }
tick(); setInterval(tick, 15000);
</script>
</body>
diff --git a/server.js b/server.js
index 2092b90..8e37320 100644
--- a/server.js
+++ b/server.js
@@ -1007,6 +1007,32 @@ app.use((req, res, next) => {
app.get('/api/healthz', (req, res) => res.json({ ok: true, service: 'abramsego', version: VERSION, uptimeSec: Math.round((Date.now() - START_TS) / 1000), snapshotAt: SNAP.builtAt }));
app.get('/api/data', (req, res) => { res.json(SNAP); });
app.get('/api/refresh', async (req, res) => { const s = await buildSnapshot(); res.json(s); });
+
+// WPB marketplace sales — proxied server-to-server from the wpb-sales-dashboard (:9812).
+// Backend-for-frontend: this server attaches the basic-auth header so the browser never
+// sees creds and there's no CORS. Cached ~30s so the 15s tick doesn't hammer :9812.
+const WPB_URL = process.env.WPB_URL || 'http://127.0.0.1:9812/api/data';
+const WPB_AUTH = 'Basic ' + Buffer.from(`${process.env.WPB_USER || 'admin'}:${process.env.WPB_PASS || 'DW2024!'}`).toString('base64');
+let _wpbCache = { at: 0, data: null };
+app.get('/api/wpb', async (req, res) => {
+ if (_wpbCache.data && Date.now() - _wpbCache.at < 30000) return res.json(_wpbCache.data);
+ try {
+ const ctrl = new AbortController();
+ const t = setTimeout(() => ctrl.abort(), 4000);
+ const r = await fetch(WPB_URL, { headers: { Authorization: WPB_AUTH }, signal: ctrl.signal });
+ clearTimeout(t);
+ if (!r.ok) return res.status(502).json({ ok: false, error: `wpb dashboard ${r.status}` });
+ const j = await r.json();
+ // slim payload — only what the panel needs
+ const slim = { ok: true, generatedAt: j.generatedAt, kpis: j.kpis, markets: (j.markets || []).map(m => ({ key: m.key, name: m.name, status: m.status, color: m.color, listings: m.listings, salesGross: m.salesGross, orders: m.orders })), launched: j.launched };
+ _wpbCache = { at: Date.now(), data: slim };
+ res.json(slim);
+ } catch (e) {
+ if (_wpbCache.data) return res.json(_wpbCache.data); // serve stale on transient failure
+ res.status(503).json({ ok: false, error: 'wpb dashboard unreachable (:9812 down?)' });
+ }
+});
+
// per-panel slices (served from cache)
for (const key of ['system', 'localFleet', 'kamatera', 'canaries', 'crons', 'activity', 'sessions', 'wins', 'officers', 'cost', 'revenue', 'pnl', 'usage', 'freshness']) {
app.get(`/api/${key}`, (req, res) => res.json(SNAP[key] || {}));
← 65e6ee5 auto-save: 2026-07-09T15:41:06 (1 files) — data/local-fleet.
·
back to AbramsEgo
·
auto-save: 2026-07-09T16:11:12 (1 files) — data/local-fleet. b518615 →