const toolButtons = document.querySelectorAll(".tool-button"); const panels = document.querySelectorAll(".panel"); document.addEventListener("click", async (event) => { const button = event.target.closest("[data-copy-value]"); if (!button) { return; } const value = button.dataset.copyValue || ""; const originalLabel = button.textContent; try { await copyText(value); button.textContent = "Copied"; button.classList.add("copied"); setTimeout(() => { button.textContent = originalLabel; button.classList.remove("copied"); }, 1500); } catch (error) { button.textContent = "Copy failed"; setTimeout(() => { button.textContent = originalLabel; }, 1500); } }); toolButtons.forEach((button) => { button.addEventListener("click", () => { const target = button.dataset.tool; toolButtons.forEach((item) => item.classList.remove("active")); panels.forEach((panel) => panel.classList.remove("active")); button.classList.add("active"); document.getElementById(target).classList.add("active"); }); }); document.getElementById("tls-form").addEventListener("submit", async (event) => { event.preventDefault(); const form = new FormData(event.currentTarget); const action = event.submitter?.value || "generate"; await submitJSON("/api/tls/generate", { commonName: form.get("commonName"), organization: form.get("organization"), organizationalUnit: form.get("organizationalUnit"), locality: form.get("locality"), state: form.get("state"), country: form.get("country"), dnsNames: form.get("dnsNames"), validDays: Number(form.get("validDays")), keySize: Number(form.get("keySize")), }, renderTLSResult, document.getElementById("tls-result"), (data) => { if (action === "download") { downloadBase64File(data.zipBase64, data.zipFilename || "tls-materials.zip", "application/zip"); } }); }); document.getElementById("dns-form").addEventListener("submit", async (event) => { event.preventDefault(); const form = new FormData(event.currentTarget); await submitJSON("/api/dns/lookup", { host: form.get("host"), }, renderDNSResult, document.getElementById("dns-result")); }); document.getElementById("ssl-form").addEventListener("submit", async (event) => { event.preventDefault(); const form = new FormData(event.currentTarget); await submitJSON("/api/ssl/check", { url: form.get("url"), }, renderSSLResult, document.getElementById("ssl-result")); }); document.getElementById("pem-form").addEventListener("submit", async (event) => { event.preventDefault(); const form = new FormData(event.currentTarget); await submitJSON("/api/pem/check", { pem: form.get("pem"), }, renderPEMResult, document.getElementById("pem-result")); }); async function submitJSON(url, payload, renderer, target, onSuccess) { setLoading(target); try { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(payload), }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || "Request failed"); } target.classList.remove("empty", "error"); target.innerHTML = renderer(data); if (onSuccess) { onSuccess(data); } } catch (error) { target.classList.remove("empty"); target.classList.add("error"); target.textContent = error.message; } } function setLoading(target) { target.classList.remove("error"); target.classList.add("empty"); target.textContent = "Working..."; } function renderTLSResult(data) { return ` ${renderMetaGrid([ ["Subject", escapeHTML(data.subject)], ["Issuer", escapeHTML(data.issuer)], ["Serial Number", escapeHTML(data.serialNumber)], ["Valid From", escapeHTML(data.notBefore)], ["Valid Until", escapeHTML(data.notAfter)], ["Certificate Type", escapeHTML(data.isSelfSigned ? "Self-Signed" : "Signed")], ["Key Size", escapeHTML(`${data.keySize || ""}-bit RSA`)], ["Organization", escapeHTML((data.organization || []).join(", "))], ["OU", escapeHTML((data.ou || []).join(", "))], ["Locality", escapeHTML((data.locality || []).join(", "))], ["State / Province", escapeHTML((data.state || []).join(", "))], ["Country", escapeHTML((data.country || []).join(", "))], ["DNS Names", escapeHTML((data.dnsNames || []).join(", "))], ])} ${renderCopyablePEMBlock("Public Key PEM", data.publicKeyPem)} ${renderCopyablePEMBlock("Certificate Signing Request", data.csrPem)} ${renderCopyablePEMBlock("Self-Signed Certificate PEM", data.selfSignedCertificatePem || data.certificatePem)} ${renderCopyablePEMBlock("Private Key PEM", data.privateKeyPem)} `; } function renderDNSResult(data) { return ` ${renderMetaGrid([ ["Host", escapeHTML(data.host)], ["CNAME", escapeHTML(data.cname || "None")], ])} ${renderListBlock("A / AAAA", data.ips)} ${renderListBlock("TXT", data.txt)} ${renderListBlock("NS", data.ns)} ${renderListBlock("MX", (data.mx || []).map((record) => `${record.host} (pref ${record.pref})`))} ${renderErrorBlock(data.errors)} `; } function renderSSLResult(data) { const leaf = (data.chain || [])[0] || {}; return ` ${renderMetaGrid([ ["Requested URL", escapeHTML(data.url)], ["Server Name", escapeHTML(data.serverName)], ["TLS Version", escapeHTML(data.version)], ["Cipher Suite", escapeHTML(data.cipherSuite)], ["ALPN", escapeHTML(data.negotiatedProtocol || "None")], ["Leaf Subject", escapeHTML(leaf.subject || "")], ["Leaf Issuer", escapeHTML(leaf.issuer || "")], ["Leaf Valid Until", escapeHTML(leaf.notAfter || "")], ])}

Certificate Chain

${escapeHTML(JSON.stringify(data.chain, null, 2))}

Leaf Certificate PEM

${escapeHTML(data.certificatePem)}
`; } function renderPEMResult(data) { return ` ${renderMetaGrid([ ["PEM Blocks", escapeHTML(String(data.count || 0))], ])} ${(data.blocks || []).map((block, index) => `

Block ${index + 1}: ${escapeHTML(block.kind || "unknown")} (${escapeHTML(block.pemType || "")})

${renderMetaGrid([ ["Subject", escapeHTML(block.subject || "None")], ["Issuer", escapeHTML(block.issuer || "None")], ["Serial Number", escapeHTML(block.serialNumber || "None")], ["Valid From", escapeHTML(block.notBefore || "None")], ["Valid Until", escapeHTML(block.notAfter || "None")], ["Algorithm", escapeHTML(block.algorithm || block.publicKeyAlgorithm || "None")], ["Signature Algorithm", escapeHTML(block.signatureAlgorithm || "None")], ["Size", escapeHTML(block.size ? `${block.size}-bit` : "None")], ["Curve", escapeHTML(block.curve || "None")], ["DNS Names", escapeHTML((block.dnsNames || []).join(", ") || "None")], ["Emails", escapeHTML((block.emailAddresses || []).join(", ") || "None")], ["CA", escapeHTML(block.isCA === true ? "Yes" : block.isCA === false ? "No" : "None")], ])}
`).join("")} `; } function renderCopyablePEMBlock(title, value) { return `

${escapeHTML(title)}

${escapeHTML(value || "")}
`; } function renderMetaGrid(items) { return `
${items.map(([label, value]) => `
${label} ${value || "None"}
`).join("")}
`; } function renderListBlock(title, values = []) { return `

${title}

${values.length ? `` : "

No records returned.

"}
`; } function renderErrorBlock(errors = {}) { const entries = Object.entries(errors); if (!entries.length) { return ""; } return `

Lookup Notes

`; } async function copyText(value) { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(value); return; } const textarea = document.createElement("textarea"); textarea.value = value; textarea.setAttribute("readonly", ""); textarea.style.position = "absolute"; textarea.style.left = "-9999px"; document.body.appendChild(textarea); textarea.select(); const success = document.execCommand("copy"); document.body.removeChild(textarea); if (!success) { throw new Error("copy failed"); } } function downloadBase64File(base64Value, filename, mimeType) { const binary = atob(base64Value || ""); const bytes = new Uint8Array(binary.length); for (let index = 0; index < binary.length; index += 1) { bytes[index] = binary.charCodeAt(index); } const blob = new Blob([bytes], { type: mimeType }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } function escapeHTML(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); }