5050 .modal { background : white; border : 2px solid # 333 ; max-width : 90vw ; max-height : 85vh ; overflow : hidden; display : flex; flex-direction : column; }
5151 .modal-header { padding : 15px 20px ; border-bottom : 1px solid # 333 ; display : flex; justify-content : space-between; align-items : center; }
5252 .modal-body { padding : 20px ; overflow-y : auto; flex : 1 ; }
53- .cid-row { display : flex; align-items : center; gap : 12px ; padding : 8px 0 ; border-bottom : 1px solid # eee ; font-size : 0.85em ; flex-wrap : wrap; }
54- .cid-row .cid-value { font-family : monospace; word-break : break-all; flex : 1 ; min-width : 200px ; }
55- .cid-row .cid-meta { color : # 666 ; font-size : 0.9em ; }
53+ .cid-row { display : flex; align-items : center; gap : 12px ; padding : 8px 0 ; border-bottom : 1px solid # eee ; font-size : 0.85em ; flex-wrap : nowrap; }
54+ .cid-row .cid-value { font-family : monospace; white-space : nowrap; flex : 1 ; min-width : 200px ; cursor : pointer; }
55+ .cid-row .cid-value : hover { color : # 06A77D ; }
56+ .cid-row .cid-meta { color : # 666 ; font-size : 0.9em ; white-space : nowrap; }
57+ .copy-toast { position : fixed; bottom : 30px ; left : 50% ; transform : translateX (-50% ); background : # 333 ; color : # fff ; padding : 8px 20px ; font-size : 0.85em ; z-index : 2000 ; opacity : 0 ; transition : opacity 0.3s ; pointer-events : none; }
5658 .cid-row .cid-actions { display : flex; gap : 6px ; flex-shrink : 0 ; }
5759 .cid-payload { margin-top : 4px ; font-size : 0.8em ; color : # 06A77D ; }
5860 .load-spinner { display : inline-block; width : 14px ; height : 14px ; border : 2px solid # ccc ; border-top-color : # 06A77D ; border-radius : 50% ; animation : spin 0.8s linear infinite; vertical-align : middle; }
@@ -147,9 +149,10 @@ <h3 style="margin:0; text-transform:uppercase; font-size:1em;">Network Nodes</h3
147149 < table id ="nodeTable "> < thead > < tr > < th style ="width: 80px; "> Action</ th > < th > Peer ID</ th > < th > Shard</ th > < th > Peers</ th > < th > Pinned</ th > < th > Known</ th > < th > Uptime</ th > < th > Last Seen</ th > </ tr > </ thead > < tbody id ="nodeTableBody "> </ tbody > </ table >
148150 </ div >
149151 </ div >
152+ < div id ="copy-toast " class ="copy-toast "> Copied to clipboard</ div >
150153 < div id ="cids-modal " class ="modal-overlay " style ="display: none; ">
151- < div class ="modal " style ="width: 700px ; ">
152- < div class ="modal-header "> < h3 style ="margin:0; text-transform: uppercase; font-size: 1em; "> Unique CIDs</ h3 > < input type ="text " id ="cids-modal-search " placeholder ="Filter CIDs... " style ="padding: 6px; font-family: inherit; font-size: 0.85em; width: 180px; border: 1px solid #333; "> < button class ="btn-text " id ="cids-modal-close "> CLOSE</ button > </ div >
154+ < div class ="modal " style ="width: 1100px ; ">
155+ < div class ="modal-header "> < h3 style ="margin:0; text-transform: uppercase; font-size: 1em; "> Unique CIDs</ h3 > < div style =" display:flex; gap:8px; align-items:center; " > < input type ="text " id ="cids-modal-search " placeholder ="Filter CIDs... " style ="padding: 6px; font-family: inherit; font-size: 0.85em; width: 180px; border: 1px solid #333; "> < button class ="btn-text " id ="cids-export-btn " title =" Export payload CIDs as text file " > EXPORT PAYLOAD CIDS </ button > < button class =" btn-text " id =" cids- modal-close "> CLOSE</ button > </ div > </ div >
153156 < div class ="modal-body "> < div id ="cids-modal-list "> </ div > </ div >
154157 </ div >
155158 </ div >
@@ -273,18 +276,29 @@ <h3 style="margin:0; text-transform:uppercase; font-size:1em;">Network Nodes</h3
273276 document . getElementById ( 'root-topic-cancel-btn' ) . onclick = hideTopicEdit ;
274277 document . getElementById ( 'root-topic-input' ) . onkeydown = function ( e ) { if ( e . key === 'Enter' ) saveTopicPrefix ( ) ; else if ( e . key === 'Escape' ) hideTopicEdit ( ) ; } ;
275278 const GATEWAY = 'https://ipfs.io' ;
279+ function showCopyToast ( ) {
280+ const toast = document . getElementById ( 'copy-toast' ) ;
281+ toast . style . opacity = '1' ;
282+ setTimeout ( function ( ) { toast . style . opacity = '0' ; } , 1200 ) ;
283+ }
284+ function copyCID ( text ) {
285+ navigator . clipboard . writeText ( text ) . then ( showCopyToast ) . catch ( function ( ) { } ) ;
286+ }
276287 function showCidsModal ( ) {
277288 document . getElementById ( 'cids-modal' ) . style . display = 'flex' ;
278289 fetch ( '/api/unique-cids?t=' + Date . now ( ) ) . then ( r => r . json ( ) ) . then ( data => {
279290 const list = document . getElementById ( 'cids-modal-list' ) ;
280291 const cids = data . cids || [ ] ;
281292 list . innerHTML = cids . length === 0 ? '<p style="color:#666;">No CIDs yet. CIDs appear when nodes announce pinned files.</p>' : cids . map ( ( entry , i ) => {
282293 const cid = entry . cid || entry . CID || entry ; const shard = ( entry . shard || entry . Shard || '' ) === '' ? 'root' : ( entry . shard || entry . Shard ) ; const replicas = entry . replicas ?? entry . Replicas ?? 0 ;
283- return '<div class="cid-row" data-cid="' + escapeHtml ( cid ) + '"><div class="cid-value">' + escapeHtml ( cid ) + '</div><div class="cid-meta">Shard: ' + escapeHtml ( shard ) + ' | Replicas: ' + replicas + '</div><div class="cid-actions"><button class="btn-text" onclick="loadManifest(\'' + escapeJs ( cid ) + '\', this.parentElement)">LOAD MANIFEST</button></div></div>' ;
294+ return '<div class="cid-row" data-cid="' + escapeHtml ( cid ) + '"><div class="cid-value" onclick="copyCID(\'' + escapeJs ( cid ) + '\')" title="Click to copy" >' + escapeHtml ( cid ) + '</div><div class="cid-meta">Shard: ' + escapeHtml ( shard ) + ' | Replicas: ' + replicas + '</div><div class="cid-actions"><button class="btn-text" onclick="loadManifest(\'' + escapeJs ( cid ) + '\', this.parentElement)">LOAD MANIFEST</button></div></div>' ;
284295 } ) . join ( '' ) ;
285296 } ) . catch ( ( ) => { document . getElementById ( 'cids-modal-list' ) . innerHTML = '<p style="color:#999;">Failed to load CIDs.</p>' ; } ) ;
286297 }
287298 function hideCidsModal ( ) { document . getElementById ( 'cids-modal' ) . style . display = 'none' ; }
299+ function exportPayloadCIDs ( ) {
300+ window . open ( '/api/payload-cids-export' , '_blank' ) ;
301+ }
288302 function showNodeFilesModal ( peerId ) {
289303 const aliases = loadAliases ( ) ;
290304 const label = aliases [ peerId ] || ( window . _nodeNames && window . _nodeNames [ peerId ] ) || peerId . slice ( - 6 ) ;
@@ -296,7 +310,7 @@ <h3 style="margin:0; text-transform:uppercase; font-size:1em;">Network Nodes</h3
296310 const cids = data . cids || [ ] ;
297311 list . innerHTML = cids . length === 0 ? '<p style="color:#666;">No pinned files for this node.</p>' : cids . map ( ( entry , i ) => {
298312 const cid = entry . cid || entry . CID || entry ; const shard = ( entry . shard || entry . Shard || '' ) === '' ? 'root' : ( entry . shard || entry . Shard ) ; const replicas = entry . replicas ?? entry . Replicas ?? 0 ;
299- return '<div class="cid-row" data-cid="' + escapeHtml ( cid ) + '"><div class="cid-value">' + escapeHtml ( cid ) + '</div><div class="cid-meta">Shard: ' + escapeHtml ( shard ) + ' | Replicas: ' + replicas + '</div><div class="cid-actions"><button class="btn-text" onclick="loadManifest(\'' + escapeJs ( cid ) + '\', this.parentElement)">LOAD MANIFEST</button></div></div>' ;
313+ return '<div class="cid-row" data-cid="' + escapeHtml ( cid ) + '"><div class="cid-value" onclick="copyCID(\'' + escapeJs ( cid ) + '\')" title="Click to copy" >' + escapeHtml ( cid ) + '</div><div class="cid-meta">Shard: ' + escapeHtml ( shard ) + ' | Replicas: ' + replicas + '</div><div class="cid-actions"><button class="btn-text" onclick="loadManifest(\'' + escapeJs ( cid ) + '\', this.parentElement)">LOAD MANIFEST</button></div></div>' ;
300314 } ) . join ( '' ) ;
301315 } ) . catch ( ( ) => { document . getElementById ( 'node-files-modal-list' ) . innerHTML = '<p style="color:#999;">Failed to load node files.</p>' ; } ) ;
302316 }
@@ -311,7 +325,7 @@ <h3 style="margin:0; text-transform:uppercase; font-size:1em;">Network Nodes</h3
311325 const cids = data . cids || [ ] ;
312326 list . innerHTML = cids . length === 0 ? '<p style="color:#666;">No files at this replication level.</p>' : cids . map ( ( entry , i ) => {
313327 const cid = entry . cid || entry . CID || entry ; const shard = ( entry . shard || entry . Shard || '' ) === '' ? 'root' : ( entry . shard || entry . Shard ) ; const replicas = entry . replicas ?? entry . Replicas ?? 0 ;
314- return '<div class="cid-row" data-cid="' + escapeHtml ( cid ) + '"><div class="cid-value">' + escapeHtml ( cid ) + '</div><div class="cid-meta">Shard: ' + escapeHtml ( shard ) + ' | Replicas: ' + replicas + '</div><div class="cid-actions"><button class="btn-text" onclick="loadManifest(\'' + escapeJs ( cid ) + '\', this.parentElement)">LOAD MANIFEST</button></div></div>' ;
328+ return '<div class="cid-row" data-cid="' + escapeHtml ( cid ) + '"><div class="cid-value" onclick="copyCID(\'' + escapeJs ( cid ) + '\')" title="Click to copy" >' + escapeHtml ( cid ) + '</div><div class="cid-meta">Shard: ' + escapeHtml ( shard ) + ' | Replicas: ' + replicas + '</div><div class="cid-actions"><button class="btn-text" onclick="loadManifest(\'' + escapeJs ( cid ) + '\', this.parentElement)">LOAD MANIFEST</button></div></div>' ;
315329 } ) . join ( '' ) ;
316330 } ) . catch ( ( ) => { document . getElementById ( 'replication-modal-list' ) . innerHTML = '<p style="color:#999;">Failed to load CIDs.</p>' ; } ) ;
317331 }
@@ -398,6 +412,7 @@ <h3 style="margin:0; text-transform:uppercase; font-size:1em;">Network Nodes</h3
398412 alert ( 'Failed to load manifest' ) ;
399413 } ) ;
400414 }
415+ document . getElementById ( 'cids-export-btn' ) . onclick = exportPayloadCIDs ;
401416 document . getElementById ( 'unique-cids-card' ) . onclick = showCidsModal ;
402417 document . getElementById ( 'total-nodes-card' ) . onclick = function ( ) { document . getElementById ( 'network-nodes-section' ) . scrollIntoView ( { behavior : 'smooth' } ) ; } ;
403418 document . getElementById ( 'total-shards-card' ) . onclick = function ( ) { document . getElementById ( 'shard-topology-section' ) . scrollIntoView ( { behavior : 'smooth' } ) ; } ;
0 commit comments