Spaces:
Running
Running
| <script lang="ts"> | |
| import type { ShardData } from "../types.js"; | |
| import { formatBytes, formatHash, formatTimestamp } from "../parsers.js"; | |
| export let data: ShardData; | |
| export let filename: string; | |
| export let fileSize: number; | |
| $: header = data.header; | |
| $: footer = data.footer; | |
| $: totalFiles = data.file_info.length; | |
| $: totalXorbs = data.cas_info.length; | |
| $: totalChunks = data.cas_info.reduce( | |
| (sum, cas) => sum + cas.header.num_entries, | |
| 0 | |
| ); | |
| // Check verification and metadata status across all files | |
| $: { | |
| const filesWithVerification = data.file_info.filter( | |
| (fileInfo) => fileInfo.header.file_flags & 0x80000000 | |
| ).length; | |
| const filesWithMetadata = data.file_info.filter( | |
| (fileInfo) => fileInfo.header.file_flags & 0x40000000 | |
| ).length; | |
| verificationStatus = | |
| filesWithVerification === totalFiles | |
| ? "β " | |
| : filesWithVerification === 0 | |
| ? "β" | |
| : "β"; | |
| metadataStatus = | |
| filesWithMetadata === totalFiles | |
| ? "β " | |
| : filesWithMetadata === 0 | |
| ? "β" | |
| : "β"; | |
| } | |
| let verificationStatus = ""; | |
| let metadataStatus = ""; | |
| $: totalStoredBytes = data.cas_info.reduce( | |
| (sum, cas) => sum + cas.header.num_bytes_in_cas, | |
| 0 | |
| ); | |
| // Track which files are expanded | |
| let expandedFiles = new Set<number>(); | |
| function toggleFileExpansion(fileIndex: number) { | |
| if (expandedFiles.has(fileIndex)) { | |
| expandedFiles.delete(fileIndex); | |
| } else { | |
| expandedFiles.add(fileIndex); | |
| } | |
| expandedFiles = expandedFiles; // Trigger reactivity | |
| } | |
| </script> | |
| <div class="shard-viewer"> | |
| <div class="header"> | |
| <h2>ποΈ Shard File Analysis</h2> | |
| <div class="file-info"> | |
| <span class="filename">{filename}</span> | |
| <span class="file-size">{formatBytes(fileSize)}</span> | |
| </div> | |
| </div> | |
| <div class="metadata-grid"> | |
| <div class="metadata-section"> | |
| <h3>Header Information</h3> | |
| <div class="metadata-item"> | |
| <span class="label">Version:</span> | |
| <span class="value">{header.version}</span> | |
| </div> | |
| <div class="metadata-item"> | |
| <span class="label">Footer Size:</span> | |
| <span class="value">{formatBytes(header.footer_size)}</span> | |
| </div> | |
| </div> | |
| <div class="metadata-section"> | |
| <h3>Content Statistics</h3> | |
| <div class="metadata-item"> | |
| <span class="label">Files:</span> | |
| <span class="value">{totalFiles.toLocaleString()}</span> | |
| </div> | |
| <div class="metadata-item"> | |
| <span class="label">XORBs:</span> | |
| <span class="value">{totalXorbs.toLocaleString()}</span> | |
| </div> | |
| <div class="metadata-item"> | |
| <span class="label">Total Chunks:</span> | |
| <span class="value">{totalChunks.toLocaleString()}</span> | |
| </div> | |
| <div class="metadata-item"> | |
| <span class="label">Stored Bytes:</span> | |
| <span class="value">{formatBytes(totalStoredBytes)}</span> | |
| </div> | |
| </div> | |
| <div class="metadata-section"> | |
| <h3>Timestamps & Security</h3> | |
| <div class="metadata-item"> | |
| <span class="label">Created:</span> | |
| <span class="value" | |
| >{formatTimestamp(footer.shard_creation_timestamp)}</span | |
| > | |
| </div> | |
| <div class="metadata-item"> | |
| <span class="label">Key Expiry:</span> | |
| <span class="value">{formatTimestamp(footer.shard_key_expiry)}</span> | |
| </div> | |
| <div class="metadata-item"> | |
| <span class="label">HMAC Key:</span> | |
| <span class="value hash" title={formatHash(footer.chunk_hash_hmac_key)}> | |
| {formatHash(footer.chunk_hash_hmac_key)} | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| {#if data.file_info.length > 0} | |
| <div class="file-details"> | |
| <h3> | |
| File Information ({data.file_info.length} files) - Verification: {verificationStatus} | |
| Metadata: {metadataStatus} | |
| </h3> | |
| <div class="table-container"> | |
| <table class="data-table"> | |
| <thead> | |
| <tr> | |
| <th>File Hash</th> | |
| <th>Entries</th> | |
| <th>Total Length</th> | |
| <th>SHA256</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {#each data.file_info as fileInfo, fileIndex} | |
| <tr | |
| class="file-row" | |
| class:expanded={expandedFiles.has(fileIndex)} | |
| onclick={() => toggleFileExpansion(fileIndex)} | |
| role="button" | |
| tabindex="0" | |
| onkeydown={(e) => | |
| e.key === "Enter" && toggleFileExpansion(fileIndex)} | |
| > | |
| <td class="hash" title={formatHash(fileInfo.header.file_hash)}> | |
| <span class="expand-icon"> | |
| {expandedFiles.has(fileIndex) ? "βΌ" : "βΆ"} | |
| </span> | |
| {formatHash(fileInfo.header.file_hash)} | |
| </td> | |
| <td>{fileInfo.header.num_entries}</td> | |
| <td> | |
| {formatBytes( | |
| fileInfo.entries.reduce( | |
| (sum, entry) => sum + entry.unpacked_segment_bytes, | |
| 0 | |
| ) | |
| )} | |
| </td> | |
| <td> | |
| {#if fileInfo.metadata_ext} | |
| <span | |
| class="hash" | |
| title={formatHash(fileInfo.metadata_ext.sha256)} | |
| > | |
| {formatHash(fileInfo.metadata_ext.sha256)} | |
| </span> | |
| {:else} | |
| <span class="no-data">β</span> | |
| {/if} | |
| </td> | |
| </tr> | |
| {#if expandedFiles.has(fileIndex)} | |
| <tr class="file-details-row"> | |
| <td colspan="4"> | |
| <div class="file-entries-container"> | |
| <div class="entries-table-container"> | |
| <h4>Data Entries for File</h4> | |
| <table class="entries-table"> | |
| <thead> | |
| <tr> | |
| <th>Entry #</th> | |
| <th>CAS Hash</th> | |
| <th>Chunk Range</th> | |
| <th>Unpacked Size</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {#each fileInfo.entries as entry, entryIndex} | |
| <tr> | |
| <td>{entryIndex + 1}</td> | |
| <td | |
| class="hash" | |
| title={formatHash(entry.cas_hash)} | |
| > | |
| {formatHash(entry.cas_hash)} | |
| </td> | |
| <td | |
| >{entry.chunk_index_start} - {entry.chunk_index_end}</td | |
| > | |
| <td | |
| >{formatBytes( | |
| entry.unpacked_segment_bytes | |
| )}</td | |
| > | |
| </tr> | |
| {/each} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </td> | |
| </tr> | |
| {/if} | |
| {/each} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| {/if} | |
| {#if data.cas_info.length > 0} | |
| <div class="cas-details"> | |
| <h3>CAS Information ({data.cas_info.length} XORBs)</h3> | |
| <div class="table-container"> | |
| <table class="data-table"> | |
| <thead> | |
| <tr> | |
| <th>CAS Hash</th> | |
| <th>Chunks</th> | |
| <th>Bytes in CAS</th> | |
| <th>Bytes on Disk</th> | |
| <th>Compression</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {#each data.cas_info as casInfo} | |
| <tr> | |
| <td class="hash" title={formatHash(casInfo.header.cas_hash)}> | |
| {formatHash(casInfo.header.cas_hash)} | |
| </td> | |
| <td>{casInfo.header.num_entries}</td> | |
| <td>{formatBytes(casInfo.header.num_bytes_in_cas)}</td> | |
| <td>{formatBytes(casInfo.header.num_bytes_on_disk)}</td> | |
| <td> | |
| {casInfo.header.num_bytes_in_cas > 0 | |
| ? ( | |
| (casInfo.header.num_bytes_on_disk / | |
| casInfo.header.num_bytes_in_cas) * | |
| 100 | |
| ).toFixed(1) + "%" | |
| : "N/A"} | |
| </td> | |
| </tr> | |
| {/each} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| {/if} | |
| </div> | |
| <style> | |
| .shard-viewer { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| .header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 30px; | |
| padding-bottom: 20px; | |
| border-bottom: 2px solid #eee; | |
| } | |
| .header h2 { | |
| margin: 0; | |
| color: #333; | |
| font-size: 24px; | |
| } | |
| .file-info { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-end; | |
| gap: 4px; | |
| } | |
| .filename { | |
| font-weight: 600; | |
| color: #555; | |
| } | |
| .file-size { | |
| font-size: 14px; | |
| color: #888; | |
| } | |
| .metadata-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 30px; | |
| } | |
| .metadata-section { | |
| background: #f8f9fa; | |
| padding: 20px; | |
| border-radius: 8px; | |
| border: 1px solid #e9ecef; | |
| } | |
| .metadata-section h3 { | |
| margin: 0 0 15px 0; | |
| color: #495057; | |
| font-size: 18px; | |
| font-weight: 600; | |
| } | |
| .metadata-item { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 12px; | |
| padding: 8px 0; | |
| border-bottom: 1px solid #e9ecef; | |
| } | |
| .metadata-item:last-child { | |
| border-bottom: none; | |
| margin-bottom: 0; | |
| } | |
| .metadata-item .label { | |
| font-weight: 500; | |
| color: #6c757d; | |
| margin-right: 10px; | |
| } | |
| .metadata-item .value { | |
| font-family: "Monaco", "Consolas", monospace; | |
| font-size: 14px; | |
| color: #212529; | |
| text-align: right; | |
| } | |
| .hash { | |
| font-family: "Monaco", "Consolas", monospace; | |
| background: #e9ecef; | |
| padding: 2px 6px; | |
| border-radius: 4px; | |
| cursor: help; | |
| font-size: 12px; | |
| } | |
| .file-details, | |
| .cas-details { | |
| margin-top: 30px; | |
| } | |
| .file-details h3, | |
| .cas-details h3 { | |
| margin: 0 0 20px 0; | |
| color: #495057; | |
| font-size: 18px; | |
| font-weight: 600; | |
| } | |
| .table-container { | |
| overflow-x: auto; | |
| border-radius: 8px; | |
| border: 1px solid #e9ecef; | |
| } | |
| .data-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| background: white; | |
| } | |
| .data-table th, | |
| .data-table td { | |
| padding: 12px; | |
| text-align: left; | |
| border-bottom: 1px solid #e9ecef; | |
| } | |
| .data-table th { | |
| background: #f8f9fa; | |
| font-weight: 600; | |
| color: #495057; | |
| } | |
| .data-table tr:hover { | |
| background: #f8f9fa; | |
| } | |
| .no-data { | |
| color: #999; | |
| font-style: italic; | |
| } | |
| /* Expandable file rows */ | |
| .file-row { | |
| cursor: pointer; | |
| transition: background-color 0.2s ease; | |
| } | |
| .file-row:hover { | |
| background-color: #f0f7ff ; | |
| } | |
| .file-row.expanded { | |
| background-color: #e3f2fd; | |
| } | |
| .expand-icon { | |
| display: inline-block; | |
| margin-right: 8px; | |
| font-size: 12px; | |
| width: 12px; | |
| transition: transform 0.2s ease; | |
| } | |
| .file-details-row { | |
| background-color: #f8f9fa; | |
| } | |
| .file-details-row td { | |
| padding: 0; | |
| } | |
| .file-entries-container { | |
| padding: 16px; | |
| border-left: 3px solid #007bff; | |
| margin-left: 12px; | |
| max-height: 90vh; | |
| overflow-y: auto; | |
| overflow-x: hidden; | |
| border-radius: 6px; | |
| background-color: #f8f9fa; | |
| } | |
| /* Scrollbar styling for webkit browsers */ | |
| .file-entries-container::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .file-entries-container::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| border-radius: 3px; | |
| } | |
| .file-entries-container::-webkit-scrollbar-thumb { | |
| background: #c1c1c1; | |
| border-radius: 3px; | |
| } | |
| .file-entries-container::-webkit-scrollbar-thumb:hover { | |
| background: #a8a8a8; | |
| } | |
| .file-entries-container h4 { | |
| margin: 0 0 12px 0; | |
| color: #495057; | |
| font-size: 14px; | |
| font-weight: 600; | |
| position: sticky; | |
| top: 0; | |
| background-color: #f8f9fa; | |
| padding: 8px 0; | |
| z-index: 1; | |
| } | |
| .entries-table-container { | |
| overflow-x: auto; | |
| border-radius: 6px; | |
| border: 1px solid #dee2e6; | |
| } | |
| .entries-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| background: white; | |
| font-size: 13px; | |
| } | |
| .entries-table th, | |
| .entries-table td { | |
| padding: 8px 12px; | |
| text-align: left; | |
| border-bottom: 1px solid #dee2e6; | |
| } | |
| .entries-table th { | |
| background: #f1f3f4; | |
| font-weight: 600; | |
| color: #495057; | |
| font-size: 12px; | |
| } | |
| .entries-table tr:hover { | |
| background: #f8f9fa; | |
| } | |
| .entries-table .hash { | |
| font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace; | |
| font-size: 11px; | |
| color: #6c757d; | |
| } | |
| @media (max-width: 768px) { | |
| .header { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| gap: 10px; | |
| } | |
| .metadata-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .metadata-item { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| gap: 4px; | |
| } | |
| .metadata-item .value { | |
| text-align: left; | |
| } | |
| } | |
| </style> | |