Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions public/admin/claims.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
.error-panel{border:1px solid #fecaca;background:#fef2f2;color:#991b1b;padding:10px;border-radius:10px;margin-bottom:12px;}
.card-url-row{display:grid;grid-template-columns:minmax(0,1fr) auto auto; gap:8px; align-items:center; padding:8px 0; border-bottom:1px solid #f1f5f9;}
.claims-list { display:flex; flex-direction:column; gap:8px; }
.claim-item { border:1px solid #e5e7eb; border-radius:12px; padding:10px 12px; background:#fff; cursor:pointer; }
.claim-item { border:1px solid #e5e7eb; border-radius:12px; padding:10px 12px; background:#fff; cursor:pointer; transition:background .15s ease,border-color .15s ease; }
.claim-item:hover { border-color:#93c5fd; background:#f8fbff; }
.claim-item.selected { border-color:#2563eb; background:#eef2ff; }
.claim-head, .claim-meta { display:flex; justify-content:space-between; gap:12px; align-items:center; }
.claim-meta { color:#6b7280; font-size:13px; }
Expand All @@ -52,13 +53,13 @@ <h1>CommandLayer Claims Admin</h1><p class="muted">Internal operator dashboard f
(() => {
const s={claims:[],selected:'',detail:null,error:null,checkoutLoading:false,checkoutUrl:null};
const apiKey=document.getElementById('apiKey'); apiKey.value=sessionStorage.getItem('cl_admin_api_key')||localStorage.getItem('cl_admin_api_key')||'';
const headers=()=>({Authorization:`Bearer ${apiKey.value.trim()}`,'Content-Type':'application/json'});
const headers=()=>{const key=apiKey.value.trim();return {Authorization:`Bearer ${key}`,'x-admin-api-key':key,'Content-Type':'application/json'};};
const short=(v,n=10)=>v?`${v.slice(0,n)}…`:''; const dt=(v)=>v?new Date(v).toLocaleString():'-';
const badge=(status)=>`<span class="badge ${status}">${status}</span>`;
const copyBtn=(txt)=>`<button class='btn btn-muted copy-url' data-url='${txt.replaceAll("'", '&#39;')}'>Copy URL</button>`;
function renderClaims(){const b=document.getElementById('claimsBody');b.innerHTML='';document.getElementById('claimsCount').textContent=`(${s.claims.length} loaded)`;for(const c of s.claims){const row=document.createElement('button');row.type='button';row.className=`claim-item ${s.selected===c.claimId?'selected':''}`;row.innerHTML=`<div class='claim-head'><span class='mono'>${short(c.claimId,14)}</span>${badge(c.status)}</div><div class='claim-meta'><span>${c.tenant||'-'}</span><span>${c.packId||'-'} · ${c.agentCount||0} agents</span></div><div class='claim-meta'><span>${dt(c.createdAt)}</span></div>`;row.onclick=()=>loadDetail(c.claimId);b.appendChild(row);}}
async function loadClaims(){status('Loading claims...');const r=await fetch('/api/admin/claims',{headers:{Authorization:headers().Authorization}});const d=await r.json();if(!r.ok||!d.ok){status(`${r.status} ${d.status}`);return;}s.claims=d.claims||[];renderClaims();status(`Loaded ${s.claims.length} claims.`);}
async function loadDetail(id){s.selected=id;renderClaims();const r=await fetch(`/api/admin/claim?claimId=${encodeURIComponent(id)}`,{headers:{Authorization:headers().Authorization}});const d=await r.json();if(!r.ok||!d.ok)return; s.detail=d; renderDetail();}
async function loadClaims(){status('Loading claims...');const r=await fetch('/api/admin/claims',{headers:{Authorization:headers().Authorization,'x-admin-api-key':headers()['x-admin-api-key']}});const d=await r.json();if(!r.ok||d?.ok===false){status(`${r.status} ${d.status||d.error||'error'}`);return;}s.claims=d.claims||[];renderClaims();status(`Loaded ${s.claims.length} claims.`);}
async function loadDetail(id){s.selected=id;s.detail=null;s.error=null;renderClaims();renderDetail('Loading claim detail...');const endpoint=`/api/admin/claim?claimId=${encodeURIComponent(id)}`;console.debug('[claims-admin] selected claimId:',id);console.debug('[claims-admin] detail endpoint:',endpoint);const r=await fetch(endpoint,{headers:{Authorization:headers().Authorization,'x-admin-api-key':headers()['x-admin-api-key']}});console.debug('[claims-admin] detail HTTP status:',r.status);const d=await r.json().catch(()=>({}));console.debug('[claims-admin] detail response status/error:',d?.status||null,d?.error||null);if(!r.ok||d?.ok===false||!d?.claim){s.error=`Claim detail failed: ${r.status} — ${d?.error||d?.status||'Unknown error'}`;renderDetail();return;}s.detail=d;renderDetail();}
async function action(action,p={}){const r=await fetch('/api/admin/claim-action',{method:'POST',headers:headers(),body:JSON.stringify({claimId:s.selected,action,actor:'admin',...p})});const d=await r.json();if(!r.ok||!d.ok){s.error=`${d.status}: ${d.error}`;renderDetail();return;}s.error=null;await loadClaims();await loadDetail(s.selected);}
async function publish(){const r=await fetch('/api/admin/publish-agent-cards',{method:'POST',headers:headers(),body:JSON.stringify({claimId:s.selected})});const d=await r.json();if(!r.ok||!d.ok){s.error=`${d.status}: ${d.error}`;renderDetail();return;}s.error=null;await loadClaims();await loadDetail(s.selected);}
async function createCheckoutSession(claimId,forceNew=false){if(!claimId){s.error='400 — claimId is required';renderDetail();return;}s.error=null;s.checkoutUrl=null;s.checkoutLoading=true;renderDetail();try{const r=await fetch('/api/admin/create-checkout-session',{method:'POST',headers:headers(),body:JSON.stringify({claimId,forceNew})});const d=await r.json().catch(()=>({}));if(!r.ok||!d.ok){const detail=d?.debug?.message?`\nDetails: ${d.debug.message}`:'';s.error=`Checkout failed:\n${d.status||r.status} — ${d.error||'Request failed'}${detail}`;return;}s.checkoutUrl=d.checkoutUrl||d.url||null;await loadClaims();await loadDetail(claimId);}catch(e){s.error=`500 — ${e?.message||'Request failed'}`;}finally{s.checkoutLoading=false;renderDetail();}}
Expand All @@ -67,7 +68,7 @@ <h1>CommandLayer Claims Admin</h1><p class="muted">Internal operator dashboard f
function copyCheckout(claim){const url=checkoutUrlFromClaim(claim);if(url)navigator.clipboard.writeText(url);}

function pipeline(status){const steps=['created','approved','cards_published','payment_pending','paid','erc8004','ens_provisioned','live'];return `<div class='pipeline'>${steps.map(x=>`<span class='badge ${status===x|| (x==='created')|| (status==='approved'&&x==='created')|| (status==='cards_published'&&(x==='created'||x==='approved')) ? (['payment_pending','paid','erc8004','ens_provisioned'].includes(x)?'created':x):'created'}'>${x.replaceAll('_',' ')}</span>`).join('')}</div><p class='muted'>Future steps are coming next.</p>`}
function renderDetail(){const el=document.getElementById('detailPanel');if(!s.detail){el.innerHTML='<p class="muted">Select a claim to review.</p>';return;}const {claim,agents=[],events=[],transitions=[],cards=[]}=s.detail;const urls=cards.map(c=>c.card_url).filter(Boolean);const missing=claim.status==='cards_published'&&agents.some(a=>!a.card_url);const firstUrl=urls[0];el.innerHTML=`${s.error?`<div class='error-panel'>${s.error}</div>`:''}<h3>Claim <span class='mono'>${short(claim.claim_id,16)}</span> <button id='copyClaim' class='btn btn-muted'>Copy</button> ${badge(claim.status)}</h3><p class='muted'>Tenant: ${claim.tenant||'-'} · Wallet: <span class='mono'>${claim.authenticated_address||'-'}</span> · Pack: ${claim.pack_id||'-'} · Agents: ${agents.length} · Created: ${dt(claim.created_at)}</p><h4>Pipeline</h4>${pipeline(claim.status)}<h4>Next action</h4><div class='row'><input id='reasonInput' placeholder='Reason'><input id='notesInput' placeholder='Notes'></div><div class='row' id='actions'></div><h4>Agents</h4><div class='table-scroll'><table><thead><tr><th>ENS</th><th>Capability</th><th>Parent</th><th>Card</th><th>Actions</th></tr></thead><tbody>${agents.map(a=>`<tr><td class='mono ens-wrap'>${a.ens||'-'}</td><td>${a.capability||'-'}</td><td>${a.canonical_parent||'-'}</td><td>${a.card_status||'-'}</td><td>${a.card_url?`<a href='${a.card_url}' target='_blank'>Open</a> <button class='btn btn-muted copy-url' data-url='${a.card_url}'>Copy</button>`:'-'}</td></tr>`).join('')}</tbody></table></div>${claim.status==='payment_pending'?`<h4>Payment pending</h4><p class='muted'>Checkout has been created. Open it to complete payment.</p><p class='mono'>${claim.stripe_checkout_session_id||'-'}</p>`:''}${claim.status==='paid'?`<h4>Payment received</h4><p class='muted'>Next: ERC-8004 registration</p>`:''}${claim.status==='cards_published'?`<h4>Agent cards published</h4><p class='muted'>Founding Activation: $20 first year for 10 Trust Verification namespaces.</p><p class='muted'>${urls.length} cards${missing?' · Missing card URLs detected':''}</p>${s.checkoutUrl?`<div class='panel'><strong>Checkout created</strong><div class='row'><a class='btn btn-secondary' href='${s.checkoutUrl}' target='_blank' rel='noopener'>Open checkout</a><button id='copyCheckoutUrl' type='button' class='btn btn-secondary' data-url='${s.checkoutUrl}'>Copy checkout URL</button></div><div class='mono' style='overflow-wrap:anywhere;'>${s.checkoutUrl}</div></div>`:''}<div class='row'><button id='copyUrls' type='button' class='btn btn-secondary'>Copy all URLs</button>${firstUrl?`<button id='openFirst' type='button' class='btn btn-secondary'>Open first card</button>`:''}</div><div>${urls.map(u=>`<div class='card-url-row'><span class='mono truncate' title='${u}'>${u.split('/').pop()?.replace(/\.json$/,'')||u}</span><a href='${u}' target='_blank'>Open</a>${copyBtn(u)}</div>`).join('')}</div>${missing?`<p class='error-panel'>Some cards are missing URLs. Use Repair / Publish cards.</p>`:''}`:''}<h4>Events</h4>${events.map(e=>`<div class='panel' style='margin-bottom:8px'><strong>${e.event_type}</strong><div style='overflow-wrap:anywhere;'>${e.message||''}</div><div class='muted'>${dt(e.created_at)}</div><details><summary>metadata</summary><pre style='white-space:pre-wrap;overflow-wrap:anywhere;'>${JSON.stringify(e.event_json||{},null,2)}</pre></details></div>`).join('')||'<p class="muted">No events.</p>'}<h4>Transitions</h4>${transitions.map(t=>`<div class='panel' style='margin-bottom:8px'>${t.from_status||'-'} → ${t.to_status||'-'} · ${t.action||'-'} · ${t.actor||'-'}<div class='muted'>${dt(t.created_at)} ${t.reason?`· ${t.reason}`:''}</div></div>`).join('')||'<p class="muted">No transitions.</p>'}<details><summary>Show raw JSON</summary><pre style='white-space:pre-wrap;overflow-wrap:anywhere;'>${JSON.stringify(s.detail,null,2)}</pre></details>`;
function renderDetail(loadingMsg=''){const el=document.getElementById('detailPanel');if(loadingMsg){el.innerHTML=`<p class="muted">${loadingMsg}</p>`;return;}if(!s.detail){el.innerHTML=`<p class="muted">${s.error||'Select a claim to review.'}</p>`;return;}const payload=s.detail||{};const claim=payload.claim||{};const agents=payload.agents||[];const events=payload.events||[];const transitions=payload.transitions||[];const cards=payload.cards||[];const urls=cards.map(c=>c.card_url).filter(Boolean);const missing=claim.status==='cards_published'&&agents.some(a=>!a.card_url);const firstUrl=urls[0];el.innerHTML=`${s.error?`<div class='error-panel'>${s.error}</div>`:''}<h3>Claim <span class='mono'>${short(claim.claim_id,16)}</span> <button id='copyClaim' class='btn btn-muted'>Copy</button> ${badge(claim.status)}</h3><p class='muted'>Tenant: ${claim.tenant||'-'} · Wallet: <span class='mono'>${claim.authenticated_address||'-'}</span> · Status: ${claim.status||'-'} · Payment: ${claim.payment_status||'-'} · Agents: ${agents.length} · Created: ${dt(claim.created_at)}</p><h4>Pipeline</h4>${pipeline(claim.status)}<h4>Next action</h4><div class='row'><input id='reasonInput' placeholder='Reason'><input id='notesInput' placeholder='Notes'></div><div class='row' id='actions'></div><h4>Agents</h4><div class='table-scroll'><table><thead><tr><th>ENS</th><th>Capability</th><th>Parent</th><th>Card</th><th>Actions</th></tr></thead><tbody>${agents.map(a=>`<tr><td class='mono ens-wrap'>${a.ens||'-'}</td><td>${a.capability||'-'}</td><td>${a.canonical_parent||'-'}</td><td>${a.card_status||'-'}</td><td>${a.card_url?`<a href='${a.card_url}' target='_blank'>Open</a> <button class='btn btn-muted copy-url' data-url='${a.card_url}'>Copy</button>`:'-'}</td></tr>`).join('')}</tbody></table></div>${claim.status==='payment_pending'?`<h4>Payment pending</h4><p class='muted'>Checkout has been created. Open it to complete payment.</p><p class='mono'>${claim.stripe_checkout_session_id||'-'}</p>`:''}${claim.status==='paid'?`<h4>Payment received</h4><p class='muted'>Next: ERC-8004 registration</p>`:''}${claim.status==='cards_published'?`<h4>Agent cards published</h4><p class='muted'>Founding Activation: $20 first year for 10 Trust Verification namespaces.</p><p class='muted'>${urls.length} cards${missing?' · Missing card URLs detected':''}</p>${s.checkoutUrl?`<div class='panel'><strong>Checkout created</strong><div class='row'><a class='btn btn-secondary' href='${s.checkoutUrl}' target='_blank' rel='noopener'>Open checkout</a><button id='copyCheckoutUrl' type='button' class='btn btn-secondary' data-url='${s.checkoutUrl}'>Copy checkout URL</button></div><div class='mono' style='overflow-wrap:anywhere;'>${s.checkoutUrl}</div></div>`:''}<div class='row'><button id='copyUrls' type='button' class='btn btn-secondary'>Copy all URLs</button>${firstUrl?`<button id='openFirst' type='button' class='btn btn-secondary'>Open first card</button>`:''}</div><div>${urls.map(u=>`<div class='card-url-row'><span class='mono truncate' title='${u}'>${u.split('/').pop()?.replace(/\.json$/,'')||u}</span><a href='${u}' target='_blank'>Open</a>${copyBtn(u)}</div>`).join('')}</div>${missing?`<p class='error-panel'>Some cards are missing URLs. Use Repair / Publish cards.</p>`:''}`:''}<h4>Events</h4>${events.map(e=>`<div class='panel' style='margin-bottom:8px'><strong>${e.event_type}</strong><div style='overflow-wrap:anywhere;'>${e.message||''}</div><div class='muted'>${dt(e.created_at)}</div><details><summary>metadata</summary><pre style='white-space:pre-wrap;overflow-wrap:anywhere;'>${JSON.stringify(e.event_json||{},null,2)}</pre></details></div>`).join('')||'<p class="muted">No events.</p>'}<h4>Transitions</h4>${transitions.map(t=>`<div class='panel' style='margin-bottom:8px'>${t.from_status||'-'} → ${t.to_status||'-'} · ${t.action||'-'} · ${t.actor||'-'}<div class='muted'>${dt(t.created_at)} ${t.reason?`· ${t.reason}`:''}</div></div>`).join('')||'<p class="muted">No transitions.</p>'}<details><summary>Show raw JSON</summary><pre style='white-space:pre-wrap;overflow-wrap:anywhere;'>${JSON.stringify(s.detail,null,2)}</pre></details>`;
const a=document.getElementById('actions');const mk=(t,c,cls='btn',opts={})=>{const b=document.createElement('button');b.type='button';b.textContent=t;b.className=cls;b.disabled=Boolean(opts.disabled);if(opts.id)b.id=opts.id;b.onclick=c;a.appendChild(b);};
if(claim.status==='created'){mk('Approve',()=>action('approve',{notes:document.getElementById('notesInput').value}),'btn btn-primary');mk('Reject',()=>action('reject',{reason:document.getElementById('reasonInput').value}),'btn btn-secondary');mk('Mark failed',()=>action('mark_failed',{reason:document.getElementById('reasonInput').value}),'btn btn-danger');}
if(claim.status==='approved'){mk('Publish agent cards',()=>publish(),'btn btn-primary');mk('Mark failed',()=>action('mark_failed',{reason:document.getElementById('reasonInput').value}),'btn btn-danger');mk('Add note',()=>action('add_note',{notes:document.getElementById('notesInput').value,reason:document.getElementById('reasonInput').value}),'btn');}
Expand Down
Loading