// --------------------------------------------------------- // Tool tab navigation // --------------------------------------------------------- // Find all tool buttons and tool panels. const toolTabs = document.querySelectorAll(".tool-tab"); const toolPanels = document.querySelectorAll(".tool-panel"); // Switch tools when a tab button is clicked. toolTabs.forEach((tab) => { tab.addEventListener("click", () => { const selectedToolId = tab.dataset.tool; // Mark only the selected tab as active. toolTabs.forEach((currentTab) => { const isSelected = currentTab === tab; currentTab.classList.toggle("active", isSelected); if (isSelected) { currentTab.setAttribute("aria-current", "page"); } else { currentTab.removeAttribute("aria-current"); } }); // Show only the selected tool panel. toolPanels.forEach((panel) => { panel.hidden = panel.id !== selectedToolId; }); }); }); // --------------------------------------------------------- // Website Status Checker // --------------------------------------------------------- const uptimeForm = document.getElementById("uptime-form"); const urlInput = document.getElementById("url-input"); const checkButton = document.getElementById("check-button"); const statusMessage = document.getElementById( "status-message" ); const resultPanel = document.getElementById("result-panel"); const onlineBadge = document.getElementById("online-badge"); const statusCode = document.getElementById("status-code"); const responseTime = document.getElementById( "response-time" ); const redirectCount = document.getElementById( "redirect-count" ); const checkedAt = document.getElementById("checked-at"); const finalUrl = document.getElementById("final-url"); // Handle Website Status form submissions. uptimeForm.addEventListener("submit", async (event) => { event.preventDefault(); const url = urlInput.value.trim(); // Reset the interface before checking. statusMessage.textContent = "Checking website..."; statusMessage.className = "status-message"; resultPanel.hidden = true; checkButton.disabled = true; checkButton.textContent = "Checking..."; try { const response = await fetch("/api/uptime", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ url }), }); const result = await response.json(); // Handle HTTP-level errors. if (!response.ok) { throw new Error( result.error || "The request failed." ); } // Handle unreachable or blocked destinations. if (!result.online) { throw new Error( result.error || "The website could not be reached." ); } // Fill in the successful result. onlineBadge.textContent = "Online"; onlineBadge.className = "status-badge online"; statusCode.textContent = result.statusCode; responseTime.textContent = `${result.responseTimeMs} ms`; redirectCount.textContent = result.redirectCount; checkedAt.textContent = new Date(result.checkedAt).toLocaleString(); finalUrl.textContent = result.finalUrl; finalUrl.href = result.finalUrl; statusMessage.textContent = "Website check completed."; resultPanel.hidden = false; } catch (error) { statusMessage.textContent = error.message; statusMessage.className = "status-message error"; } finally { checkButton.disabled = false; checkButton.textContent = "Check Website"; } }); // --------------------------------------------------------- // DNS Lookup // --------------------------------------------------------- const dnsForm = document.getElementById("dns-form"); const hostnameInput = document.getElementById( "hostname-input" ); const dnsButton = document.getElementById("dns-button"); const dnsMessage = document.getElementById("dns-message"); const dnsResultPanel = document.getElementById( "dns-result-panel" ); const dnsHostname = document.getElementById( "dns-hostname" ); const dnsRecords = document.getElementById("dns-records"); const dnsCheckedAt = document.getElementById( "dns-checked-at" ); // Convert one DNS record into readable text. function formatDnsRecord(recordType, record) { // MX records are objects containing an exchange and priority. if ( recordType === "MX" && typeof record === "object" && record !== null ) { return `${record.exchange} (priority ${record.priority})`; } return String(record); } // Build one DNS record section. function createDnsRecordGroup(recordType, records) { const group = document.createElement("section"); group.className = "dns-record-group"; const heading = document.createElement("h4"); heading.textContent = recordType; group.appendChild(heading); // Show an empty state when no records exist. if (!Array.isArray(records) || records.length === 0) { const emptyMessage = document.createElement("p"); emptyMessage.className = "empty-record"; emptyMessage.textContent = "No records found."; group.appendChild(emptyMessage); return group; } const list = document.createElement("ul"); list.className = "dns-record-list"; records.forEach((record) => { const item = document.createElement("li"); item.textContent = formatDnsRecord( recordType, record ); list.appendChild(item); }); group.appendChild(list); return group; } // Handle DNS form submissions. dnsForm.addEventListener("submit", async (event) => { event.preventDefault(); const hostname = hostnameInput.value.trim(); // Reset the DNS interface before the request. dnsMessage.textContent = "Looking up DNS records..."; dnsMessage.className = "status-message"; dnsResultPanel.hidden = true; dnsButton.disabled = true; dnsButton.textContent = "Looking Up..."; try { const response = await fetch("/api/dns", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ hostname }), }); const result = await response.json(); if (!response.ok) { throw new Error( result.error || "The DNS lookup failed." ); } dnsHostname.textContent = result.hostname; // Remove any records from the previous lookup. dnsRecords.replaceChildren(); const recordOrder = [ "A", "AAAA", "CNAME", "MX", "TXT", "NS", ]; // Render each supported record type. recordOrder.forEach((recordType) => { const group = createDnsRecordGroup( recordType, result.records[recordType] ); dnsRecords.appendChild(group); }); dnsCheckedAt.textContent = `Checked ${new Date( result.checkedAt ).toLocaleString()}`; dnsMessage.textContent = "DNS lookup completed."; dnsResultPanel.hidden = false; } catch (error) { dnsMessage.textContent = error.message; dnsMessage.className = "status-message error"; } finally { dnsButton.disabled = false; dnsButton.textContent = "Look Up DNS"; } }); // --------------------------------------------------------- // SSL Certificate Checker // --------------------------------------------------------- const sslForm = document.getElementById("ssl-form"); const sslHostnameInput = document.getElementById( "ssl-hostname-input" ); const sslButton = document.getElementById("ssl-button"); const sslMessage = document.getElementById("ssl-message"); const sslResultPanel = document.getElementById( "ssl-result-panel" ); const sslStatusBadge = document.getElementById( "ssl-status-badge" ); const sslHostname = document.getElementById( "ssl-hostname" ); const sslDaysRemaining = document.getElementById( "ssl-days-remaining" ); const sslValidFrom = document.getElementById( "ssl-valid-from" ); const sslValidTo = document.getElementById("ssl-valid-to"); const sslProtocol = document.getElementById("ssl-protocol"); const sslCipher = document.getElementById("ssl-cipher"); const sslAddress = document.getElementById("ssl-address"); const sslCheckedAt = document.getElementById( "ssl-checked-at" ); const sslSubject = document.getElementById("ssl-subject"); const sslIssuer = document.getElementById("ssl-issuer"); const sslFingerprint = document.getElementById( "ssl-fingerprint" ); const sslAuthorizationErrorBlock = document.getElementById( "ssl-authorization-error-block" ); const sslAuthorizationError = document.getElementById( "ssl-authorization-error" ); // Convert a certificate date into local readable text. function formatCertificateDate(value) { if (!value) { return "Unknown"; } const date = new Date(value); if (Number.isNaN(date.getTime())) { return value; } return date.toLocaleString(); } // Convert days remaining into readable text. function getExpirationText(daysRemaining) { if ( daysRemaining === null || daysRemaining === undefined ) { return "Unknown"; } if (daysRemaining < 0) { return `${Math.abs(daysRemaining)} days expired`; } if (daysRemaining === 0) { return "Expires today"; } return `${daysRemaining} days`; } // Handle SSL certificate form submissions. sslForm.addEventListener("submit", async (event) => { event.preventDefault(); const hostname = sslHostnameInput.value.trim(); // Reset the SSL interface before the request. sslMessage.textContent = "Checking certificate..."; sslMessage.className = "status-message"; sslResultPanel.hidden = true; sslButton.disabled = true; sslButton.textContent = "Checking..."; try { const response = await fetch("/api/ssl", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ hostname }), }); const result = await response.json(); if (!response.ok) { throw new Error( result.error || "The SSL certificate check failed." ); } // Show whether Node trusted the certificate. if (result.authorized) { sslStatusBadge.textContent = "Valid"; sslStatusBadge.className = "status-badge online"; } else { sslStatusBadge.textContent = "Not trusted"; sslStatusBadge.className = "status-badge offline"; } sslHostname.textContent = result.hostname; sslDaysRemaining.textContent = getExpirationText(result.daysRemaining); sslValidFrom.textContent = formatCertificateDate(result.validFrom); sslValidTo.textContent = formatCertificateDate(result.validTo); sslProtocol.textContent = result.protocol || "Unknown"; sslCipher.textContent = result.cipher || "Unknown"; sslAddress.textContent = result.resolvedAddress ? `${result.resolvedAddress} (IPv${result.addressFamily})` : "Unknown"; sslCheckedAt.textContent = formatCertificateDate(result.checkedAt); sslSubject.textContent = result.subject || "Unknown"; sslIssuer.textContent = result.issuer || "Unknown"; sslFingerprint.textContent = result.fingerprint256 || "Unavailable"; // Only show the authorization error when one exists. if (result.authorizationError) { sslAuthorizationError.textContent = result.authorizationError; sslAuthorizationErrorBlock.hidden = false; } else { sslAuthorizationError.textContent = ""; sslAuthorizationErrorBlock.hidden = true; } sslMessage.textContent = "Certificate check completed."; sslResultPanel.hidden = false; } catch (error) { sslMessage.textContent = error.message; sslMessage.className = "status-message error"; } finally { sslButton.disabled = false; sslButton.textContent = "Check Certificate"; } }); // --------------------------------------------------------- // HTTP Headers Checker // --------------------------------------------------------- const headersForm = document.getElementById( "headers-form" ); const headersUrlInput = document.getElementById( "headers-url-input" ); const headersButton = document.getElementById( "headers-button" ); const headersMessage = document.getElementById( "headers-message" ); const headersResultPanel = document.getElementById( "headers-result-panel" ); const headersScoreBadge = document.getElementById( "headers-score-badge" ); const headersStatus = document.getElementById( "headers-status" ); const headersRedirects = document.getElementById( "headers-redirects" ); const headersScore = document.getElementById( "headers-score" ); const headersCheckedAt = document.getElementById( "headers-checked-at" ); const headersFinalUrl = document.getElementById( "headers-final-url" ); const securityHeaderResults = document.getElementById( "security-header-results" ); const allHeaderResults = document.getElementById( "all-header-results" ); // Build one visible security-header result. function createSecurityHeaderItem(header) { const item = document.createElement("article"); item.className = "security-header-item"; const heading = document.createElement("div"); heading.className = "security-header-heading"; const name = document.createElement("strong"); name.textContent = header.name; const status = document.createElement("span"); status.className = header.present ? "header-status present" : "header-status missing"; status.textContent = header.present ? "Present" : "Missing"; heading.append(name, status); const purpose = document.createElement("p"); purpose.className = "security-header-purpose"; purpose.textContent = header.purpose; item.append(heading, purpose); // Show the raw header value when present. if (header.present && header.value) { const value = document.createElement("p"); value.className = "security-header-value"; const code = document.createElement("code"); code.textContent = header.value; value.appendChild(code); item.appendChild(value); } return item; } // Build one raw response-header row. function createResponseHeaderItem(name, value) { const item = document.createElement("div"); item.className = "response-header-item"; const nameElement = document.createElement("div"); nameElement.className = "response-header-name"; nameElement.textContent = name; const valueElement = document.createElement("div"); valueElement.className = "response-header-value"; valueElement.textContent = value; item.append(nameElement, valueElement); return item; } // Handle HTTP header form submissions. headersForm.addEventListener("submit", async (event) => { event.preventDefault(); const url = headersUrlInput.value.trim(); // Reset the interface before checking. headersMessage.textContent = "Checking response headers..."; headersMessage.className = "status-message"; headersResultPanel.hidden = true; headersButton.disabled = true; headersButton.textContent = "Checking..."; try { const response = await fetch("/api/headers", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ url }), }); const result = await response.json(); if (!response.ok) { throw new Error( result.error || "The HTTP header check failed." ); } // Fill in the summary information. headersStatus.textContent = `${result.statusCode} ${ result.statusText || "" }`.trim(); headersRedirects.textContent = result.redirectCount; headersScore.textContent = `${result.presentSecurityHeaderCount} of ` + `${result.totalSecurityHeaderCount}`; headersScoreBadge.textContent = `${result.presentSecurityHeaderCount}/` + `${result.totalSecurityHeaderCount}`; headersCheckedAt.textContent = new Date(result.checkedAt).toLocaleString(); headersFinalUrl.textContent = result.finalUrl; headersFinalUrl.href = result.finalUrl; // Remove old security header results. securityHeaderResults.replaceChildren(); result.securityHeaders.forEach((header) => { securityHeaderResults.appendChild( createSecurityHeaderItem(header) ); }); // Remove old raw response headers. allHeaderResults.replaceChildren(); // Render all response headers alphabetically. Object.entries(result.headers) .sort(([nameA], [nameB]) => nameA.localeCompare(nameB) ) .forEach(([name, value]) => { allHeaderResults.appendChild( createResponseHeaderItem(name, value) ); }); headersMessage.textContent = "Header check completed."; headersResultPanel.hidden = false; } catch (error) { headersMessage.textContent = error.message; headersMessage.className = "status-message error"; } finally { headersButton.disabled = false; headersButton.textContent = "Check Headers"; } });