[object Object]

← back to AbramsEgo

add WPB marketplace-sales panel (proxy :9812 via /api/wpb)

25b2bd6f70c849d00bc79ee89c9abdfb7de0f68b · 2026-07-09 15:55:33 -0700 · Steve

Files touched

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 →