← 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
A .gitignoreA domains.jsonA public/index.htmlA server.js
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 →