Ping Test Tool

Ping Test Tool

HTTP(S) Ping

Note: This tool measures HTTP(S) request latency, not a true ICMP ping.

Ping History

Your ping history will appear here.

Your ping history will appear here.

'; return; } ui.logList.innerHTML = lookupLog.map(log => `
${log.timestamp}

Pinged ${escapeHTML(log.host)} (${log.sent} packets, ${log.avg} ms avg)

`).join(''); } function logEntry(host, sent, avg) { const timestamp = new Date().toLocaleString('en-US'); pingLog.unshift({ timestamp, host, sent, avg }); if (pingLog.length > 50) pingLog.pop(); // Limit log size saveState(); renderLog(); } function showMessage(message, isError = true) { ui.errorMsg.textContent = message; ui.errorMsg.style.display = 'block'; } // --- PING LOGIC --- function sanitizeHost(host) { return host.trim().replace(/^(https?:\/\/)/, '').replace(/\/.*$/, ''); } async function executePing(host, timeout) { const controller = new AbortController(); const signal = controller.signal; const timeoutId = setTimeout(() => controller.abort(), timeout); const startTime = performance.now(); try { // We use 'no-cors' to measure latency to any domain without a CORS error. // We can't read the response, but we can time the round-trip. await fetch(`https://${host}`, { mode: 'no-cors', signal, cache: 'no-store' }); const endTime = performance.now(); clearTimeout(timeoutId); return { status: 'success', time: (endTime - startTime) }; } catch (e) { clearTimeout(timeoutId); if (e.name === 'AbortError') { return { status: 'timeout', time: null }; } return { status: 'error', time: null }; } } async function startPingSequence() { const host = sanitizeHost(ui.hostInput.value); if (!host) { showMessage("Please enter a valid host or IP address."); return; } isPinging = true; ui.startBtn.disabled = true; ui.stopBtn.style.display = 'inline-block'; ui.errorMsg.style.display = 'none'; ui.resultsContainer.style.display = 'block'; ui.resultsLog.innerHTML = ''; currentResults = { host: host, pings: [] }; const count = ui.countSelect.value === 'Infinity' ? Infinity : parseInt(ui.countSelect.value); const timeout = parseInt(ui.timeoutInput.value) || 2000; for (let i = 0; i < count; i++) { if (!isPinging) break; const result = await executePing(host, timeout); result.seq = i + 1; currentResults.pings.push(result); renderPingLine(result); updateSummary(); // Wait 1 second between pings if (i < count - 1) await new Promise(resolve => setTimeout(resolve, 1000)); } stopPingSequence(); } function stopPingSequence() { isPinging = false; ui.startBtn.disabled = false; ui.stopBtn.style.display = 'none'; if (currentResults.pings.length > 0) { const { sent, lost, avg } = getSummaryStats(); logEntry(currentResults.host, sent, avg.toFixed(2)); } } function getSummaryStats() { const sent = currentResults.pings.length; const successfulPings = currentResults.pings.filter(p => p.status === 'success'); const lost = sent - successfulPings.length; const avg = successfulPings.length > 0 ? successfulPings.reduce((acc, p) => acc + p.time, 0) / successfulPings.length : 0; return { sent, lost, avg }; } function updateSummary() { const { sent, lost, avg } = getSummaryStats(); ui.summaryHost.textContent = currentResults.host; ui.summarySent.textContent = sent; ui.summaryLost.textContent = lost; ui.summaryAvg.textContent = `${avg.toFixed(2)} ms`; } function renderPingLine(ping) { const line = document.createElement('div'); line.className = 'ptt-log-line'; let statusText = ''; switch (ping.status) { case 'success': statusText = `Reply from ${escapeHTML(currentResults.host)}: seq=${ping.seq} time=${ping.time.toFixed(2)} ms`; if (ping.time > 500) line.classList.add('warn'); else if (ping.time > 1000) line.classList.add('error'); else line.classList.add('success'); break; case 'timeout': statusText = `Request timed out for seq=${ping.seq}`; line.classList.add('error'); break; case 'error': statusText = `General error for seq=${ping.seq} (Host may be unreachable)`; line.classList.add('error'); break; } line.textContent = statusText; ui.resultsLog.appendChild(line); ui.resultsLog.scrollTop = ui.resultsLog.scrollHeight; } // --- PDF LOGIC --- function downloadReportPDF(data, title, filename) { const { jsPDF } = window.jspdf; if (!jsPDF || !window.jspdf.autoTable) { alert("PDF library not loaded!"); return; } const doc = new jsPDF(); doc.setFontSize(18); doc.text(title, 14, 22); if (data.pings) { // Single Report const { sent, lost, avg } = getSummaryStats(); doc.setFontSize(11); doc.setTextColor(100); doc.text(`Report for: ${data.host}`, 14, 28); doc.autoTable({ startY: 35, theme: 'grid', head: [['Metric', 'Value']], body: [ ['Packets Sent', sent], ['Packets Lost', lost], ['Avg. Latency', `${avg.toFixed(2)} ms`], ] }); doc.autoTable({ startY: doc.autoTable.previous.finalY + 10, head: [['Sequence', 'Status', 'Time (ms)']], body: data.pings.map(p => [p.seq, p.status, p.time ? p.time.toFixed(2) : 'N/A']), theme: 'striped', headStyles: { fillColor: [22, 163, 74] } // var(--primary-color) }); } else { // Log Report doc.autoTable({ startY: 30, head: [['Timestamp', 'Host', 'Packets Sent', 'Avg. Latency (ms)']], body: data.map(log => [log.timestamp, log.host, log.sent, log.avg]), theme: 'striped', headStyles: { fillColor: [22, 163, 74] } }); } doc.save(filename); } // --- EVENT LISTENERS --- ui.startBtn.addEventListener('click', startPingSequence); ui.stopBtn.addEventListener('click', stopPingSequence); ui.downloadReportBtn.addEventListener('click', () => { if (currentResults.pings.length === 0) { alert("Please run a test before downloading."); return; } downloadReportPDF(currentResults, "Ping Test Report", `Ping_Report_${currentResults.host}.pdf`); }); ui.downloadLogBtn.addEventListener('click', () => { if (pingLog.length === 0) { alert("Log is empty."); return; } downloadReportPDF(pingLog, "Ping History Log", "Ping_Log.pdf"); }); ui.clearLogBtn.addEventListener('click', () => { pingLog = []; saveState(); renderLog(); }); // --- TABBING LOGIC --- function switchTab(targetTabId) { ui.tabContents.forEach(c => c.classList.remove('active')); ui.tabButtons.forEach(b => b.classList.remove('active')); document.getElementById(`ptt-tab-${targetTabId}`).classList.add('active'); document.querySelector(`.ptt-tab-button[data-tab="${targetTabId}"]`).classList.add('active'); } function navigateTabs(dir) { const tabs = Array.from(ui.tabButtons); const active = tabs.find(t => t.classList.contains('active')); let index = tabs.indexOf(active) + dir; if (index < 0) index = 0; if (index >= tabs.length) index = tabs.length - 1; switchTab(tabs[index].dataset.tab); } ui.tabButtons.forEach(button => button.addEventListener('click', () => switchTab(button.dataset.tab))); ui.nextTabBtn.addEventListener('click', () => navigateTabs(1)); ui.prevTabBtn.addEventListener('click', () => navigateTabs(-1)); // --- UTILITY --- function escapeHTML(str) { const p = document.createElement('p'); p.textContent = str; return p.innerHTML; } // --- INITIALIZATION --- loadState(); renderLog(); });
Scroll to Top