Spaces:
Building
Building
| <html><head> | |
| <meta charset="utf-8"> | |
| <title>retro-sync — API Dashboard</title> | |
| <style> | |
| *{margin:0;padding:0;box-sizing:border-box} | |
| body{background:#0a0a0a;color:#e0e0e0;font-family:monospace;padding:1em;max-width:1000px;margin:0 auto} | |
| h1{color:#0ff;font-size:1.4em;margin-bottom:.5em} | |
| h2{color:#ffd700;font-size:1em;margin:1em 0 .3em;border-bottom:1px solid #333;padding-bottom:4px} | |
| .card{background:#111;border:1px solid #333;padding:12px;margin:8px 0;border-radius:6px} | |
| .row{display:flex;gap:8px;align-items:center;flex-wrap:wrap} | |
| button{background:#222;color:#0ff;border:1px solid #0ff;padding:6px 12px;cursor:pointer;font:12px monospace;border-radius:4px} | |
| button:hover{background:#0ff;color:#000} | |
| input,select{background:#111;color:#0f0;border:1px solid #333;padding:6px;font:12px monospace;border-radius:4px} | |
| input{width:300px} | |
| pre{background:#0a0a0a;border:1px solid #222;padding:8px;margin:6px 0;overflow:auto;max-height:300px;font-size:11px;color:#0f0} | |
| .ok{color:#0f0} .err{color:#f44} .warn{color:#ff0} | |
| a{color:#0ff} | |
| .status{font-size:24px;margin-right:8px} | |
| table{width:100%;border-collapse:collapse;margin:8px 0} | |
| td,th{border:1px solid #333;padding:4px 8px;text-align:left;font-size:11px} | |
| th{background:#1a1a1a;color:#ffd700} | |
| </style> | |
| </head><body> | |
| <h1>🎵 retro-sync — API Dashboard</h1> | |
| <div class="card"> | |
| <div class="row"> | |
| <span class="status" id="health-icon">⏳</span> | |
| <div> | |
| <strong>API Server</strong><br> | |
| <span id="health-text">checking...</span> | |
| </div> | |
| <input id="api-url" value="https://localhost:8443" style="margin-left:auto"> | |
| <button onclick="checkHealth()">🔄 Check</button> | |
| </div> | |
| </div> | |
| <h2>📋 Catalog</h2> | |
| <div class="card"> | |
| <div class="row"> | |
| <button onclick="loadCatalog()">Load Catalog</button> | |
| <button onclick="loadArtists()">Load Artists</button> | |
| <a href="/retro-sync/catalog/" target="_blank">📂 Browse files</a> | |
| <a href="/retro-sync/catalog/retro-sync.cwr" download>⬇ CWR 2.2</a> | |
| </div> | |
| <pre id="catalog-out">click Load to view</pre> | |
| </div> | |
| <h2>🔍 API Explorer</h2> | |
| <div class="card"> | |
| <div class="row"> | |
| <select id="method"><option>GET</option><option>POST</option></select> | |
| <input id="endpoint" value="/api/societies" placeholder="/api/endpoint"> | |
| <button onclick="callApi()">▶ Send</button> | |
| </div> | |
| <div class="row" style="margin-top:6px"> | |
| <small>Quick:</small> | |
| <button onclick="quick('/health')">health</button> | |
| <button onclick="quick('/api/societies')">societies</button> | |
| <button onclick="quick('/api/gateway/status')">gateway</button> | |
| <button onclick="quick('/api/vault/summary')">vault</button> | |
| <button onclick="quick('/metrics')">metrics</button> | |
| </div> | |
| <pre id="api-out">response will appear here</pre> | |
| </div> | |
| <h2>📊 Collections</h2> | |
| <div class="card" id="collections"> | |
| <button onclick="loadCollections()">Load Collections</button> | |
| </div> | |
| <h2>🎵 Register Work</h2> | |
| <div class="card"> | |
| <div class="row"> | |
| <input id="work-title" placeholder="Title"> | |
| <input id="work-writer" placeholder="Writer name"> | |
| <button onclick="registerWork()">Register</button> | |
| </div> | |
| <pre id="register-out"></pre> | |
| </div> | |
| <h2>📡 Endpoints Reference</h2> | |
| <table> | |
| <tr><th>Method</th><th>Path</th><th>Description</th></tr> | |
| <tr><td>GET</td><td>/health</td><td>Health check</td></tr> | |
| <tr><td>GET</td><td>/metrics</td><td>Prometheus metrics</td></tr> | |
| <tr><td>POST</td><td>/api/upload</td><td>Upload track (multipart)</td></tr> | |
| <tr><td>POST</td><td>/api/register</td><td>Register work</td></tr> | |
| <tr><td>GET</td><td>/api/track/:id</td><td>Track status</td></tr> | |
| <tr><td>GET</td><td>/api/societies</td><td>List collection societies</td></tr> | |
| <tr><td>GET</td><td>/api/societies/:id</td><td>Society details</td></tr> | |
| <tr><td>POST</td><td>/api/societies/route</td><td>Route royalty</td></tr> | |
| <tr><td>POST</td><td>/api/gateway/ern/push</td><td>DDEX ERN push</td></tr> | |
| <tr><td>POST</td><td>/api/gateway/dsr/cycle</td><td>DSR royalty cycle</td></tr> | |
| <tr><td>GET</td><td>/api/manifest/:id</td><td>NFT manifest</td></tr> | |
| <tr><td>POST</td><td>/api/manifest/mint</td><td>Mint NFT</td></tr> | |
| <tr><td>POST</td><td>/api/manifest/proof</td><td>Ownership proof</td></tr> | |
| <tr><td>GET</td><td>/api/vault/summary</td><td>Multi-sig vault</td></tr> | |
| <tr><td>POST</td><td>/api/takedown</td><td>DMCA takedown</td></tr> | |
| <tr><td>GET</td><td>/api/privacy/export/:uid</td><td>GDPR export</td></tr> | |
| </table> | |
| <script> | |
| const $ = id => document.getElementById(id); | |
| const base = () => $('api-url').value.replace(/\/+$/, ''); | |
| async function checkHealth() { | |
| try { | |
| const r = await fetch(base() + '/health'); | |
| const d = await r.json(); | |
| $('health-icon').textContent = '✅'; | |
| $('health-text').innerHTML = `<span class="ok">${d.status} — ${d.service}</span>`; | |
| } catch(e) { | |
| $('health-icon').textContent = '❌'; | |
| $('health-text').innerHTML = `<span class="err">${e.message}</span>`; | |
| } | |
| } | |
| async function callApi() { | |
| const method = $('method').value; | |
| const endpoint = $('endpoint').value; | |
| try { | |
| const r = await fetch(base() + endpoint, {method}); | |
| const text = await r.text(); | |
| try { $('api-out').textContent = JSON.stringify(JSON.parse(text), null, 2); } | |
| catch { $('api-out').textContent = text; } | |
| } catch(e) { $('api-out').textContent = 'Error: ' + e.message; } | |
| } | |
| function quick(path) { $('endpoint').value = path; $('method').value = 'GET'; callApi(); } | |
| async function loadCatalog() { | |
| try { | |
| const r = await fetch('/retro-sync/catalog/works.json'); | |
| const d = await r.json(); | |
| let out = `${d.total_works} works:\n\n`; | |
| for (const w of d.works.slice(0, 20)) { | |
| const rs = w.writers[0]?.rs_id || '?'; | |
| out += ` ${rs} ${w.title.slice(0,40).padEnd(40)} ${w.writers[0]?.name || '?'}\n`; | |
| } | |
| if (d.total_works > 20) out += ` ... (${d.total_works - 20} more)\n`; | |
| $('catalog-out').textContent = out; | |
| } catch(e) { $('catalog-out').textContent = 'Error: ' + e.message; } | |
| } | |
| async function loadArtists() { | |
| try { | |
| const r = await fetch('/retro-sync/catalog/artists.json'); | |
| const d = await r.json(); | |
| let out = `${d.count} artists:\n\n`; | |
| for (const a of d.artists) { | |
| out += ` ${a.id.padEnd(26)} ${a.name.padEnd(30)} ISNI:${a.isni||'?'} QID:${a.qid}\n`; | |
| } | |
| $('catalog-out').textContent = out; | |
| } catch(e) { $('catalog-out').textContent = 'Error: ' + e.message; } | |
| } | |
| async function loadCollections() { | |
| const collections = [ | |
| {name:'Bach Inventions', slug:'bach-invention', n:15}, | |
| {name:'Bartók', slug:'bartok', n:20}, | |
| ]; | |
| let html = '<table><tr><th>Collection</th><th>Works</th><th>Tiles</th><th>Links</th></tr>'; | |
| for (const c of collections) { | |
| html += `<tr><td>${c.name}</td><td>${c.n}</td><td>71</td>`; | |
| html += `<td><a href="/retro-sync/${c.slug}/">View</a> · `; | |
| html += `<a href="/retro-sync/${c.slug}/tiles/01.png">Tile 1</a></td></tr>`; | |
| } | |
| html += '</table>'; | |
| $('collections').innerHTML = html; | |
| } | |
| checkHealth(); | |
| </script> | |
| </body></html> | |