Add comprehensive column visibility controls to HTTP requests table
- Create ColumnSettings component with collapsible panel and organized column groups - Implement localStorage persistence for column visibility preferences across sessions - Add default visible columns: expand, url, start time, total response time, data rate, content-length - Group columns by category: Basic, Connection, Timing, Performance, Advanced - Provide bulk actions: Show All, Hide All, Reset to Defaults - Add conditional rendering for all table headers and cells based on visibility state - Update RequestRowDetails to dynamically calculate colSpan based on visible columns - Create responsive grid layout for column settings with hover effects - Use CSS modules with App.css variables for consistent theming - Implement type-safe column management with proper TypeScript interfaces Features: - 🎛️ Gear icon toggle button for easy access to column settings - 📁 Logical grouping of related columns for better organization - 💾 Automatic persistence of user preferences in localStorage - 🎯 Clean default view showing only essential columns - 🔧 Flexible customization allowing users to show exactly what they need - 📱 Responsive design that adapts to different screen sizes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
33cafe695c
commit
f9abfbc8ff
125
src/components/httprequestviewer/ColumnSettings.module.css
Normal file
125
src/components/httprequestviewer/ColumnSettings.module.css
Normal file
@ -0,0 +1,125 @@
|
||||
/* Column Settings component styles using CSS variables from App.module.css */
|
||||
|
||||
.columnSettings {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.toggleButton {
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-base);
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.toggleButton:hover {
|
||||
background: var(--color-bg-hover);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-lg);
|
||||
margin-top: var(--spacing-sm);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.panelHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
padding-bottom: var(--spacing-md);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.panelHeader h3 {
|
||||
margin: 0;
|
||||
color: var(--color-text-highlight);
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.bulkActions {
|
||||
display: flex;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.bulkButton {
|
||||
background: var(--color-bg-light);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-sm);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.bulkButton:hover {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.columnGroups {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.columnGroup {
|
||||
background: var(--color-bg-light);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.groupTitle {
|
||||
margin: 0 0 var(--spacing-md) 0;
|
||||
color: var(--color-text-highlight);
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.columnList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.columnItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
cursor: pointer;
|
||||
padding: var(--spacing-xs);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.columnItem:hover {
|
||||
background-color: var(--color-bg-hover);
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
accent-color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.columnLabel {
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
122
src/components/httprequestviewer/ColumnSettings.tsx
Normal file
122
src/components/httprequestviewer/ColumnSettings.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import React from 'react'
|
||||
import styles from './ColumnSettings.module.css'
|
||||
|
||||
interface ColumnConfig {
|
||||
key: string
|
||||
label: string
|
||||
group: string
|
||||
}
|
||||
|
||||
const COLUMN_CONFIGS: ColumnConfig[] = [
|
||||
// Basic Info
|
||||
{ key: 'expand', label: 'Expand', group: 'Basic' },
|
||||
{ key: 'method', label: 'Method', group: 'Basic' },
|
||||
{ key: 'status', label: 'Status', group: 'Basic' },
|
||||
{ key: 'type', label: 'Type', group: 'Basic' },
|
||||
{ key: 'priority', label: 'Priority', group: 'Basic' },
|
||||
{ key: 'url', label: 'URL', group: 'Basic' },
|
||||
|
||||
// Connection Info
|
||||
{ key: 'connectionNumber', label: 'Connection #', group: 'Connection' },
|
||||
{ key: 'requestNumber', label: 'Request #', group: 'Connection' },
|
||||
|
||||
// Timing
|
||||
{ key: 'startTime', label: 'Start Time', group: 'Timing' },
|
||||
{ key: 'queueTime', label: 'Queue Time', group: 'Timing' },
|
||||
{ key: 'dns', label: 'DNS', group: 'Timing' },
|
||||
{ key: 'connection', label: 'Connection', group: 'Timing' },
|
||||
{ key: 'serverLatency', label: 'Server Latency', group: 'Timing' },
|
||||
{ key: 'duration', label: 'Duration', group: 'Timing' },
|
||||
{ key: 'totalResponseTime', label: 'Total Response Time', group: 'Timing' },
|
||||
|
||||
// Size & Performance
|
||||
{ key: 'dataRate', label: 'Data Rate', group: 'Performance' },
|
||||
{ key: 'size', label: 'Size', group: 'Performance' },
|
||||
{ key: 'contentLength', label: 'Content-Length', group: 'Performance' },
|
||||
|
||||
// Advanced
|
||||
{ key: 'protocol', label: 'Protocol', group: 'Advanced' },
|
||||
{ key: 'cdn', label: 'CDN', group: 'Advanced' },
|
||||
{ key: 'cache', label: 'Cache', group: 'Advanced' }
|
||||
]
|
||||
|
||||
interface ColumnSettingsProps {
|
||||
visibleColumns: Record<string, boolean>
|
||||
onColumnToggle: (column: string) => void
|
||||
isOpen: boolean
|
||||
onToggle: () => void
|
||||
onShowAll: () => void
|
||||
onHideAll: () => void
|
||||
onResetDefaults: () => void
|
||||
}
|
||||
|
||||
const ColumnSettings: React.FC<ColumnSettingsProps> = ({
|
||||
visibleColumns,
|
||||
onColumnToggle,
|
||||
isOpen,
|
||||
onToggle,
|
||||
onShowAll,
|
||||
onHideAll,
|
||||
onResetDefaults
|
||||
}) => {
|
||||
const groups = [...new Set(COLUMN_CONFIGS.map(col => col.group))]
|
||||
|
||||
return (
|
||||
<div className={styles.columnSettings}>
|
||||
<button
|
||||
className={styles.toggleButton}
|
||||
onClick={onToggle}
|
||||
title="Column Settings"
|
||||
>
|
||||
⚙️ Columns {isOpen ? '▼' : '▶'}
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div className={styles.panel}>
|
||||
<div className={styles.panelHeader}>
|
||||
<h3>Column Visibility</h3>
|
||||
<div className={styles.bulkActions}>
|
||||
<button onClick={onShowAll} className={styles.bulkButton}>
|
||||
Show All
|
||||
</button>
|
||||
<button onClick={onHideAll} className={styles.bulkButton}>
|
||||
Hide All
|
||||
</button>
|
||||
<button onClick={onResetDefaults} className={styles.bulkButton}>
|
||||
Reset Defaults
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.columnGroups}>
|
||||
{groups.map(group => (
|
||||
<div key={group} className={styles.columnGroup}>
|
||||
<h4 className={styles.groupTitle}>{group}</h4>
|
||||
<div className={styles.columnList}>
|
||||
{COLUMN_CONFIGS
|
||||
.filter(col => col.group === group)
|
||||
.map(column => (
|
||||
<label key={column.key} className={styles.columnItem}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={visibleColumns[column.key] || false}
|
||||
onChange={() => onColumnToggle(column.key)}
|
||||
className={styles.checkbox}
|
||||
/>
|
||||
<span className={styles.columnLabel}>
|
||||
{column.label}
|
||||
</span>
|
||||
</label>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ColumnSettings
|
@ -3,6 +3,7 @@ import { useDatabaseTraceData } from '../../hooks/useDatabaseTraceData'
|
||||
import { getUrlParams, updateUrlWithTraceId } from '../../App'
|
||||
import RequestFilters from './RequestFilters'
|
||||
import RequestsTable from './RequestsTable'
|
||||
import ColumnSettings from './ColumnSettings'
|
||||
import styles from './HTTPRequestViewer.module.css'
|
||||
|
||||
// Lazy load 3D viewers to reduce main bundle size
|
||||
@ -76,6 +77,43 @@ export default function HTTPRequestViewer({ traceId }: HTTPRequestViewerProps) {
|
||||
// 3D viewer state - initialized from URL
|
||||
const [show3DViewer, setShow3DViewer] = useState(false)
|
||||
const [showTimelineViewer, setShowTimelineViewer] = useState(false)
|
||||
|
||||
// Column visibility state - default visible columns as requested
|
||||
const [visibleColumns, setVisibleColumns] = useState(() => {
|
||||
const saved = localStorage.getItem('httpRequestTableColumns')
|
||||
if (saved) {
|
||||
try {
|
||||
return JSON.parse(saved)
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse saved column settings, using defaults')
|
||||
}
|
||||
}
|
||||
// Default visible columns
|
||||
return {
|
||||
expand: true,
|
||||
method: false,
|
||||
status: false,
|
||||
type: false,
|
||||
priority: false,
|
||||
url: true,
|
||||
connectionNumber: false,
|
||||
requestNumber: false,
|
||||
startTime: true,
|
||||
queueTime: false,
|
||||
dns: false,
|
||||
connection: false,
|
||||
serverLatency: false,
|
||||
duration: false,
|
||||
totalResponseTime: true,
|
||||
dataRate: true,
|
||||
size: false,
|
||||
contentLength: true,
|
||||
protocol: false,
|
||||
cdn: false,
|
||||
cache: false
|
||||
}
|
||||
})
|
||||
const [columnSettingsOpen, setColumnSettingsOpen] = useState(false)
|
||||
|
||||
// Initialize 3D view state from URL on component mount and handle URL changes
|
||||
useEffect(() => {
|
||||
@ -117,6 +155,62 @@ export default function HTTPRequestViewer({ traceId }: HTTPRequestViewerProps) {
|
||||
setSsimThreshold(pendingSSIMThreshold)
|
||||
}
|
||||
|
||||
// Column visibility handlers
|
||||
const handleColumnToggle = (column: string) => {
|
||||
const newVisibleColumns = {
|
||||
...visibleColumns,
|
||||
[column]: !visibleColumns[column]
|
||||
}
|
||||
setVisibleColumns(newVisibleColumns)
|
||||
localStorage.setItem('httpRequestTableColumns', JSON.stringify(newVisibleColumns))
|
||||
}
|
||||
|
||||
const handleShowAllColumns = () => {
|
||||
const allVisible = Object.keys(visibleColumns).reduce((acc, key) => {
|
||||
acc[key] = true
|
||||
return acc
|
||||
}, {} as Record<string, boolean>)
|
||||
setVisibleColumns(allVisible)
|
||||
localStorage.setItem('httpRequestTableColumns', JSON.stringify(allVisible))
|
||||
}
|
||||
|
||||
const handleHideAllColumns = () => {
|
||||
const allHidden = Object.keys(visibleColumns).reduce((acc, key) => {
|
||||
acc[key] = key === 'expand' // Always keep expand column visible
|
||||
return acc
|
||||
}, {} as Record<string, boolean>)
|
||||
setVisibleColumns(allHidden)
|
||||
localStorage.setItem('httpRequestTableColumns', JSON.stringify(allHidden))
|
||||
}
|
||||
|
||||
const handleResetDefaults = () => {
|
||||
const defaultColumns = {
|
||||
expand: true,
|
||||
method: false,
|
||||
status: false,
|
||||
type: false,
|
||||
priority: false,
|
||||
url: true,
|
||||
connectionNumber: false,
|
||||
requestNumber: false,
|
||||
startTime: true,
|
||||
queueTime: false,
|
||||
dns: false,
|
||||
connection: false,
|
||||
serverLatency: false,
|
||||
duration: false,
|
||||
totalResponseTime: true,
|
||||
dataRate: true,
|
||||
size: false,
|
||||
contentLength: true,
|
||||
protocol: false,
|
||||
cdn: false,
|
||||
cache: false
|
||||
}
|
||||
setVisibleColumns(defaultColumns)
|
||||
localStorage.setItem('httpRequestTableColumns', JSON.stringify(defaultColumns))
|
||||
}
|
||||
|
||||
const httpRequests = useMemo(() => {
|
||||
if (!traceData) return []
|
||||
const httpRequests = processHTTPRequests(traceData.traceEvents)
|
||||
@ -429,6 +523,17 @@ export default function HTTPRequestViewer({ traceId }: HTTPRequestViewerProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Column Settings */}
|
||||
<ColumnSettings
|
||||
visibleColumns={visibleColumns}
|
||||
onColumnToggle={handleColumnToggle}
|
||||
isOpen={columnSettingsOpen}
|
||||
onToggle={() => setColumnSettingsOpen(!columnSettingsOpen)}
|
||||
onShowAll={handleShowAllColumns}
|
||||
onHideAll={handleHideAllColumns}
|
||||
onResetDefaults={handleResetDefaults}
|
||||
/>
|
||||
|
||||
{/* Requests Table */}
|
||||
<RequestsTable
|
||||
httpRequests={httpRequests}
|
||||
@ -438,6 +543,7 @@ export default function HTTPRequestViewer({ traceId }: HTTPRequestViewerProps) {
|
||||
showQueueAnalysis={showQueueAnalysis}
|
||||
expandedRows={expandedRows}
|
||||
onToggleRowExpansion={toggleRowExpansion}
|
||||
visibleColumns={visibleColumns}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -14,12 +14,15 @@ import {
|
||||
|
||||
interface RequestRowDetailsProps {
|
||||
request: HTTPRequest
|
||||
visibleColumns: Record<string, boolean>
|
||||
}
|
||||
|
||||
const RequestRowDetails: React.FC<RequestRowDetailsProps> = ({ request }) => {
|
||||
const RequestRowDetails: React.FC<RequestRowDetailsProps> = ({ request, visibleColumns }) => {
|
||||
// Calculate the number of visible columns for colSpan
|
||||
const visibleColumnCount = Object.values(visibleColumns).filter(Boolean).length
|
||||
return (
|
||||
<tr key={`${request.requestId}-expanded`} className={styles.expandedRow}>
|
||||
<td colSpan={20}>
|
||||
<td colSpan={visibleColumnCount}>
|
||||
<div className={styles.expandedContent}>
|
||||
|
||||
{/* Request Details */}
|
||||
|
@ -25,125 +25,174 @@ interface RequestRowSummaryProps {
|
||||
showQueueAnalysis: boolean
|
||||
isExpanded: boolean
|
||||
onToggleRowExpansion: (requestId: string) => void
|
||||
visibleColumns: Record<string, boolean>
|
||||
}
|
||||
|
||||
const RequestRowSummary: React.FC<RequestRowSummaryProps> = ({
|
||||
request,
|
||||
showQueueAnalysis,
|
||||
isExpanded,
|
||||
onToggleRowExpansion
|
||||
onToggleRowExpansion,
|
||||
visibleColumns
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<tr className={styles.default} key={request.requestId}
|
||||
onClick={() => onToggleRowExpansion(request.requestId)}
|
||||
>
|
||||
<td>
|
||||
{isExpanded ? '−' : '+'}
|
||||
</td>
|
||||
<td>
|
||||
{request.method}
|
||||
</td>
|
||||
<td>
|
||||
{request.statusCode || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{request.resourceType}
|
||||
</td>
|
||||
<td className={styles.priorityCell}>
|
||||
<span>
|
||||
{getPriorityIcon(request.priority)}
|
||||
{request.priority || '-'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href={request.url} target="_blank" rel="noopener noreferrer">
|
||||
{truncateUrl(request.url)}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{request.connectionNumber || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{request.requestNumberOnConnection || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{formatDuration(request.timing.startOffset)}
|
||||
</td>
|
||||
<td className={getConnectionClass(request?.timing?.queueTime ||0)}>
|
||||
<div>
|
||||
{formatDuration(request.timing.queueTime)}
|
||||
{showQueueAnalysis && request.queueAnalysis && (
|
||||
<span
|
||||
title={request.queueAnalysis.description}
|
||||
>
|
||||
{getQueueAnalysisIcon(request.queueAnalysis)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
{visibleColumns.expand && (
|
||||
<td>
|
||||
{isExpanded ? '−' : '+'}
|
||||
</td>
|
||||
)}
|
||||
{visibleColumns.method && (
|
||||
<td>
|
||||
{request.method}
|
||||
</td>
|
||||
)}
|
||||
{visibleColumns.status && (
|
||||
<td>
|
||||
{request.statusCode || '-'}
|
||||
</td>
|
||||
)}
|
||||
{visibleColumns.type && (
|
||||
<td>
|
||||
{request.resourceType}
|
||||
</td>
|
||||
)}
|
||||
{visibleColumns.priority && (
|
||||
<td className={styles.priorityCell}>
|
||||
<span>
|
||||
{getPriorityIcon(request.priority)}
|
||||
{request.priority || '-'}
|
||||
</span>
|
||||
</td>
|
||||
)}
|
||||
{visibleColumns.url && (
|
||||
<td>
|
||||
<a href={request.url} target="_blank" rel="noopener noreferrer">
|
||||
{truncateUrl(request.url)}
|
||||
</a>
|
||||
</td>
|
||||
)}
|
||||
{visibleColumns.connectionNumber && (
|
||||
<td>
|
||||
{request.connectionNumber || '-'}
|
||||
</td>
|
||||
)}
|
||||
{visibleColumns.requestNumber && (
|
||||
<td>
|
||||
{request.requestNumberOnConnection || '-'}
|
||||
</td>
|
||||
)}
|
||||
{visibleColumns.startTime && (
|
||||
<td>
|
||||
{formatDuration(request.timing.startOffset)}
|
||||
</td>
|
||||
)}
|
||||
{visibleColumns.queueTime && (
|
||||
<td className={getConnectionClass(request?.timing?.queueTime ||0)}>
|
||||
<div>
|
||||
{formatDuration(request.timing.queueTime)}
|
||||
{showQueueAnalysis && request.queueAnalysis && (
|
||||
<span
|
||||
title={request.queueAnalysis.description}
|
||||
>
|
||||
{getQueueAnalysisIcon(request.queueAnalysis)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
|
||||
{/* DNS Time */}
|
||||
<td >
|
||||
{request.timing.dnsStart !== undefined && request.timing.dnsEnd !== undefined && request.timing.dnsStart >= 0 && request.timing.dnsEnd >= 0
|
||||
? formatDuration((request.timing.dnsEnd || 0) - (request.timing.dnsStart || 0))
|
||||
: <span>cached</span>
|
||||
}
|
||||
</td>
|
||||
{visibleColumns.dns && (
|
||||
<td >
|
||||
{request.timing.dnsStart !== undefined && request.timing.dnsEnd !== undefined && request.timing.dnsStart >= 0 && request.timing.dnsEnd >= 0
|
||||
? formatDuration((request.timing.dnsEnd || 0) - (request.timing.dnsStart || 0))
|
||||
: <span>cached</span>
|
||||
}
|
||||
</td>
|
||||
)}
|
||||
|
||||
{/* Connection Time */}
|
||||
<td className={getConnectionClass((request.timing.connectEnd || 0) - (request.timing.connectStart || 0))}>
|
||||
{request.timing.connectStart !== undefined && request.timing.connectEnd !== undefined
|
||||
&& request.timing.connectStart >= 0 && request.timing.connectEnd >= 0
|
||||
? <span >
|
||||
{formatDuration((request.timing.connectEnd || 0) - (request.timing.connectStart || 0))}
|
||||
</span>
|
||||
: <span>
|
||||
{request.connectionReused ? 'reused' : 'cached'}
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
<td className={`${getServerLatencyClass(request.timing.serverLatency)}`}>
|
||||
{formatDuration(request.timing.serverLatency)}
|
||||
</td>
|
||||
{visibleColumns.connection && (
|
||||
<td className={getConnectionClass((request.timing.connectEnd || 0) - (request.timing.connectStart || 0))}>
|
||||
{request.timing.connectStart !== undefined && request.timing.connectEnd !== undefined
|
||||
&& request.timing.connectStart >= 0 && request.timing.connectEnd >= 0
|
||||
? <span >
|
||||
{formatDuration((request.timing.connectEnd || 0) - (request.timing.connectStart || 0))}
|
||||
</span>
|
||||
: <span>
|
||||
{request.connectionReused ? 'reused' : 'cached'}
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
)}
|
||||
|
||||
<td className={getDurationClass(request.timing.duration)}>
|
||||
{formatDuration(request.timing.duration)}
|
||||
</td>
|
||||
{visibleColumns.serverLatency && (
|
||||
<td className={`${getServerLatencyClass(request.timing.serverLatency)}`}>
|
||||
{formatDuration(request.timing.serverLatency)}
|
||||
</td>
|
||||
)}
|
||||
|
||||
{visibleColumns.duration && (
|
||||
<td className={getDurationClass(request.timing.duration)}>
|
||||
{formatDuration(request.timing.duration)}
|
||||
</td>
|
||||
)}
|
||||
|
||||
{/* Total Response Time */}
|
||||
<td className={`${getTotalResponseTimeClass(request.timing.totalResponseTime)}`}>
|
||||
{formatDuration(request.timing.totalResponseTime)}
|
||||
</td>
|
||||
{visibleColumns.totalResponseTime && (
|
||||
<td className={`${getTotalResponseTimeClass(request.timing.totalResponseTime)}`}>
|
||||
{formatDuration(request.timing.totalResponseTime)}
|
||||
</td>
|
||||
)}
|
||||
|
||||
{/* Data Rate */}
|
||||
<td className={`${getDataRateClass(request.encodedDataLength, request.contentLength, request.timing.duration)}`}>
|
||||
{formatDataRate(request.encodedDataLength, request.contentLength, request.timing.duration)}
|
||||
</td>
|
||||
{visibleColumns.dataRate && (
|
||||
<td className={`${getDataRateClass(request.encodedDataLength, request.contentLength, request.timing.duration)}`}>
|
||||
{formatDataRate(request.encodedDataLength, request.contentLength, request.timing.duration)}
|
||||
</td>
|
||||
)}
|
||||
|
||||
<td className={`${getSizeClass(request.encodedDataLength)}`}>
|
||||
{formatSize(request.encodedDataLength)}
|
||||
</td>
|
||||
<td className={`${getSizeClass(request.contentLength)}`}>
|
||||
{request.contentLength ? formatSize(request.contentLength) : '-'}
|
||||
</td>
|
||||
<td className={getProtocolClass(request.protocol)}>
|
||||
{request.protocol || '-'}
|
||||
</td>
|
||||
<td
|
||||
title={request.cdnAnalysis ?
|
||||
`${getCDNDisplayName(request.cdnAnalysis.provider)} ${request.cdnAnalysis.isEdge ? '(Edge)' : '(Origin)'} - ${request.cdnAnalysis.detectionMethod}` :
|
||||
'No CDN detected'}
|
||||
>
|
||||
{request.cdnAnalysis ? getCDNIcon(request.cdnAnalysis) : '-'}
|
||||
</td>
|
||||
<td>
|
||||
{request.fromCache ? '💾' : request.connectionReused ? '🔄' : '🌐'}
|
||||
</td>
|
||||
{visibleColumns.size && (
|
||||
<td className={`${getSizeClass(request.encodedDataLength)}`}>
|
||||
{formatSize(request.encodedDataLength)}
|
||||
</td>
|
||||
)}
|
||||
|
||||
{visibleColumns.contentLength && (
|
||||
<td className={`${getSizeClass(request.contentLength)}`}>
|
||||
{request.contentLength ? formatSize(request.contentLength) : '-'}
|
||||
</td>
|
||||
)}
|
||||
|
||||
{visibleColumns.protocol && (
|
||||
<td className={getProtocolClass(request.protocol)}>
|
||||
{request.protocol || '-'}
|
||||
</td>
|
||||
)}
|
||||
|
||||
{visibleColumns.cdn && (
|
||||
<td
|
||||
title={request.cdnAnalysis ?
|
||||
`${getCDNDisplayName(request.cdnAnalysis.provider)} ${request.cdnAnalysis.isEdge ? '(Edge)' : '(Origin)'} - ${request.cdnAnalysis.detectionMethod}` :
|
||||
'No CDN detected'}
|
||||
>
|
||||
{request.cdnAnalysis ? getCDNIcon(request.cdnAnalysis) : '-'}
|
||||
</td>
|
||||
)}
|
||||
|
||||
{visibleColumns.cache && (
|
||||
<td>
|
||||
{request.fromCache ? '💾' : request.connectionReused ? '🔄' : '🌐'}
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
|
||||
{/* Expanded Row Details */}
|
||||
{isExpanded && <RequestRowDetails request={request} />}
|
||||
{isExpanded && <RequestRowDetails request={request} visibleColumns={visibleColumns} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ interface RequestsTableProps {
|
||||
|
||||
// Display options
|
||||
showQueueAnalysis: boolean
|
||||
visibleColumns: Record<string, boolean>
|
||||
|
||||
// Row expansion state
|
||||
expandedRows: Set<string>
|
||||
@ -31,6 +32,7 @@ const RequestsTable: React.FC<RequestsTableProps> = ({
|
||||
paginatedTimelineEntries,
|
||||
paginatedRequests,
|
||||
showQueueAnalysis,
|
||||
visibleColumns,
|
||||
expandedRows,
|
||||
onToggleRowExpansion
|
||||
}) => {
|
||||
@ -41,111 +43,153 @@ const RequestsTable: React.FC<RequestsTableProps> = ({
|
||||
<table className={styles.table}>
|
||||
<thead className={styles.tableHeader}>
|
||||
<tr>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center} ${styles.expandColumn}`}>
|
||||
<Tooltip type={TooltipType.EXPAND_ROW}>
|
||||
Expand
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.left}`}>
|
||||
<Tooltip type={TooltipType.HTTP_METHOD}>
|
||||
Method
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.HTTP_STATUS}>
|
||||
Status
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.left}`}>
|
||||
<Tooltip type={TooltipType.RESOURCE_TYPE}>
|
||||
Type
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.REQUEST_PRIORITY}>
|
||||
Priority
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.left}`}>
|
||||
<Tooltip type={TooltipType.REQUEST_URL}>
|
||||
URL
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.CONNECTION_NUMBER}>
|
||||
Connection #
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.REQUEST_NUMBER}>
|
||||
Request #
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.START_TIME}>
|
||||
Start Time
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.QUEUE_TIME}>
|
||||
Queue Time
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.DNS_TIME}>
|
||||
DNS
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.CONNECTION_TIME}>
|
||||
Connection
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.SERVER_LATENCY}>
|
||||
Server Latency
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.REQUEST_DURATION}>
|
||||
Duration
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.TOTAL_RESPONSE_TIME}>
|
||||
Total Response Time
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.DATA_RATE}>
|
||||
Data Rate
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.TRANSFER_SIZE}>
|
||||
Size
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.CONTENT_LENGTH}>
|
||||
Content-Length
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.HTTP_PROTOCOL}>
|
||||
Protocol
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.CDN_DETECTION}>
|
||||
CDN
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.CACHE_STATUS}>
|
||||
Cache
|
||||
</Tooltip>
|
||||
</th>
|
||||
{visibleColumns.expand && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center} ${styles.expandColumn}`}>
|
||||
<Tooltip type={TooltipType.EXPAND_ROW}>
|
||||
Expand
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.method && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.left}`}>
|
||||
<Tooltip type={TooltipType.HTTP_METHOD}>
|
||||
Method
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.status && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.HTTP_STATUS}>
|
||||
Status
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.type && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.left}`}>
|
||||
<Tooltip type={TooltipType.RESOURCE_TYPE}>
|
||||
Type
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.priority && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.REQUEST_PRIORITY}>
|
||||
Priority
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.url && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.left}`}>
|
||||
<Tooltip type={TooltipType.REQUEST_URL}>
|
||||
URL
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.connectionNumber && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.CONNECTION_NUMBER}>
|
||||
Connection #
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.requestNumber && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.REQUEST_NUMBER}>
|
||||
Request #
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.startTime && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.START_TIME}>
|
||||
Start Time
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.queueTime && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.QUEUE_TIME}>
|
||||
Queue Time
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.dns && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.DNS_TIME}>
|
||||
DNS
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.connection && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.CONNECTION_TIME}>
|
||||
Connection
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.serverLatency && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.SERVER_LATENCY}>
|
||||
Server Latency
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.duration && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.REQUEST_DURATION}>
|
||||
Duration
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.totalResponseTime && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.TOTAL_RESPONSE_TIME}>
|
||||
Total Response Time
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.dataRate && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.DATA_RATE}>
|
||||
Data Rate
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.size && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.TRANSFER_SIZE}>
|
||||
Size
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.contentLength && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.CONTENT_LENGTH}>
|
||||
Content-Length
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.protocol && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.HTTP_PROTOCOL}>
|
||||
Protocol
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.cdn && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.CDN_DETECTION}>
|
||||
CDN
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
{visibleColumns.cache && (
|
||||
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
|
||||
<Tooltip type={TooltipType.CACHE_STATUS}>
|
||||
Cache
|
||||
</Tooltip>
|
||||
</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -173,6 +217,7 @@ const RequestsTable: React.FC<RequestsTableProps> = ({
|
||||
showQueueAnalysis={showQueueAnalysis}
|
||||
isExpanded={isExpanded}
|
||||
onToggleRowExpansion={onToggleRowExpansion}
|
||||
visibleColumns={visibleColumns}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
Loading…
Reference in New Issue
Block a user