[object Object]

← back to Agentabrams Viewer

agentabrams domain viewer: left-rail list of all 47 *.agentabrams.com vhosts, right-panel live iframe, health dots, frame-block fallback

21d5179037c8e051d3f431474c8d25864983ad79 · 2026-07-22 17:39:59 -0700 · Steve Abrams

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files touched

Diff

commit 21d5179037c8e051d3f431474c8d25864983ad79
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 22 17:39:59 2026 -0700

    agentabrams domain viewer: left-rail list of all 47 *.agentabrams.com vhosts, right-panel live iframe, health dots, frame-block fallback
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 .gitignore        |   8 ++++
 domains.json      |  49 ++++++++++++++++++++++
 public/index.html | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 server.js         |  77 ++++++++++++++++++++++++++++++++++
 4 files changed, 256 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/domains.json b/domains.json
new file mode 100644
index 0000000..1f647b8
--- /dev/null
+++ b/domains.json
@@ -0,0 +1,49 @@
+[
+  "agentabrams.com",
+  "www.agentabrams.com",
+  "4square.agentabrams.com",
+  "abramsego.agentabrams.com",
+  "abramsreport.agentabrams.com",
+  "admin.agentabrams.com",
+  "animals.agentabrams.com",
+  "auctions.agentabrams.com",
+  "auth.agentabrams.com",
+  "builds.agentabrams.com",
+  "businessclaw.agentabrams.com",
+  "butlr.agentabrams.com",
+  "cncp.agentabrams.com",
+  "consultingintake.agentabrams.com",
+  "costarica.agentabrams.com",
+  "crcp.agentabrams.com",
+  "dandeana.agentabrams.com",
+  "deadradio.agentabrams.com",
+  "designerline.agentabrams.com",
+  "dogpark.agentabrams.com",
+  "fantasea.agentabrams.com",
+  "forza.agentabrams.com",
+  "gmail.agentabrams.com",
+  "grant.agentabrams.com",
+  "hormuzy.agentabrams.com",
+  "ideas.agentabrams.com",
+  "lawyers.agentabrams.com",
+  "live.agentabrams.com",
+  "livefleet.agentabrams.com",
+  "livesiteaudit.agentabrams.com",
+  "marketing.agentabrams.com",
+  "notes.agentabrams.com",
+  "pitch.sdcc.agentabrams.com",
+  "prestige.agentabrams.com",
+  "prestigecarwash.agentabrams.com",
+  "rentv.agentabrams.com",
+  "rentv-v1.agentabrams.com",
+  "rentv-website.agentabrams.com",
+  "robert.agentabrams.com",
+  "sdcc.agentabrams.com",
+  "sdccanalysis.agentabrams.com",
+  "sixskills.agentabrams.com",
+  "sublease.agentabrams.com",
+  "sublease-admin.agentabrams.com",
+  "usrealestate.agentabrams.com",
+  "venturablvd.agentabrams.com",
+  "videos.agentabrams.com"
+]
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..49f58bb
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<title>agentabrams.com — Domain Viewer</title>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<style>
+  :root { --rail: 300px; --bg:#0e1116; --panel:#161b22; --line:#2b313b; --txt:#dfe6ee; --dim:#8b95a3; --accent:#3fb6a8; }
+  * { box-sizing: border-box; }
+  body { margin:0; height:100vh; display:flex; font:14px/1.45 -apple-system, "SF Pro Text", Helvetica, sans-serif; background:var(--bg); color:var(--txt); }
+
+  #rail { width:var(--rail); min-width:230px; max-width:480px; display:flex; flex-direction:column; border-right:1px solid var(--line); background:var(--panel); }
+  #rail header { padding:14px 14px 10px; border-bottom:1px solid var(--line); }
+  #rail h1 { margin:0 0 8px; font-size:15px; font-weight:700; letter-spacing:.02em; }
+  #rail h1 small { color:var(--dim); font-weight:400; }
+  #q { width:100%; padding:7px 10px; border-radius:7px; border:1px solid var(--line); background:var(--bg); color:var(--txt); outline:none; }
+  #q:focus { border-color:var(--accent); }
+  #list { flex:1; overflow-y:auto; padding:6px; }
+  .item { display:flex; align-items:center; gap:8px; padding:7px 9px; border-radius:7px; cursor:pointer; color:var(--txt); user-select:none; }
+  .item:hover { background:#1e2530; }
+  .item.active { background:#213132; outline:1px solid var(--accent); }
+  .dot { width:8px; height:8px; border-radius:50%; flex:none; background:#555; }
+  .dot.up { background:#3fbf6f; }
+  .dot.auth { background:#d9a53a; }
+  .dot.down { background:#d95555; }
+  .name { flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
+  .name b { color:var(--accent); font-weight:600; }
+  .ms { color:var(--dim); font-size:11px; flex:none; }
+  #foot { padding:8px 14px; border-top:1px solid var(--line); color:var(--dim); font-size:11.5px; }
+
+  #main { flex:1; display:flex; flex-direction:column; min-width:0; }
+  #bar { display:flex; align-items:center; gap:10px; padding:8px 12px; border-bottom:1px solid var(--line); background:var(--panel); }
+  #cur { font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
+  #cur a { color:var(--accent); text-decoration:none; }
+  #bar .sp { flex:1; }
+  button { padding:6px 12px; border-radius:7px; border:1px solid var(--line); background:var(--bg); color:var(--txt); cursor:pointer; font-size:13px; }
+  button:hover { border-color:var(--accent); color:var(--accent); }
+  #frame { flex:1; border:0; width:100%; background:#fff; }
+  #empty { flex:1; display:flex; align-items:center; justify-content:center; color:var(--dim); flex-direction:column; gap:8px; }
+  #empty .big { font-size:40px; }
+</style>
+</head>
+<body>
+  <div id="rail">
+    <header>
+      <h1>agentabrams.com <small id="count"></small></h1>
+      <input id="q" type="search" placeholder="Filter domains…" autocomplete="off">
+    </header>
+    <div id="list"></div>
+    <div id="foot">click a domain → loads in the right panel · dot: <span style="color:#3fbf6f">up</span> / <span style="color:#d9a53a">auth-gated</span> / <span style="color:#d95555">down</span></div>
+  </div>
+  <div id="main">
+    <div id="bar">
+      <span id="cur">— pick a domain</span>
+      <span class="sp"></span>
+      <button id="reload" title="Reload iframe">⟳ Reload</button>
+      <button id="newtab" title="Open in a new browser tab">↗ New Tab</button>
+    </div>
+    <div id="empty"><div class="big">🌐</div><div>Select a domain on the left to view the live site here.</div></div>
+    <iframe id="frame" style="display:none" referrerpolicy="no-referrer"></iframe>
+  </div>
+
+<script>
+let domains = [], status = {}, current = localStorage.getItem('aav-current') || '';
+const list = document.getElementById('list'), q = document.getElementById('q');
+const frame = document.getElementById('frame'), empty = document.getElementById('empty');
+const cur = document.getElementById('cur'), count = document.getElementById('count');
+
+function render() {
+  const f = q.value.trim().toLowerCase();
+  const shown = domains.filter(d => d.includes(f));
+  count.textContent = `(${shown.length}/${domains.length})`;
+  list.innerHTML = '';
+  for (const d of shown) {
+    const s = status[d];
+    const row = document.createElement('div');
+    row.className = 'item' + (d === current ? ' active' : '');
+    const dotCls = !s ? '' : s.status === 401 ? 'auth' : s.up ? 'up' : 'down';
+    const label = f ? d.replace(f, `<b>${f}</b>`) : d;
+    row.innerHTML = `<span class="dot ${dotCls}"></span><span class="name">${label}</span>` +
+      (s && s.up ? `<span class="ms">${s.ms}ms</span>` : s ? `<span class="ms">—</span>` : '');
+    row.onclick = () => select(d);
+    list.appendChild(row);
+  }
+}
+
+function select(d) {
+  current = d;
+  localStorage.setItem('aav-current', d);
+  cur.innerHTML = `<a href="https://${d}" target="_blank" rel="noopener noreferrer">${d}</a>`;
+  const s = status[d];
+  if (s && s.framable === false) {
+    frame.style.display = 'none'; frame.src = 'about:blank';
+    empty.style.display = 'flex';
+    empty.innerHTML = `<div class="big">🚫</div><div><b>${d}</b> sends X-Frame-Options and refuses to render inside a panel.</div>` +
+      `<button onclick="window.open('https://${d}','_blank','noopener')">↗ Open in New Tab</button>`;
+  } else {
+    empty.style.display = 'none';
+    frame.style.display = 'block';
+    frame.src = 'https://' + d;
+  }
+  render();
+}
+
+document.getElementById('reload').onclick = () => { if (current) frame.src = 'https://' + current; };
+document.getElementById('newtab').onclick = () => { if (current) window.open('https://' + current, '_blank', 'noopener'); };
+q.oninput = render;
+
+(async () => {
+  domains = await (await fetch('/api/domains')).json();
+  render();
+  if (current && domains.includes(current)) select(current);
+  // Health dots load after the list — probes take a few seconds for 47 domains.
+  const st = await (await fetch('/api/status')).json();
+  for (const s of st) status[s.domain] = s;
+  // Re-select so a frame-blocked domain picked before probes finished swaps to the fallback card.
+  if (current && status[current] && status[current].framable === false) select(current);
+  render();
+})();
+</script>
+</body>
+</html>
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..013e3cf
--- /dev/null
+++ b/server.js
@@ -0,0 +1,77 @@
+#!/usr/bin/env node
+// agentabrams-viewer — left-panel domain list, right-panel iframe of the live site.
+// Zero-dependency Node http server. Basic auth admin/DW2024! (internal-viewer standard).
+const http = require('http');
+const https = require('https');
+const fs = require('fs');
+const path = require('path');
+
+const PORT = process.env.PORT || 9783;
+const ROOT = __dirname;
+const AUTH = 'Basic ' + Buffer.from(process.env.BASIC_AUTH || 'admin:DW2024!').toString('base64');
+
+const MIME = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.png': 'image/png', '.svg': 'image/svg+xml' };
+
+function loadDomains() {
+  return JSON.parse(fs.readFileSync(path.join(ROOT, 'domains.json'), 'utf8'));
+}
+
+// Server-side health probe — the browser can't check cross-origin status itself.
+// 401 counts as UP (basic-auth gate is the healthy state for several of these sites).
+function probe(domain) {
+  return new Promise((resolve) => {
+    const started = Date.now();
+    const req = https.request({ host: domain, method: 'HEAD', path: '/', timeout: 6000, rejectUnauthorized: false }, (res) => {
+      res.resume();
+      resolve({ domain, status: res.statusCode, ms: Date.now() - started, up: res.statusCode < 500, framable: !frameBlocked(res.headers) });
+    });
+    req.on('timeout', () => { req.destroy(); resolve({ domain, status: 0, ms: Date.now() - started, up: false, framable: true }); });
+    req.on('error', () => resolve({ domain, status: 0, ms: Date.now() - started, up: false, framable: true }));
+    req.end();
+  });
+}
+
+function frameBlocked(headers) {
+  const xfo = (headers['x-frame-options'] || '').toLowerCase();
+  if (xfo.includes('deny') || xfo.includes('sameorigin')) return true;
+  const csp = (headers['content-security-policy'] || '').toLowerCase();
+  return /frame-ancestors\s+(?!.*agentabrams)('none'|'self')/.test(csp);
+}
+
+const server = http.createServer(async (req, res) => {
+  const url = new URL(req.url, 'http://x');
+
+  if (url.pathname === '/healthz') { res.writeHead(200); return res.end('ok'); }
+
+  if (req.headers.authorization !== AUTH) {
+    res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="agentabrams-viewer"' });
+    return res.end('auth required');
+  }
+
+  if (url.pathname === '/api/domains') {
+    res.writeHead(200, { 'Content-Type': 'application/json' });
+    return res.end(JSON.stringify(loadDomains()));
+  }
+
+  if (url.pathname === '/api/status') {
+    const domains = loadDomains();
+    // Probe in batches of 12 so 47 HEADs don't stampede.
+    const out = [];
+    for (let i = 0; i < domains.length; i += 12) {
+      out.push(...await Promise.all(domains.slice(i, i + 12).map(probe)));
+    }
+    res.writeHead(200, { 'Content-Type': 'application/json' });
+    return res.end(JSON.stringify(out));
+  }
+
+  let file = url.pathname === '/' ? '/index.html' : url.pathname;
+  const fp = path.join(ROOT, 'public', path.normalize(file).replace(/^(\.\.[\/\\])+/, ''));
+  if (!fp.startsWith(path.join(ROOT, 'public'))) { res.writeHead(403); return res.end(); }
+  fs.readFile(fp, (err, data) => {
+    if (err) { res.writeHead(404); return res.end('not found'); }
+    res.writeHead(200, { 'Content-Type': MIME[path.extname(fp)] || 'application/octet-stream' });
+    res.end(data);
+  });
+});
+
+server.listen(PORT, () => console.log(`agentabrams-viewer on http://127.0.0.1:${PORT}`));

(oldest)  ·  back to Agentabrams Viewer  ·  viewer: credential-injecting reverse proxy so unified admin 02f58ea →