[object Object]

← back to Coming Soon Template

add coming-soon fleet review viewer (21 domains + dead-domain purge + George status) on :9778

b5bdfdf775205126939f4966be27356bbb0b2b81 · 2026-05-18 13:07:10 -0700 · SteveStudio2

Files touched

Diff

commit b5bdfdf775205126939f4966be27356bbb0b2b81
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Mon May 18 13:07:10 2026 -0700

    add coming-soon fleet review viewer (21 domains + dead-domain purge + George status) on :9778
---
 viewer/index.html | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 viewer/server.js  | 124 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 277 insertions(+)

diff --git a/viewer/index.html b/viewer/index.html
new file mode 100644
index 0000000..5990c1d
--- /dev/null
+++ b/viewer/index.html
@@ -0,0 +1,153 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Coming-Soon Fleet — Review</title>
+<style>
+  :root{
+    --bg:#0e0d0b; --panel:#1a1815; --line:#2e2a24; --ink:#ece7dc; --dim:#8c857a;
+    --gold:#c9a24b; --ok:#3fae6b; --warn:#d6a23a; --bad:#cf5b4e; --pend:#5b87c4;
+  }
+  *{box-sizing:border-box}
+  body{margin:0;background:var(--bg);color:var(--ink);
+    font:14px/1.55 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
+  header{padding:22px 28px 14px;border-bottom:1px solid var(--line)}
+  h1{margin:0;font-size:18px;font-weight:600;letter-spacing:.04em}
+  h1 span{color:var(--gold)}
+  .sub{color:var(--dim);font-size:12px;margin-top:4px}
+  .wrap{padding:20px 28px;max-width:1200px}
+  h2{font-size:13px;letter-spacing:.08em;text-transform:uppercase;color:var(--gold);
+    margin:28px 0 10px;font-weight:600}
+  .cards{display:flex;gap:12px;flex-wrap:wrap;margin:14px 0 4px}
+  .card{background:var(--panel);border:1px solid var(--line);border-radius:8px;
+    padding:12px 16px;min-width:120px}
+  .card .n{font-size:24px;font-weight:700}
+  .card .l{font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.06em}
+  table{width:100%;border-collapse:collapse;margin-top:6px}
+  th,td{text-align:left;padding:7px 10px;border-bottom:1px solid var(--line);font-size:13px}
+  th{color:var(--dim);font-weight:500;font-size:11px;text-transform:uppercase;letter-spacing:.05em}
+  tr:hover td{background:#171511}
+  a{color:var(--gold);text-decoration:none}
+  a:hover{text-decoration:underline}
+  .pill{display:inline-block;padding:1px 8px;border-radius:10px;font-size:11px;font-weight:600}
+  .pill.ok{background:rgba(63,174,107,.16);color:var(--ok)}
+  .pill.pend{background:rgba(91,135,196,.16);color:var(--pend)}
+  .pill.warn{background:rgba(214,162,58,.16);color:var(--warn)}
+  .pill.bad{background:rgba(207,91,78,.16);color:var(--bad)}
+  .pill.host{background:#26221b;color:var(--ink)}
+  .note{color:var(--dim);font-size:12px}
+  .deadrow td{color:var(--dim)}
+  .deadrow .dd{color:var(--bad);text-decoration:line-through}
+  .foot{color:var(--dim);font-size:11px;margin-top:24px;padding-top:12px;border-top:1px solid var(--line)}
+  .cmd{background:#000;border:1px solid var(--line);border-radius:6px;padding:9px 12px;
+    color:var(--gold);font-size:12px;margin:6px 0;overflow-x:auto}
+  #err{color:var(--bad);font-size:12px}
+</style>
+</head>
+<body>
+<header>
+  <h1><span>◆</span> Coming-Soon Fleet — Review</h1>
+  <div class="sub" id="stamp">loading…</div>
+</header>
+<div class="wrap">
+  <div id="err"></div>
+
+  <div class="cards" id="cards"></div>
+
+  <h2>21 Domains — Cloudflare</h2>
+  <table id="domains">
+    <thead><tr>
+      <th>Domain</th><th>Host</th><th>CF Zone</th><th>DNS</th>
+      <th>Nameservers</th><th>Registrar</th><th>Preview</th><th>Live</th>
+    </tr></thead>
+    <tbody></tbody>
+  </table>
+  <div class="note" id="flipnote"></div>
+
+  <h2>Pending Action — Nameserver Flip</h2>
+  <div class="note">Auto-mode blocks the flip from Claude. Run it yourself:</div>
+  <div class="cmd">!cd ~/Projects/coming-soon-template &amp;&amp; node scripts/godaddy_ns_api.js --apply</div>
+
+  <h2>Dead Domains — Purged</h2>
+  <table id="dead"><tbody></tbody></table>
+
+  <h2>George — Gmail Accounts</h2>
+  <div class="note">George's OAuth tokens are dead. <code>/auth</code> only re-authorizes
+    <b>steve-office</b> (it hardcodes that login hint). Open in a tailnet browser:
+    <a href="http://100.107.67.67:9850/auth" target="_blank">http://100.107.67.67:9850/auth</a></div>
+  <table id="george">
+    <thead><tr><th>Slot</th><th>Email</th><th>/auth re-auths it?</th><th>Reachable</th></tr></thead>
+    <tbody></tbody>
+  </table>
+
+  <div class="foot" id="foot"></div>
+</div>
+
+<script>
+const $ = s => document.querySelector(s);
+
+function pill(cls, txt){ return '<span class="pill '+cls+'">'+txt+'</span>'; }
+
+function render(d){
+  $('#err').textContent = '';
+  $('#stamp').textContent = 'generated ' + new Date(d.generatedAt).toLocaleString()
+    + '  ·  auto-refresh 30s';
+
+  const s = d.summary;
+  $('#cards').innerHTML = [
+    ['Total', s.total, ''],
+    ['Pages', s.pages, ''],
+    ['Workers', s.workers, ''],
+    ['NS Flipped', s.flipped + ' / ' + s.total, s.flipped===s.total?'ok':'pend'],
+  ].map(c => '<div class="card"><div class="n">'+c[1]+'</div>'
+    + '<div class="l">'+c[0]+'</div></div>').join('');
+
+  $('#domains tbody').innerHTML = d.domains.map(x => {
+    const ns = x.ns_on_cloudflare ? pill('ok','Cloudflare')
+      : pill('pend','GoDaddy — awaiting flip');
+    const zone = /created|already_exists/.test(x.zone_status)
+      ? pill('ok','ready') : pill('warn', x.zone_status);
+    const dns = x.dns_ready ? pill('ok','wired') : pill('bad','missing');
+    const reg = x.registrar_status==='ACTIVE'
+      ? pill('ok','ACTIVE') : pill('warn', x.registrar_status);
+    return '<tr><td>'+x.domain+'</td>'
+      + '<td>'+pill('host',x.host)+'</td>'
+      + '<td>'+zone+'</td><td>'+dns+'</td><td>'+ns+'</td><td>'+reg+'</td>'
+      + '<td>'+(x.pages_dev?'<a href="https://'+x.pages_dev+'" target="_blank">'+x.pages_dev+'</a>':'<span class="note">worker</span>')+'</td>'
+      + '<td><a href="'+x.live_url+'" target="_blank">open ↗</a></td></tr>';
+  }).join('');
+
+  const flipped = d.summary.flipped, total = d.summary.total;
+  $('#flipnote').textContent = flipped===0
+    ? 'No domains flipped yet — run the command below; this page turns green as nameservers propagate.'
+    : flipped<total ? flipped+' of '+total+' flipped — propagation in progress.'
+    : 'All '+total+' on Cloudflare nameservers. ✓';
+
+  $('#dead tbody').innerHTML = d.dead.map(x =>
+    '<tr class="deadrow"><td class="dd">'+x.domain+'</td>'
+    + '<td>'+pill('bad','DEAD')+'</td><td>'+x.note+'</td></tr>').join('');
+
+  $('#george tbody').innerHTML = d.george.map(g => {
+    const isAuth = g.slot==='steve-office';
+    return '<tr><td>'+g.slot+'</td><td>'+g.email+'</td>'
+      + '<td>'+(isAuth?pill('ok','yes — default'):pill('warn','no — needs /auth fix'))+'</td>'
+      + '<td>'+(g.reachable?pill('ok','up'):pill('bad','unreachable'))+'</td></tr>';
+  }).join('');
+
+  $('#foot').textContent = 'coming-soon-template review viewer · 127.0.0.1:9778';
+}
+
+async function load(){
+  try{
+    const r = await fetch('/api/status');
+    const d = await r.json();
+    if(d.error){ $('#err').textContent = 'API error: '+d.error; return; }
+    render(d);
+  }catch(e){ $('#err').textContent = 'load failed: '+e.message; }
+}
+load();
+setInterval(load, 30000);
+</script>
+</body>
+</html>
diff --git a/viewer/server.js b/viewer/server.js
new file mode 100644
index 0000000..cd5fac4
--- /dev/null
+++ b/viewer/server.js
@@ -0,0 +1,124 @@
+#!/usr/bin/env node
+/* Coming-Soon fleet review viewer — single page, zero dependencies.
+ * Consolidates: the 21 abrams/butler domains (Pages/Worker/DNS/NS state),
+ * the dead-domain purge, and George account status.
+ *
+ *   node viewer/server.js        # serves http://127.0.0.1:9778
+ *
+ * /api/status aggregates live GoDaddy NS state + the project's data/*.json.
+ */
+'use strict';
+const http = require('http');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+
+const PORT = 9778;
+const ENV = path.join(os.homedir(), 'Projects/secrets-manager/.env');
+const ROOT = path.join(os.homedir(), 'Projects/coming-soon-template');
+const WORKER_DOMAINS = ['bhbutler.com', 'boulevardbutler.com'];
+
+function envVal(key) {
+  const line = fs.readFileSync(ENV, 'utf8').split('\n').find(l => l.startsWith(key + '='));
+  return line ? line.slice(key.length + 1).trim() : '';
+}
+const GD_KEY = envVal('GODADDY_API_KEY');
+const GD_SECRET = envVal('GODADDY_API_SECRET');
+
+const readJSON = (p, fb) => { try { return JSON.parse(fs.readFileSync(path.join(ROOT, p), 'utf8')); } catch { return fb; } };
+const isCfNs = ns => Array.isArray(ns) && ns.length > 0 && ns.every(h => /\.ns\.cloudflare\.com$/i.test(h));
+
+async function godaddyNs(domain) {
+  try {
+    const res = await fetch(`https://api.godaddy.com/v1/domains/${domain}`, {
+      headers: { 'Authorization': `sso-key ${GD_KEY}:${GD_SECRET}` },
+    });
+    const j = await res.json();
+    return { ns: j.nameServers || [], status: j.status || 'UNKNOWN' };
+  } catch (e) {
+    return { ns: [], status: 'ERROR', error: String(e.message || e) };
+  }
+}
+
+async function georgeStatus() {
+  try {
+    const ctl = AbortSignal.timeout(5000);
+    const res = await fetch('http://100.107.67.67:9850/health', { signal: ctl });
+    const j = await res.json();
+    return Object.entries(j.accounts || {}).map(([slot, a]) => ({
+      slot, email: a.email, reachable: true,
+    }));
+  } catch {
+    return [
+      { slot: 'steve-office', email: 'steve@designerwallcoverings.com', reachable: false },
+      { slot: 'info', email: 'info@designerwallcoverings.com', reachable: false },
+      { slot: 'steve-personal', email: 'steveabramsdesigns@gmail.com', reachable: false },
+    ];
+  }
+}
+
+async function buildStatus() {
+  const zones = readJSON('data/zone-setup.json', []);
+  const dns = readJSON('data/dns-setup.json', []);
+  const dnsByDomain = Object.fromEntries(dns.map(d => [d.domain, d]));
+
+  const domains = await Promise.all(zones.map(async z => {
+    const gd = await godaddyNs(z.domain);
+    const project = z.domain.replace(/\.com$/, '');
+    const isWorker = WORKER_DOMAINS.includes(z.domain);
+    const d = dnsByDomain[z.domain];
+    return {
+      domain: z.domain,
+      host: isWorker ? 'Worker' : 'Pages',
+      zone_status: z.status,
+      dns_ready: isWorker ? true : !!(d && /already_correct|created|updated/.test(d.status || '')),
+      ns_on_cloudflare: isCfNs(gd.ns),
+      registrar_status: gd.status,
+      current_ns: gd.ns,
+      pages_dev: isWorker ? null : `${project}.pages.dev`,
+      live_url: `https://${z.domain}`,
+    };
+  }));
+
+  return {
+    generatedAt: new Date().toISOString(),
+    domains,
+    summary: {
+      total: domains.length,
+      pages: domains.filter(d => d.host === 'Pages').length,
+      workers: domains.filter(d => d.host === 'Worker').length,
+      flipped: domains.filter(d => d.ns_on_cloudflare).length,
+    },
+    dead: [
+      { domain: 'brazilliancewallpaper.com', note: 'Expired 2026-05-04 (PENDING_HOLD_REDEMPTION). Purged from 5 active-config files + memory.' },
+      { domain: 'dohngia.com', note: 'Expired 2026-05-04 (PENDING_HOLD_REDEMPTION). Purged from godaddy-active.json + decisions.json + memory.' },
+    ],
+    george: await georgeStatus(),
+  };
+}
+
+const server = http.createServer(async (req, res) => {
+  if (req.url === '/api/status') {
+    try {
+      const data = await buildStatus();
+      res.writeHead(200, { 'Content-Type': 'application/json' });
+      res.end(JSON.stringify(data));
+    } catch (e) {
+      res.writeHead(500, { 'Content-Type': 'application/json' });
+      res.end(JSON.stringify({ error: String(e.message || e) }));
+    }
+    return;
+  }
+  // serve the single page
+  try {
+    const html = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
+    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
+    res.end(html);
+  } catch {
+    res.writeHead(404); res.end('index.html missing');
+  }
+});
+
+server.listen(PORT, '127.0.0.1', () => {
+  console.log(`coming-soon review viewer -> http://127.0.0.1:${PORT}`);
+});

← a06f760 deploy bhbutler+boulevardbutler as Workers; flip-script guar  ·  back to Coming Soon Template  ·  snapshot — gitify backup 2026-05-19 b48c7da →