Skip to content

Commit 87cda3c

Browse files
authored
Merge pull request #363 from commandlayer/codex/fix-admin-claims-dashboard-detail-panel
Fix admin claims row click to load detail panel
2 parents 647445f + aacfcd1 commit 87cda3c

1 file changed

Lines changed: 6 additions & 5 deletions

File tree

public/admin/claims.html

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
.error-panel{border:1px solid #fecaca;background:#fef2f2;color:#991b1b;padding:10px;border-radius:10px;margin-bottom:12px;}
3232
.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;}
3333
.claims-list { display:flex; flex-direction:column; gap:8px; }
34-
.claim-item { border:1px solid #e5e7eb; border-radius:12px; padding:10px 12px; background:#fff; cursor:pointer; }
34+
.claim-item { border:1px solid #e5e7eb; border-radius:12px; padding:10px 12px; background:#fff; cursor:pointer; transition:background .15s ease,border-color .15s ease; }
35+
.claim-item:hover { border-color:#93c5fd; background:#f8fbff; }
3536
.claim-item.selected { border-color:#2563eb; background:#eef2ff; }
3637
.claim-head, .claim-meta { display:flex; justify-content:space-between; gap:12px; align-items:center; }
3738
.claim-meta { color:#6b7280; font-size:13px; }
@@ -52,13 +53,13 @@ <h1>CommandLayer Claims Admin</h1><p class="muted">Internal operator dashboard f
5253
(() => {
5354
const s={claims:[],selected:'',detail:null,error:null,checkoutLoading:false,checkoutUrl:null};
5455
const apiKey=document.getElementById('apiKey'); apiKey.value=sessionStorage.getItem('cl_admin_api_key')||localStorage.getItem('cl_admin_api_key')||'';
55-
const headers=()=>({Authorization:`Bearer ${apiKey.value.trim()}`,'Content-Type':'application/json'});
56+
const headers=()=>{const key=apiKey.value.trim();return {Authorization:`Bearer ${key}`,'x-admin-api-key':key,'Content-Type':'application/json'};};
5657
const short=(v,n=10)=>v?`${v.slice(0,n)}…`:''; const dt=(v)=>v?new Date(v).toLocaleString():'-';
5758
const badge=(status)=>`<span class="badge ${status}">${status}</span>`;
5859
const copyBtn=(txt)=>`<button class='btn btn-muted copy-url' data-url='${txt.replaceAll("'", '&#39;')}'>Copy URL</button>`;
5960
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);}}
60-
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.`);}
61-
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();}
61+
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.`);}
62+
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();}
6263
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);}
6364
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);}
6465
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();}}
@@ -67,7 +68,7 @@ <h1>CommandLayer Claims Admin</h1><p class="muted">Internal operator dashboard f
6768
function copyCheckout(claim){const url=checkoutUrlFromClaim(claim);if(url)navigator.clipboard.writeText(url);}
6869

6970
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>`}
70-
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>`;
71+
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>`;
7172
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);};
7273
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');}
7374
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');}

0 commit comments

Comments
 (0)