← back to Sublease Agentabrams

public/admin.html

51 lines

<!doctype html>
<html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Admin · CRUnifiedDB</title>
<style>
  :root{--ink:#14181d;--mut:#6b7480;--line:#e7e9ee;--bg:#fbfbfc;--accent:#1a5f3c}
  body{margin:0;font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,sans-serif;color:var(--ink);background:var(--bg)}
  .wrap{max-width:1100px;margin:0 auto;padding:24px 22px}
  a{color:inherit}h1{font-size:20px;margin:0 0 4px}.sub{color:var(--mut);margin:0 0 22px}
  h2{font-size:13px;text-transform:uppercase;letter-spacing:.09em;color:var(--mut);margin:26px 0 12px}
  .cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px}
  .card{border:1px solid var(--line);border-radius:12px;background:#fff;padding:14px}
  .card .n{font-weight:700}.card .m{color:var(--mut);font-size:12.5px}
  .when{display:inline-block;margin-top:8px;font-size:11.5px;color:var(--mut);background:#f4f6f8;border-radius:6px;padding:3px 8px}
  .st{font-size:11px;padding:2px 7px;border-radius:20px;text-transform:uppercase;letter-spacing:.04em}
  .st.ok{background:#e7f5ec;color:#1a7a44}.st.error{background:#fbe9e7;color:#b3261e}.st.running{background:#eef2f7;color:#4a5b6d}
  .st.high{background:#fbe9e7;color:#b3261e}.st.low{background:#eef2f7;color:#4a5b6d}
  code{background:#f0f2f5;padding:1px 5px;border-radius:4px;font-size:12px}
  table{width:100%;border-collapse:collapse;background:#fff;border:1px solid var(--line);border-radius:12px;overflow:hidden}
  th,td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--line);font-size:13px}
  th{color:var(--mut);text-transform:uppercase;font-size:11px;letter-spacing:.06em}
</style></head><body><div class="wrap">
<h1>CRUnifiedDB — Admin</h1>
<p class="sub"><a href="/">← marketplace</a> · <a href="/claims">profile claims →</a> · per-firm crawler agents write here. Run one with <code>node crawl/run.js &lt;slug&gt;</code>.</p>

<h2>Sponsor firms</h2>
<div class="cards" id="sponsors"></div>

<h2>Recent crawl runs</h2>
<table id="runs"><thead><tr><th>Firm</th><th>Status</th><th>Found</th><th>Finished (local)</th></tr></thead><tbody></tbody></table>
</div>
<script>
const fmt=t=>t?new Date(t).toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}):'—';
async function boot(){
  const [sp,st]=await Promise.all([fetch('/api/sponsors').then(r=>r.json()),fetch('/api/stats').then(r=>r.json())]);
  document.getElementById('sponsors').innerHTML=sp.sponsors.map(s=>`
    <div class="card">
      <div class="n">${s.name}</div>
      <div class="m">${s.kind} · ${s.role.replace('_',' ')}</div>
      <div style="margin:8px 0"><span class="st ${s.tos_risk}">ToS: ${s.tos_risk}</span>
        ${s.role==='broker'?`<span class="st low">${s.listing_count} listings</span>`:'<span class="st low">financing</span>'}</div>
      <div class="when" title="${s.created_at}">🕓 added ${fmt(s.created_at)}</div>
    </div>`).join('');
  document.querySelector('#runs tbody').innerHTML=(st.recent_runs||[]).length?st.recent_runs.map(r=>`
    <tr><td>${r.slug}</td><td><span class="st ${r.status}">${r.status}</span></td>
      <td>${r.listings_found??'—'}</td><td>${fmt(r.finished_at)}</td></tr>`).join(''):
    `<tr><td colspan="4" style="color:var(--mut)">No crawl runs yet.</td></tr>`;
}
boot();
</script></body></html>