← back to Coming Soon Template

viewer/index.html

154 lines

<!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" rel="noopener noreferrer">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" rel="noopener noreferrer">'+x.pages_dev+'</a>':'<span class="note">worker</span>')+'</td>'
      + '<td><a href="'+x.live_url+'" target="_blank" rel="noopener noreferrer">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>