diff --git a/AppImage/components/latency-detail-modal.tsx b/AppImage/components/latency-detail-modal.tsx index c55de82b..827f398f 100644 --- a/AppImage/components/latency-detail-modal.tsx +++ b/AppImage/components/latency-detail-modal.tsx @@ -170,6 +170,49 @@ const generateLatencyReport = (report: ReportData) => { endTime: new Date(report.data[report.data.length - 1].timestamp * 1000).toLocaleString(), } : null + // Generate chart SVG + const chartData = report.isRealtime + ? report.realtimeResults.map(r => r.latency_avg || 0) + : report.data.map(d => d.value || 0) + + let chartSvg = '

Not enough data points for chart

' + if (chartData.length >= 2) { + const minVal = Math.min(...chartData) + const maxVal = Math.max(...chartData) + const range = maxVal - minVal || 1 + const width = 700 + const height = 120 + const padding = 30 + + const points = chartData.map((val, i) => { + const x = padding + (i / (chartData.length - 1)) * (width - padding * 2) + const y = height - padding - ((val - minVal) / range) * (height - padding * 2) + return `${x},${y}` + }).join(' ') + + const areaPoints = `${padding},${height - padding} ${points} ${width - padding},${height - padding}` + + chartSvg = ` + + + + + + + + + + + ${maxVal.toFixed(0)}ms + ${((minVal + maxVal) / 2).toFixed(0)}ms + ${minVal.toFixed(0)}ms + + + ${chartData.length} samples + + ` + } + const html = ` @@ -248,11 +291,22 @@ const generateLatencyReport = (report: ReportData) => { .exec-text h3 { font-size: 16px; margin-bottom: 4px; } .exec-text p { font-size: 12px; color: #64748b; line-height: 1.5; } - /* Score bar */ - .score-bar-wrap { margin: 10px 0 6px; } - .score-bar-bg { height: 10px; background: #e2e8f0; border-radius: 5px; position: relative; overflow: hidden; } - .score-bar-fill { height: 100%; border-radius: 5px; } - .score-bar-labels { display: flex; justify-content: space-between; font-size: 9px; color: #94a3b8; margin-top: 3px; } + /* Latency gauge */ + .latency-gauge { + display: flex; flex-direction: column; align-items: center; flex-shrink: 0; width: 160px; + } + .gauge-value { display: flex; align-items: baseline; gap: 2px; margin-top: -10px; } + .gauge-num { font-size: 32px; font-weight: 800; line-height: 1; } + .gauge-unit { font-size: 14px; font-weight: 600; opacity: 0.8; } + .gauge-status { font-size: 10px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; margin-top: 2px; } + + /* Latency range display */ + .latency-range { + display: flex; gap: 24px; margin-top: 12px; padding-top: 12px; border-top: 1px solid #e2e8f0; + } + .range-item { display: flex; flex-direction: column; gap: 2px; } + .range-label { font-size: 10px; font-weight: 600; color: #94a3b8; text-transform: uppercase; } + .range-value { font-size: 16px; font-weight: 700; } /* Grids */ .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; } @@ -297,6 +351,58 @@ const generateLatencyReport = (report: ReportData) => { margin-top: 32px; padding-top: 12px; border-top: 1px solid #e2e8f0; display: flex; justify-content: space-between; font-size: 10px; color: #94a3b8; } + + /* Print styles */ + @media print { + * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; } + body { padding: 10mm; font-size: 10pt; } + .no-print { display: none !important; } + .container { max-width: 100%; padding: 0; } + + /* Prevent page breaks inside elements */ + .section { page-break-inside: avoid; break-inside: avoid; } + .exec-box { page-break-inside: avoid; break-inside: avoid; } + .card { page-break-inside: avoid; break-inside: avoid; } + .threshold-item { page-break-inside: avoid; break-inside: avoid; } + .info-box { page-break-inside: avoid; break-inside: avoid; } + .chk-tbl { page-break-inside: avoid; break-inside: avoid; } + .latency-gauge { page-break-inside: avoid; break-inside: avoid; } + .latency-range { page-break-inside: avoid; break-inside: avoid; } + + /* Force page breaks before major sections if needed */ + .section { page-break-before: auto; } + + /* Keep headers with their content */ + .section-title { page-break-after: avoid; break-after: avoid; } + + /* Ensure grids don't break awkwardly */ + .grid-2, .grid-3, .grid-4 { page-break-inside: avoid; break-inside: avoid; } + + /* Table rows - try to keep together */ + .chk-tbl tr { page-break-inside: avoid; break-inside: avoid; } + .chk-tbl thead { display: table-header-group; } + + /* Footer always at bottom */ + .rpt-footer { + page-break-inside: avoid; break-inside: avoid; + margin-top: 20px; + } + + /* Reduce spacing for print */ + .section { margin-bottom: 15px; } + .exec-box { padding: 12px; } + + /* Ensure SVG charts print correctly */ + svg { max-width: 100%; height: auto; } + } + + /* Mobile print adjustments */ + @media print and (max-width: 600px) { + .exec-box { flex-direction: column; gap: 15px; } + .latency-gauge { width: 100%; } + .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr 1fr; } + .latency-range { flex-wrap: wrap; gap: 12px; } + } @@ -343,10 +449,27 @@ function pmxPrint(){
1. Executive Summary
-
-
${report.isRealtime ? (realtimeStats?.current?.toFixed(0) ?? 'N/A') : report.stats.current}
-
ms
-
${statusText}
+
+ + + + + + + + + + + + + 0 + 300+ + +
+ ${report.isRealtime ? (realtimeStats?.avg?.toFixed(0) ?? 'N/A') : report.stats.avg} + ms +
+
${statusText}

Network Latency Assessment${report.isRealtime ? ' (Real-time)' : ''}

@@ -360,16 +483,19 @@ function pmxPrint(){ Average latency: ${report.stats.avg} ms.` }

-
-
- Min: ${report.isRealtime ? (realtimeStats?.min?.toFixed(1) ?? 'N/A') : report.stats.min} ms - Avg: ${report.isRealtime ? (realtimeStats?.avg?.toFixed(1) ?? 'N/A') : report.stats.avg} ms - Max: ${report.isRealtime ? (realtimeStats?.max?.toFixed(1) ?? 'N/A') : report.stats.max} ms +
+
+ Minimum + ${report.isRealtime ? (realtimeStats?.min?.toFixed(1) ?? 'N/A') : report.stats.min} ms
-
-
+
+ Average + ${report.isRealtime ? (realtimeStats?.avg?.toFixed(1) ?? 'N/A') : report.stats.avg} ms +
+
+ Maximum + ${report.isRealtime ? (realtimeStats?.max?.toFixed(1) ?? 'N/A') : report.stats.max} ms
-
0ms - Excellent100ms - Fair200ms - Poor300ms+
@@ -441,83 +567,17 @@ ${report.isRealtime && report.realtimeResults.length > 0 ? `
` : ''} - -
-
${report.isRealtime ? '4' : '3'}. Reference Thresholds
-
-
-

Excellent (< 50ms): Optimal for real-time applications, gaming, and video calls.

-
-
-
-

Good (50-100ms): Acceptable for most applications with minimal impact.

-
-
-
-

Fair (100-200ms): Noticeable delay. May affect VoIP and interactive applications.

-
-
-
-

Poor (> 200ms): Significant latency. Investigation recommended.

-
-
-
-
\${report.isRealtime ? '4' : '3'}. Latency Graph
+
${report.isRealtime ? '4' : '3'}. Latency Graph
- \${(() => { - const chartData = report.isRealtime - ? report.realtimeResults.map(r => r.latency_avg || 0) - : report.data.map(d => d.value || 0); - if (chartData.length < 2) return '

Not enough data points for chart

'; - - const minVal = Math.min(...chartData); - const maxVal = Math.max(...chartData); - const range = maxVal - minVal || 1; - const width = 700; - const height = 120; - const padding = 30; - - const points = chartData.map((val, i) => { - const x = padding + (i / (chartData.length - 1)) * (width - padding * 2); - const y = height - padding - ((val - minVal) / range) * (height - padding * 2); - return \`\${x},\${y}\`; - }).join(' '); - - const areaPoints = \`\${padding},\${height - padding} \${points} \${width - padding},\${height - padding}\`; - - return \` - - - - - - - - - - - - - \${maxVal.toFixed(0)}ms - \${((minVal + maxVal) / 2).toFixed(0)}ms - \${minVal.toFixed(0)}ms - - - - - - \${chartData.length} samples - - \`; - })()} + ${chartSvg}
-
\${report.isRealtime ? '5' : '4'}. Performance Thresholds
+
${report.isRealtime ? '5' : '4'}. Performance Thresholds

Excellent (< 50ms): Optimal for real-time applications, gaming, and video calls.

@@ -538,7 +598,7 @@ ${report.isRealtime && report.realtimeResults.length > 0 ? `
-
\${report.isRealtime ? '6' : '5'}. Methodology
+
${report.isRealtime ? '6' : '5'}. Methodology
Test Method
@@ -550,16 +610,16 @@ ${report.isRealtime && report.realtimeResults.length > 0 ? `
Target
-
\${report.targetLabel}
+
${report.targetLabel}
Target IP
-
\${report.target === 'gateway' ? 'Default Gateway' : report.target === 'cloudflare' ? '1.1.1.1' : '8.8.8.8'}
+
${report.target === 'gateway' ? 'Default Gateway' : report.target === 'cloudflare' ? '1.1.1.1' : '8.8.8.8'}

Performance Assessment

-

\${ +

${ statusText === 'Excellent' ? 'Network latency is excellent. No action required.' : statusText === 'Good' ? 'Network latency is within acceptable parameters.' : statusText === 'Fair' ? 'Network latency is elevated. Consider investigating network congestion or routing issues.' : @@ -572,10 +632,10 @@ ${report.isRealtime && report.realtimeResults.length > 0 ? `

Samples per Test