365 lines
11 KiB
JavaScript
365 lines
11 KiB
JavaScript
// ---------------------------------------------------------
|
|
// 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");
|
|
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");
|
|
|
|
uptimeForm.addEventListener("submit", async (event) => {
|
|
event.preventDefault();
|
|
|
|
const url = urlInput.value.trim();
|
|
|
|
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();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(result.error || "The request failed.");
|
|
}
|
|
|
|
if (!result.online) {
|
|
throw new Error(result.error || "The website could not be reached.");
|
|
}
|
|
|
|
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) {
|
|
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";
|
|
}
|
|
});
|
|
// ---------------------------------------------------------
|
|
// SSL Certificate Checker
|
|
// ---------------------------------------------------------
|
|
|
|
// Get references to the SSL form and result elements.
|
|
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 certificate date strings into a readable local date.
|
|
function formatCertificateDate(value) {
|
|
if (!value) {
|
|
return "Unknown";
|
|
}
|
|
|
|
const date = new Date(value);
|
|
|
|
if (Number.isNaN(date.getTime())) {
|
|
return value;
|
|
}
|
|
|
|
return date.toLocaleString();
|
|
}
|
|
|
|
// Choose readable text for the certificate expiration state.
|
|
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 beginning 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 chain and hostname.
|
|
if (result.authorized) {
|
|
sslStatusBadge.textContent = "Valid";
|
|
sslStatusBadge.className = "status-badge online";
|
|
} else {
|
|
sslStatusBadge.textContent = "Not trusted";
|
|
sslStatusBadge.className = "status-badge offline";
|
|
}
|
|
|
|
// Populate the certificate summary.
|
|
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
|
|
);
|
|
|
|
// Populate the longer certificate values.
|
|
sslSubject.textContent = result.subject || "Unknown";
|
|
sslIssuer.textContent = result.issuer || "Unknown";
|
|
sslFingerprint.textContent =
|
|
result.fingerprint256 || "Unavailable";
|
|
|
|
// Show the authorization problem only 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";
|
|
}
|
|
}); |