Files
SpeedTest/frontend.html
Alexander Zinn 21b9e52f40 Add frontend speed test application and server setup
* Introduced a new HTML frontend for network speed testing with a responsive UI
* Implemented backend server functionality to serve the frontend and handle speed test APIs
* Added speed test logic for downloading and uploading data, including progress tracking and result validation
* Created README-SPEEDTEST.md for documentation on application architecture, setup, and usage.
* Updated package.json to include necessary scripts and dependencies for development and testing
2025-11-21 04:56:18 -05:00

366 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Speed Test</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 20px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.controls {
display: flex;
gap: 15px;
margin-bottom: 30px;
align-items: center;
}
.size-selector {
display: flex;
align-items: center;
gap: 10px;
}
select, button {
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 16px;
}
button {
background: #007bff;
color: white;
border: none;
cursor: pointer;
transition: background 0.2s;
}
button:hover:not(:disabled) {
background: #0056b3;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.progress {
margin-bottom: 20px;
}
.progress-bar {
width: 100%;
height: 20px;
background: #e9ecef;
border-radius: 10px;
overflow: hidden;
margin-bottom: 10px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #28a745, #20c997);
width: 0%;
transition: width 0.3s ease;
}
.progress-text {
text-align: center;
font-weight: bold;
color: #666;
}
.results {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin-top: 20px;
}
.result-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #dee2e6;
}
.result-item:last-child {
border-bottom: none;
}
.result-label {
font-weight: 500;
}
.result-value {
font-weight: bold;
color: #007bff;
}
.status {
text-align: center;
padding: 10px;
margin: 10px 0;
border-radius: 6px;
font-weight: bold;
}
.status.idle {
background: #e9ecef;
color: #6c757d;
}
.status.downloading {
background: #d1ecf1;
color: #0c5460;
}
.status.uploading {
background: #d4edda;
color: #155724;
}
.status.completed {
background: #d1ecf1;
color: #0c5460;
}
.status.error {
background: #f8d7da;
color: #721c24;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div class="container">
<h1>🌐 Network Speed Test</h1>
<div class="controls">
<div class="size-selector">
<label for="testSize">Test Size:</label>
<select id="testSize">
<option value="1048576">1 MB</option>
<option value="5242880">5 MB</option>
<option value="10485760" selected>10 MB</option>
<option value="26214400">25 MB</option>
<option value="52428800">50 MB</option>
<option value="104857600">100 MB</option>
<option value="262144000">250 MB</option>
<option value="524288000">500 MB</option>
<option value="1048576000">1 GB</option>
<option value="2621440000">2.5 GB</option>
</select>
</div>
<button id="startTest" onclick="startSpeedTest()">Start Speed Test</button>
</div>
<div class="status idle" id="status">Ready to start speed test</div>
<div class="progress hidden" id="progressContainer">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="progress-text" id="progressText">0%</div>
</div>
<div class="results hidden" id="results">
<h3>Test Results</h3>
<div class="result-item">
<span class="result-label">Download Speed:</span>
<span class="result-value" id="downloadSpeed">-</span>
</div>
<div class="result-item">
<span class="result-label">Upload Speed:</span>
<span class="result-value" id="uploadSpeed">-</span>
</div>
<div class="result-item">
<span class="result-label">Total Time:</span>
<span class="result-value" id="totalTime">-</span>
</div>
<div class="result-item">
<span class="result-label">Data Size:</span>
<span class="result-value" id="dataSize">-</span>
</div>
<div class="result-item">
<span class="result-label">Data Integrity:</span>
<span class="result-value" id="dataIntegrity">-</span>
</div>
</div>
</div>
<script>
let downloadedData = null;
let requestId = null;
const baseUrl = window.location.origin; // Same server as frontend
async function startSpeedTest() {
const testSize = document.getElementById('testSize').value;
const startButton = document.getElementById('startTest');
// Reset UI
updateStatus('idle', 'Starting speed test...');
document.getElementById('progressContainer').classList.add('hidden');
document.getElementById('results').classList.add('hidden');
startButton.disabled = true;
startButton.textContent = 'Testing...';
try {
// Phase 1: Download
await performDownload(testSize);
// Phase 2: Upload
await performUpload();
// Show results
document.getElementById('results').classList.remove('hidden');
updateStatus('completed', 'Speed test completed successfully!');
} catch (error) {
console.error('Speed test failed:', error);
updateStatus('error', `Speed test failed: ${error.message}`);
} finally {
startButton.disabled = false;
startButton.textContent = 'Start Speed Test';
}
}
async function performDownload(size) {
updateStatus('downloading', 'Downloading test data...');
updateProgress(0);
document.getElementById('progressContainer').classList.remove('hidden');
const startTime = Date.now();
const response = await fetch(`${baseUrl}/data?size=${size}`);
if (!response.ok) {
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
}
// Get request ID from headers
requestId = response.headers.get('X-Request-ID');
if (!requestId) {
throw new Error('No request ID received from server');
}
// Read the response as array buffer
const contentLength = parseInt(response.headers.get('Content-Length') || '0');
const reader = response.body.getReader();
const chunks = [];
let receivedLength = 0;
while (true) {
const {done, value} = await reader.read();
if (done) break;
chunks.push(value);
receivedLength += value.length;
// Update progress
const progress = (receivedLength / contentLength) * 100;
updateProgress(progress);
}
// Combine chunks
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
const combined = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
combined.set(chunk, offset);
offset += chunk.length;
}
downloadedData = combined;
const endTime = Date.now();
const downloadTime = (endTime - startTime) / 1000; // seconds
const downloadSpeedMBps = (totalLength / 1024 / 1024) / downloadTime;
const downloadSpeedMbps = downloadSpeedMBps * 8;
document.getElementById('downloadSpeed').textContent = `${downloadSpeedMbps.toFixed(2)} Mbps`;
document.getElementById('dataSize').textContent = `${(totalLength / 1024 / 1024).toFixed(2)} MB`;
console.log(`Download completed: ${downloadSpeedMbps.toFixed(2)} Mbps`);
}
async function performUpload() {
updateStatus('uploading', 'Uploading test data...');
updateProgress(0);
const startTime = Date.now();
const response = await fetch(`${baseUrl}/data?requestId=${requestId}`, {
method: 'POST',
body: downloadedData,
headers: {
'Content-Type': 'application/octet-stream'
}
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
}
const result = await response.json();
const endTime = Date.now();
const uploadTime = (endTime - startTime) / 1000; // seconds
const uploadSpeedMBps = (downloadedData.length / 1024 / 1024) / uploadTime;
const uploadSpeedMbps = uploadSpeedMBps * 8;
// Update results
document.getElementById('uploadSpeed').textContent = `${uploadSpeedMbps.toFixed(2)} Mbps`;
document.getElementById('totalTime').textContent = `${((endTime - startTime) / 1000).toFixed(2)} seconds`;
document.getElementById('dataIntegrity').textContent = result.valid ? '✅ Valid' : '❌ Invalid';
updateProgress(100);
console.log(`Upload completed: ${uploadSpeedMbps.toFixed(2)} Mbps`);
console.log('Validation result:', result);
}
function updateStatus(statusClass, message) {
const statusEl = document.getElementById('status');
statusEl.className = `status ${statusClass}`;
statusEl.textContent = message;
}
function updateProgress(percent) {
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
progressFill.style.width = `${percent}%`;
progressText.textContent = `${percent.toFixed(1)}%`;
}
// Server URL (same origin since frontend is served from same server)
// Initialize
document.addEventListener('DOMContentLoaded', function() {
console.log('Speed test frontend loaded. Server URL:', baseUrl);
});
</script>
</body>
</html>