diff --git a/msp/msp/doctype/x509_certificate/__init__.py b/msp/msp/doctype/ssl_certificate/__init__.py
similarity index 100%
rename from msp/msp/doctype/x509_certificate/__init__.py
rename to msp/msp/doctype/ssl_certificate/__init__.py
diff --git a/msp/msp/doctype/ssl_certificate/ssl_certificate.js b/msp/msp/doctype/ssl_certificate/ssl_certificate.js
new file mode 100644
index 0000000..191fa15
--- /dev/null
+++ b/msp/msp/doctype/ssl_certificate/ssl_certificate.js
@@ -0,0 +1,112 @@
+function copy_to_clipboard(text) {
+ if (navigator.clipboard && window.isSecureContext) {
+ return navigator.clipboard.writeText(text);
+ }
+ // Fallback for non-HTTPS contexts
+ const textarea = document.createElement("textarea");
+ textarea.value = text;
+ textarea.style.position = "fixed";
+ textarea.style.opacity = "0";
+ document.body.appendChild(textarea);
+ textarea.select();
+ document.execCommand("copy");
+ document.body.removeChild(textarea);
+ return Promise.resolve();
+}
+
+frappe.ui.form.on("SSL Certificate", {
+ refresh(frm) {
+ if (!frm.is_new() && frm.doc.certificate_data) {
+ frm.trigger("render_downloads");
+ }
+ },
+
+ render_downloads(frm) {
+ const cn = frm.doc.common_name || frm.doc.name;
+ const has_key = !!frm.doc.private_key;
+ const has_ca = !!frm.doc.ca_chain;
+ const has_csr = !!frm.doc.csr_data;
+
+ const btn = (label, icon, format_type, disabled) => {
+ const cls = disabled ? "btn-default disabled" : "btn-primary-light";
+ const title = disabled ? 'title="Nicht vorhanden"' : "";
+ return ``;
+ };
+
+ const copy_btn = (label, format_type, disabled) => {
+ const cls = disabled ? "btn-default disabled" : "btn-default";
+ const title = disabled ? 'title="Nicht vorhanden"' : "";
+ return ``;
+ };
+
+ const html = `
+
+
+ Download
+ ${btn("Certificate", "certificate", "cert", false)}
+ ${btn("Private Key", "key", "key", !has_key)}
+ ${btn("Fullchain", "link", "fullchain", false)}
+ ${btn("Cert + Key", "files-o", "cert_key", !has_key)}
+ ${btn("CA Chain", "sitemap", "ca", !has_ca)}
+ ${btn("CSR", "file-text-o", "csr", !has_csr)}
+
+
+ Copy to Clipboard
+ ${copy_btn("Certificate", "cert", false)}
+ ${copy_btn("Private Key", "key", !has_key)}
+ ${copy_btn("Fullchain", "fullchain", false)}
+ ${copy_btn("CA Chain", "ca", !has_ca)}
+ ${copy_btn("CSR", "csr", !has_csr)}
+
+
`;
+
+ frm.fields_dict.downloads_html.$wrapper.html(html);
+
+ // Download handlers
+ frm.fields_dict.downloads_html.$wrapper.find("button[data-format]:not(.copy-btn):not(:disabled)").on("click", function () {
+ const format_type = $(this).data("format");
+ frappe.call({
+ method: "msp.msp.doctype.ssl_certificate.ssl_certificate.get_certificate_pem",
+ args: { name: frm.doc.name, format_type },
+ callback(r) {
+ if (r.message) {
+ const blob = new Blob([r.message.content], { type: "application/x-pem-file" });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = r.message.filename;
+ a.click();
+ URL.revokeObjectURL(url);
+ frappe.show_alert({ message: `${r.message.filename} heruntergeladen`, indicator: "green" });
+ }
+ },
+ });
+ });
+
+ // Copy handlers
+ frm.fields_dict.downloads_html.$wrapper.find(".copy-btn:not(:disabled)").on("click", function () {
+ const format_type = $(this).data("format");
+ const btn_el = $(this);
+ frappe.call({
+ method: "msp.msp.doctype.ssl_certificate.ssl_certificate.get_certificate_pem",
+ args: { name: frm.doc.name, format_type },
+ callback(r) {
+ if (r.message) {
+ copy_to_clipboard(r.message.content).then(() => {
+ const orig = btn_el.html();
+ btn_el.html('