diff --git a/waterfall.html b/waterfall.html index 853cfb7..61ae033 100644 --- a/waterfall.html +++ b/waterfall.html @@ -247,6 +247,57 @@ color: white; border-color: var(--color-accent); } + + .request-details-table { + margin-top: 1rem; + overflow-x: auto; + } + + .details-table { + width: 100%; + border-collapse: collapse; + background: var(--color-bg-secondary); + border-radius: 8px; + overflow: hidden; + } + + .details-table th, + .details-table td { + padding: 0.75rem; + text-align: left; + border-bottom: 1px solid var(--color-border); + font-size: 0.9rem; + } + + .details-table th { + background: var(--color-bg-tertiary); + font-weight: 600; + color: var(--color-text-secondary); + cursor: pointer; + user-select: none; + } + + .details-table th:hover { + background: rgba(114, 9, 183, 0.1); + } + + .details-table tbody tr:hover { + background: rgba(114, 9, 183, 0.05); + } + + .details-table .sort-icon { + font-size: 0.7rem; + margin-left: 0.3rem; + opacity: 0.5; + } + + .details-table th.sorted { + color: var(--color-accent); + } + + .details-table th.sorted .sort-icon { + opacity: 1; + } @@ -278,6 +329,9 @@
+ +

Request Details

+
diff --git a/waterfall.js b/waterfall.js index 2f80c80..79f9d79 100644 --- a/waterfall.js +++ b/waterfall.js @@ -93,6 +93,168 @@ function renderWaterfall() { showRequestDetails(requestId); }); }); + + // Render details table + renderDetailsTable(filteredEntries); +} + +function renderDetailsTable(entries) { + const container = document.getElementById('requestDetailsTable'); + + let html = ` + + + + + + + + + + + + + + + + `; + + entries.forEach(entry => { + const statusColor = getStatusColor(entry.status); + const sizeKB = (entry.size.transferSize / 1024).toFixed(1); + const timeMS = entry.timing.total.toFixed(0); + + html += ` + + + + + + + + + + + + `; + }); + + html += ` + +
# URL Status Type Method Size Time Protocol Priority
${entry.requestId}${truncateUrl(entry.url, 60)}${entry.status}${getResourceTypeBadgeText(entry.resourceType)}${entry.method}${sizeKB} KB${timeMS} ms${entry.protocol || 'N/A'}${entry.priority || 'N/A'}
+ `; + + container.innerHTML = html; + + // Add click handlers to table rows + container.querySelectorAll('tbody tr').forEach(row => { + row.style.cursor = 'pointer'; + row.addEventListener('click', () => { + const requestId = parseInt(row.dataset.requestId); + showRequestDetails(requestId); + }); + }); + + // Add sort handlers to headers + setupTableSort(); +} + +function getResourceTypeBadgeText(type) { + const badges = { + 'Document': 'HTML', + 'Stylesheet': 'CSS', + 'Script': 'JavaScript', + 'Image': 'Image', + 'Font': 'Font', + 'XHR': 'XHR', + 'Fetch': 'Fetch' + }; + return badges[type] || type; +} + +function setupTableSort() { + let currentTableSort = { column: null, ascending: true }; + + document.querySelectorAll('.details-table th[data-sort]').forEach(header => { + header.addEventListener('click', () => { + const column = header.dataset.sort; + + // Toggle sort direction if same column + if (currentTableSort.column === column) { + currentTableSort.ascending = !currentTableSort.ascending; + } else { + currentTableSort.column = column; + currentTableSort.ascending = true; + } + + // Update header styles + document.querySelectorAll('.details-table th').forEach(h => { + h.classList.remove('sorted'); + h.querySelector('.sort-icon').textContent = '▼'; + }); + header.classList.add('sorted'); + header.querySelector('.sort-icon').textContent = currentTableSort.ascending ? '▲' : '▼'; + + // Sort and re-render + sortTableBy(column, currentTableSort.ascending); + }); + }); +} + +function sortTableBy(column, ascending) { + const entries = [...currentHarData.entries]; + const filteredEntries = currentFilter === 'all' + ? entries + : entries.filter(e => e.resourceType === currentFilter); + + filteredEntries.sort((a, b) => { + let valA, valB; + + switch(column) { + case 'id': + valA = a.requestId; + valB = b.requestId; + break; + case 'url': + valA = a.url.toLowerCase(); + valB = b.url.toLowerCase(); + return ascending ? valA.localeCompare(valB) : valB.localeCompare(valA); + case 'status': + valA = a.status; + valB = b.status; + break; + case 'type': + valA = a.resourceType; + valB = b.resourceType; + return ascending ? valA.localeCompare(valB) : valB.localeCompare(valA); + case 'method': + valA = a.method; + valB = b.method; + return ascending ? valA.localeCompare(valB) : valB.localeCompare(valA); + case 'size': + valA = a.size.transferSize; + valB = b.size.transferSize; + break; + case 'time': + valA = a.timing.total; + valB = b.timing.total; + break; + case 'protocol': + valA = a.protocol || ''; + valB = b.protocol || ''; + return ascending ? valA.localeCompare(valB) : valB.localeCompare(valA); + case 'priority': + valA = a.priority || ''; + valB = b.priority || ''; + return ascending ? valA.localeCompare(valB) : valB.localeCompare(valA); + default: + return 0; + } + + return ascending ? valA - valB : valB - valA; + }); + + renderDetailsTable(filteredEntries); } function getStatusColor(status) {