| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- const toolButtons = document.querySelectorAll(".tool-button");
- const panels = document.querySelectorAll(".panel");
- let currentTSLTemplate = null;
- document.addEventListener("click", async (event) => {
- const button = event.target.closest("[data-copy-value]");
- if (button) {
- 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);
- }
- return;
- }
- const prefillButton = event.target.closest("[data-action='prefill-tls']");
- if (prefillButton && currentTSLTemplate) {
- populateTLSForm(currentTSLTemplate);
- activateTool("tls-generator");
- }
- });
- toolButtons.forEach((button) => {
- button.addEventListener("click", () => {
- activateTool(button.dataset.tool);
- });
- });
- 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("tsl-form").addEventListener("submit", async (event) => {
- event.preventDefault();
- const form = new FormData(event.currentTarget);
- await submitJSON("/api/tsl/check", {
- url: form.get("url"),
- }, renderTSLResult, document.getElementById("tsl-result"), (data) => {
- currentTSLTemplate = data.leafTemplate || null;
- });
- });
- 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 renderTSLResult(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 || "")],
- ])}
- <div class="record-block">
- <button type="button" class="secondary-button prefill-button" data-action="prefill-tls">Use In TLS Generator</button>
- </div>
- <div class="record-block">
- <h3>Certificate Chain</h3>
- <pre>${escapeHTML(JSON.stringify(data.chain, null, 2))}</pre>
- </div>
- <div class="record-block">
- <h3>Leaf Certificate PEM</h3>
- <pre>${escapeHTML(data.certificatePem)}</pre>
- </div>
- `;
- }
- function activateTool(target) {
- toolButtons.forEach((item) => item.classList.remove("active"));
- panels.forEach((panel) => panel.classList.remove("active"));
- document.querySelector(`.tool-button[data-tool="${target}"]`)?.classList.add("active");
- document.getElementById(target)?.classList.add("active");
- }
- function populateTLSForm(template) {
- const form = document.getElementById("tls-form");
- if (!form || !template) {
- return;
- }
- setFormValue(form, "commonName", template.commonName);
- setFormValue(form, "organization", template.organization);
- setFormValue(form, "organizationalUnit", template.organizationalUnit);
- setFormValue(form, "locality", template.locality);
- setFormValue(form, "state", template.state);
- setFormValue(form, "country", template.country);
- setFormValue(form, "dnsNames", template.dnsNames);
- setFormValue(form, "validDays", template.validDays);
- setFormValue(form, "keySize", template.keySize);
- }
- function setFormValue(form, name, value) {
- const field = form.elements.namedItem(name);
- if (field) {
- field.value = value ?? "";
- }
- }
- function renderPEMResult(data) {
- return `
- ${renderMetaGrid([
- ["PEM Blocks", escapeHTML(String(data.count || 0))],
- ])}
- ${(data.blocks || []).map((block, index) => `
- <div class="record-block">
- <h3>Block ${index + 1}: ${escapeHTML(block.kind || "unknown")} (${escapeHTML(block.pemType || "")})</h3>
- ${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")],
- ])}
- </div>
- `).join("")}
- `;
- }
- function renderCopyablePEMBlock(title, value) {
- return `
- <div class="record-block">
- <div class="record-heading">
- <h3>${escapeHTML(title)}</h3>
- <button type="button" class="copy-button" data-copy-value="${escapeHTML(value || "")}">Copy</button>
- </div>
- <pre>${escapeHTML(value || "")}</pre>
- </div>
- `;
- }
- function renderMetaGrid(items) {
- return `
- <div class="meta-grid">
- ${items.map(([label, value]) => `
- <div class="meta-item">
- <strong>${label}</strong>
- <span>${value || "None"}</span>
- </div>
- `).join("")}
- </div>
- `;
- }
- function renderListBlock(title, values = []) {
- return `
- <div class="record-block">
- <h3>${title}</h3>
- ${values.length ? `<ul>${values.map((value) => `<li>${escapeHTML(value)}</li>`).join("")}</ul>` : "<p>No records returned.</p>"}
- </div>
- `;
- }
- function renderErrorBlock(errors = {}) {
- const entries = Object.entries(errors);
- if (!entries.length) {
- return "";
- }
- return `
- <div class="record-block">
- <h3>Lookup Notes</h3>
- <ul>${entries.map(([kind, message]) => `<li>${escapeHTML(kind)}: ${escapeHTML(message)}</li>`).join("")}</ul>
- </div>
- `;
- }
- 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("'", "'");
- }
|