Add DNS lookup tool and tests

This commit is contained in:
Keith Casko
2026-07-23 02:24:31 -04:00
parent dd5092d352
commit 28ccd40686
9 changed files with 666 additions and 30 deletions
+140 -15
View File
@@ -1,5 +1,32 @@
// Get references to the page elements we need to update.
const form = document.getElementById("uptime-form");
// ---------------------------------------------------------
// Tool tab navigation
// ---------------------------------------------------------
const toolTabs = document.querySelectorAll(".tool-tab");
const toolPanels = document.querySelectorAll(".tool-panel");
toolTabs.forEach((tab) => {
tab.addEventListener("click", () => {
const selectedToolId = tab.dataset.tool;
// Update the active tab.
toolTabs.forEach((currentTab) => {
currentTab.classList.toggle("active", currentTab === tab);
});
// 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");
@@ -12,14 +39,11 @@ const redirectCount = document.getElementById("redirect-count");
const checkedAt = document.getElementById("checked-at");
const finalUrl = document.getElementById("final-url");
// Listen for the form submission.
form.addEventListener("submit", async (event) => {
// Prevent the browser from reloading the page.
uptimeForm.addEventListener("submit", async (event) => {
event.preventDefault();
const url = urlInput.value.trim();
// Reset the interface before starting a new check.
statusMessage.textContent = "Checking website...";
statusMessage.className = "status-message";
resultPanel.hidden = true;
@@ -37,17 +61,14 @@ form.addEventListener("submit", async (event) => {
const result = await response.json();
// Handle API-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";
@@ -62,15 +83,119 @@ form.addEventListener("submit", async (event) => {
statusMessage.textContent = "Website check completed.";
resultPanel.hidden = false;
} catch (error) {
// Show a safe, readable message to the user.
statusMessage.textContent = error.message;
statusMessage.className = "status-message error";
onlineBadge.textContent = "Offline";
onlineBadge.className = "status-badge offline";
} finally {
// Restore the button whether the request succeeded or failed.
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) {
if (recordType === "MX" && typeof record === "object") {
return `${record.exchange} (priority ${record.priority})`;
}
return String(record);
}
// Build one visible record group.
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);
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;
}
dnsForm.addEventListener("submit", async (event) => {
event.preventDefault();
const hostname = hostnameInput.value.trim();
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;
dnsRecords.replaceChildren();
const recordOrder = ["A", "AAAA", "CNAME", "MX", "TXT", "NS"];
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";
}
});
+82 -14
View File
@@ -3,27 +3,55 @@
<head>
<meta charset="UTF-8">
<!-- Makes the page scale correctly on phones and tablets. -->
<!-- Makes the layout work correctly on phones and tablets. -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ByteGeist Toolbox</title>
<!-- Load the stylesheet from the public folder. -->
<!-- Load the site stylesheet. -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main class="page-shell">
<section class="tool-panel">
<header class="site-header">
<p class="eyebrow">BYTEGEIST TOOLBOX</p>
<h1>Practical Website and Network Tools</h1>
<p class="description">
Check a website's current status or inspect its public DNS records.
</p>
</header>
<nav class="tool-tabs" aria-label="Tool selection">
<button
id="status-tab"
class="tool-tab active"
type="button"
data-tool="status-tool"
>
Website Status
</button>
<button
id="dns-tab"
class="tool-tab"
type="button"
data-tool="dns-tool"
>
DNS Lookup
</button>
</nav>
<!-- Website Status Checker -->
<section id="status-tool" class="tool-panel">
<header class="tool-header">
<p class="eyebrow">BYTEGEIST TOOLBOX</p>
<h1>Website Status Checker</h1>
<p class="description">
Check whether a public website is responding right now and view its status code,
response time, redirects, and final destination.
<h2>Website Status Checker</h2>
<p class="tool-description">
Check whether a public website is responding right now and view its
status code, response time, redirects, and final destination.
</p>
</header>
<form id="uptime-form" class="uptime-form">
<form id="uptime-form" class="tool-form">
<label for="url-input">Website URL</label>
<div class="input-row">
@@ -42,13 +70,11 @@
</div>
</form>
<!-- JavaScript updates this message during loading and errors. -->
<p id="status-message" class="status-message" aria-live="polite"></p>
<!-- Hidden until a result is returned. -->
<section id="result-panel" class="result-panel" hidden>
<div class="result-heading">
<h2>Current Status</h2>
<h3>Current Status</h3>
<span id="online-badge" class="status-badge"></span>
</div>
@@ -85,9 +111,51 @@
</div>
</section>
</section>
<!-- DNS Lookup -->
<section id="dns-tool" class="tool-panel" hidden>
<header class="tool-header">
<h2>DNS Lookup</h2>
<p class="tool-description">
Look up public A, AAAA, CNAME, MX, TXT, and NS records for a domain.
</p>
</header>
<form id="dns-form" class="tool-form">
<label for="hostname-input">Hostname or website URL</label>
<div class="input-row">
<input
id="hostname-input"
name="hostname"
type="text"
placeholder="example.com"
autocomplete="off"
required
>
<button id="dns-button" type="submit">
Look Up DNS
</button>
</div>
</form>
<p id="dns-message" class="status-message" aria-live="polite"></p>
<section id="dns-result-panel" class="result-panel" hidden>
<div class="result-heading">
<h3>DNS Records</h3>
<span id="dns-hostname" class="hostname-badge"></span>
</div>
<div id="dns-records" class="dns-records"></div>
<p id="dns-checked-at" class="dns-checked-at"></p>
</section>
</section>
</main>
<!-- Load the browser JavaScript after the HTML is available. -->
<!-- Load browser JavaScript after the page elements exist. -->
<script src="app.js"></script>
</body>
</html>
</html>
+109 -1
View File
@@ -233,4 +233,112 @@ button:disabled {
button {
width: 100%;
}
}
}
.site-header {
width: min(760px, 100%);
margin-bottom: 18px;
}
.site-header h1 {
margin-bottom: 10px;
}
.tool-tabs {
display: flex;
width: min(760px, 100%);
gap: 8px;
margin-bottom: 14px;
}
.tool-tab {
padding: 10px 14px;
border: 1px solid var(--border);
background: #0d1523;
color: var(--muted);
}
.tool-tab:hover {
background: var(--surface-light);
color: var(--text);
}
.tool-tab.active {
border-color: var(--accent);
background: rgba(76, 166, 255, 0.12);
color: var(--accent);
}
.tool-header h2 {
margin-bottom: 8px;
font-size: 1.6rem;
}
.tool-description {
margin-bottom: 24px;
color: var(--muted);
line-height: 1.6;
}
.hostname-badge {
padding: 5px 10px;
border-radius: 999px;
background: rgba(76, 166, 255, 0.12);
color: var(--accent);
font-size: 0.82rem;
font-weight: 700;
overflow-wrap: anywhere;
}
.dns-records {
display: grid;
gap: 12px;
}
.dns-record-group {
border: 1px solid var(--border);
border-radius: 8px;
background: #0d1523;
overflow: hidden;
}
.dns-record-group h4 {
margin: 0;
padding: 11px 14px;
border-bottom: 1px solid var(--border);
color: var(--accent);
font-size: 0.9rem;
letter-spacing: 0.08em;
}
.dns-record-list {
margin: 0;
padding: 12px 14px 12px 34px;
}
.dns-record-list li {
margin: 5px 0;
overflow-wrap: anywhere;
}
.empty-record {
margin: 0;
padding: 12px 14px;
color: var(--muted);
}
.dns-checked-at {
margin: 14px 0 0;
color: var(--muted);
font-size: 0.84rem;
}
@media (max-width: 640px) {
.tool-tabs {
display: grid;
grid-template-columns: 1fr 1fr;
}
.tool-tab {
width: 100%;
}
}