mirror of
https://github.com/DeNNiiInc/Web-Page-Performance-Test.git
synced 2026-04-19 04:25:59 +00:00
Phase A.1 - Add sortable Request Details Table to waterfall
This commit is contained in:
@@ -247,6 +247,57 @@
|
|||||||
color: white;
|
color: white;
|
||||||
border-color: var(--color-accent);
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -278,6 +329,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="waterfall-canvas" id="waterfallCanvas"></div>
|
<div class="waterfall-canvas" id="waterfallCanvas"></div>
|
||||||
|
|
||||||
|
<h2 style="margin-top: 3rem;">Request Details</h2>
|
||||||
|
<div class="request-details-table" id="requestDetailsTable"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-overlay" id="dialogOverlay"></div>
|
<div class="dialog-overlay" id="dialogOverlay"></div>
|
||||||
|
|||||||
162
waterfall.js
162
waterfall.js
@@ -93,6 +93,168 @@ function renderWaterfall() {
|
|||||||
showRequestDetails(requestId);
|
showRequestDetails(requestId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Render details table
|
||||||
|
renderDetailsTable(filteredEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDetailsTable(entries) {
|
||||||
|
const container = document.getElementById('requestDetailsTable');
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<table class="details-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-sort="id"># <span class="sort-icon">▼</span></th>
|
||||||
|
<th data-sort="url">URL <span class="sort-icon">▼</span></th>
|
||||||
|
<th data-sort="status">Status <span class="sort-icon">▼</span></th>
|
||||||
|
<th data-sort="type">Type <span class="sort-icon">▼</span></th>
|
||||||
|
<th data-sort="method">Method <span class="sort-icon">▼</span></th>
|
||||||
|
<th data-sort="size">Size <span class="sort-icon">▼</span></th>
|
||||||
|
<th data-sort="time">Time <span class="sort-icon">▼</span></th>
|
||||||
|
<th data-sort="protocol">Protocol <span class="sort-icon">▼</span></th>
|
||||||
|
<th data-sort="priority">Priority <span class="sort-icon">▼</span></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
entries.forEach(entry => {
|
||||||
|
const statusColor = getStatusColor(entry.status);
|
||||||
|
const sizeKB = (entry.size.transferSize / 1024).toFixed(1);
|
||||||
|
const timeMS = entry.timing.total.toFixed(0);
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<tr data-request-id="${entry.requestId}">
|
||||||
|
<td>${entry.requestId}</td>
|
||||||
|
<td style="max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${entry.url}">${truncateUrl(entry.url, 60)}</td>
|
||||||
|
<td><span style="background: ${statusColor}; color: white; padding: 2px 6px; border-radius: 3px; font-size: 0.85rem;">${entry.status}</span></td>
|
||||||
|
<td>${getResourceTypeBadgeText(entry.resourceType)}</td>
|
||||||
|
<td>${entry.method}</td>
|
||||||
|
<td style="text-align: right;">${sizeKB} KB</td>
|
||||||
|
<td style="text-align: right;">${timeMS} ms</td>
|
||||||
|
<td>${entry.protocol || 'N/A'}</td>
|
||||||
|
<td>${entry.priority || 'N/A'}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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) {
|
function getStatusColor(status) {
|
||||||
|
|||||||
Reference in New Issue
Block a user