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 = `
+
+
+
+ | # ▼ |
+ URL ▼ |
+ Status ▼ |
+ Type ▼ |
+ Method ▼ |
+ Size ▼ |
+ Time ▼ |
+ Protocol ▼ |
+ Priority ▼ |
+
+
+
+ `;
+
+ entries.forEach(entry => {
+ const statusColor = getStatusColor(entry.status);
+ const sizeKB = (entry.size.transferSize / 1024).toFixed(1);
+ const timeMS = entry.timing.total.toFixed(0);
+
+ html += `
+
+ | ${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'} |
+
+ `;
+ });
+
+ html += `
+
+
+ `;
+
+ 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) {