← 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 && 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>