← back to Dw Internal Gateway
Add 'Re-check all sites' control + /api/status health probe + status dots on landing
1061465152175757fa88895d3f7351c52e0db46f · 2026-07-28 08:17:33 -0700 · Steve Abrams
Files touched
Diff
commit 1061465152175757fa88895d3f7351c52e0db46f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jul 28 08:17:33 2026 -0700
Add 'Re-check all sites' control + /api/status health probe + status dots on landing
---
server.js | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 67 insertions(+), 7 deletions(-)
diff --git a/server.js b/server.js
index 6c5de09..faa764f 100644
--- a/server.js
+++ b/server.js
@@ -216,6 +216,23 @@ load(true);
</script></body></html>`;
}
+// Health probe for the "Re-check all sites" control on the landing page. Hits a
+// local port's /healthz (any answer works) — anything that responds with a status
+// < 500, incl. a 401 auth gate, is "up". This is a re-CHECK, not a restart: it only
+// re-tests reachability, so it's safe to click anytime.
+function probePort(port) {
+ return new Promise((resolve) => {
+ const started = Date.now();
+ const rq = http.request({ host: '127.0.0.1', port, method: 'GET', path: '/healthz', timeout: 4000 }, (r) => {
+ r.resume();
+ resolve({ up: r.statusCode > 0 && r.statusCode < 500, status: r.statusCode, ms: Date.now() - started });
+ });
+ rq.on('timeout', () => { rq.destroy(); resolve({ up: false, status: 0, ms: Date.now() - started }); });
+ rq.on('error', () => resolve({ up: false, status: 0, ms: Date.now() - started }));
+ rq.end();
+ });
+}
+
async function indexPage() {
await refreshVendorMap();
const { rows } = await pool.query(`
@@ -227,8 +244,9 @@ async function indexPage() {
const suffix = PORT === 80 ? '' : ':' + PORT;
const catalogSlugs = new Set(rows.map(r => r.table_name.replace(/_catalog$/, '').replace(/_/g, '-')));
- const li = (slug, badge, n) =>
- `<li><a href="//${slug}.internal.dw${suffix}/">${esc(slug)}.internal.dw</a>` +
+ const li = (slug, badge, n, port) =>
+ `<li><span class="dot"${port ? ` data-port="${port}"` : ' data-alldw="1"'}></span>` +
+ `<a href="//${slug}.internal.dw${suffix}/">${esc(slug)}.internal.dw</a>` +
(n != null ? ` <span class="n">${Number(n).toLocaleString()}</span>` : '') + ` ${badge}</li>`;
const catalogSet = catalogSlugs;
@@ -237,25 +255,25 @@ async function indexPage() {
// Group 1 — bespoke dedicated viewers.
const dedicated = Object.entries(ROUTES)
.sort((a, b) => a[0].localeCompare(b[0]))
- .map(([slug, port]) => li(slug, `<span class="ded">dedicated :${port}</span>`, null));
+ .map(([slug, port]) => li(slug, `<span class="ded">dedicated :${port}</span>`, null, port));
// Group 2 — all.dw-schema vendors with LIVE shopify rows (not dedicated).
const alldw = [...VENDOR_MAP.entries()]
.filter(([slug, e]) => !ROUTES[slug] && e.live > 0)
.sort((a, b) => a[0].localeCompare(b[0]))
- .map(([slug, e]) => li(slug, `<span class="all">all.dw</span>`, e.live));
+ .map(([slug, e]) => li(slug, `<span class="all">all.dw</span>`, e.live, null));
// Group 3 — catalog-backed lines (a *_catalog table, not dedicated, not a live vendor).
const catalogOnly = rows
.map(r => [r.table_name.replace(/_catalog$/, '').replace(/_/g, '-'), Number(r.est)])
.filter(([slug]) => !ROUTES[slug] && !liveVendor(slug))
- .map(([slug, est]) => li(slug, `<span class="cat">catalog</span>`, est));
+ .map(([slug, est]) => li(slug, `<span class="cat">catalog</span>`, est, null));
// Group 4 — archived-only shopify vendors (no live rows, no catalog table) → all.dw Archived view.
const archived = [...VENDOR_MAP.entries()]
.filter(([slug, e]) => !ROUTES[slug] && e.live === 0 && e.total > 0 && !catalogSet.has(slug))
.sort((a, b) => a[0].localeCompare(b[0]))
- .map(([slug, e]) => li(slug, `<span class="arc">archived</span>`, e.total));
+ .map(([slug, e]) => li(slug, `<span class="arc">archived</span>`, e.total, null));
const total = dedicated.length + alldw.length + catalogOnly.length + archived.length;
const section = (title, arr) => arr.length
@@ -274,12 +292,44 @@ a{color:#1e2a3a;text-decoration:none}a:hover{text-decoration:underline}
.all{color:#1e4b8a;font-size:10px;border:1px solid #1e4b8a;border-radius:4px;padding:0 4px}
.cat{color:#8a6d1e;font-size:10px;border:1px solid #8a6d1e;border-radius:4px;padding:0 4px}
.arc{color:#777;font-size:10px;border:1px solid #999;border-radius:4px;padding:0 4px}
+.dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:#c3c3c3;margin-right:7px;vertical-align:middle}
+.dot.up{background:#2e7d32}.dot.down{background:#c0392b}
+#recheck{margin-top:10px;background:#2c3e52;color:#fff;border:1px solid #46617d;border-radius:6px;padding:6px 13px;font-size:13px;cursor:pointer}
+#recheck:hover:not(:disabled){background:#3a516b}#recheck:disabled{opacity:.6;cursor:progress}
+#lastcheck{color:#cbd6e2;font-size:12px;margin-top:7px}
@media(max-width:900px){ul{columns:2}}@media(max-width:600px){ul{columns:1}}</style></head>
-<body><header><h1>internal.dw — ${total} vendor internal sites</h1></header>
+<body><header><h1>internal.dw — ${total} vendor internal sites</h1>
+<button id="recheck" title="Re-run the health check on every internal site (no restart — just re-probes reachability)">↻ Re-check all sites</button>
+<div id="lastcheck"></div></header>
${section('Dedicated viewers', dedicated)}
${section('all.dw schema · shopify vendors', alldw)}
${section('Catalog-backed lines', catalogOnly)}
${section('Archived lines', archived)}
+<script>
+const rc=document.getElementById('recheck'),lc=document.getElementById('lastcheck');
+let busy=false;
+// Re-CHECK (not restart): re-probe every internal site and refresh the dots.
+// Dedicated viewers get their own dot (own port); all.dw/catalog/archived rows
+// share the single all.dw backend probe.
+async function loadStatus(){
+ if(busy)return;busy=true;rc.disabled=true;rc.textContent='↻ Checking…';
+ try{
+ const r=await fetch('/api/status');if(!r.ok)throw new Error('HTTP '+r.status);
+ const d=await r.json();let up=0,tot=0;
+ document.querySelectorAll('.dot').forEach(el=>{
+ tot++;
+ const st=el.dataset.port?d.dedicated[el.dataset.port]:d.alldw;
+ const ok=!!(st&&st.up);
+ el.className='dot '+(ok?'up':'down');
+ if(ok)up++;
+ });
+ lc.textContent='checked '+up+'/'+tot+' up · '+new Date().toLocaleTimeString([],{hour:'numeric',minute:'2-digit',second:'2-digit'});
+ }catch(e){ lc.textContent='check failed — click ↻ to retry'; }
+ finally{ busy=false;rc.disabled=false;rc.textContent='↻ Re-check all sites'; }
+}
+rc.onclick=loadStatus;
+loadStatus();
+</script>
</body></html>`;
}
@@ -372,6 +422,16 @@ const server = http.createServer(async (req, res) => {
const slug = m ? m[1] : null;
if (!m || !slug || slug === 'www') {
+ const u = new URL(req.url, 'http://x');
+ // Fleet-wide re-check: probe every dedicated viewer port + the shared all.dw
+ // backend. Non-dedicated sites (all.dw/catalog/archived) share the :9958 fate.
+ if (u.pathname === '/api/status') {
+ const dedicated = {};
+ await Promise.all(Object.entries(ROUTES).map(async ([, port]) => { dedicated[port] = await probePort(port); }));
+ const alldw = await probePort(ALLDW_PORT);
+ res.writeHead(200, { 'content-type': 'application/json' });
+ return res.end(JSON.stringify({ dedicated, alldw }));
+ }
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
return res.end(await indexPage());
}
← 9940353 gateway: prefer staging catalog over tiny live-shopify view
·
back to Dw Internal Gateway
·
(newest)