<!DOCTYPE html> <html> <head> <!-- Notes: This should be open in its original path--> <meta charset="utf-8"> <meta name="zoraxy.csrf.Token" content="{{.csrfToken}}"> <link rel="stylesheet" href="../script/semantic/semantic.min.css"> <script src="../script/jquery-3.6.0.min.js"></script> <script src="../script/semantic/semantic.min.js"></script> <script src="../script/utils.js"></script> <style> .disabled.table{ opacity: 0.5; pointer-events: none; } .expiredDomain{ color: rgb(238, 31, 31); } .validDomain{ color: rgb(49, 192, 113); } </style> </head> <body> <link rel="stylesheet" href="../darktheme.css"> <script src="../script/darktheme.js"></script> <br> <div class="ui container"> <div class="ui header"> <div class="content"> Certificates Auto Renew Settings <div class="sub header">Fetch and renew your certificates with Automated Certificate Management Environment (ACME) protocol</div> </div> </div> <div class="ui basic segment"> <p style="float: right; color: #21ba45; display:none;" id="enableToggleSucc"><i class="green checkmark icon"></i> Setting Updated</p> <div class="ui toggle checkbox"> <input type="checkbox" id="enableCertAutoRenew"> <label>Enable Certificate Auto Renew</label> </div> <br> <h3>ACME Email</h3> <p>Email is required by many CAs for renewing via ACME protocol</p> <div class="ui fluid action input"> <input id="caRegisterEmail" type="text" placeholder="webmaster@example.com"> <button class="ui icon basic button" onclick="saveEmailToConfig(this);"> <i class="blue save icon"></i> </button> </div> <small>If you don't want to share your private email address, you can also fill in an email address that point to a mailbox not exists on your domain.</small> </div> <div class="ui basic segment advanceoptions"> <div class="ui accordion advanceSettings"> <div class="title"> <i class="dropdown icon"></i> Advance Renew Policy </div> <div class="content"> <p>Renew all certificates with ACME supported CAs</p> <div class="ui toggle checkbox"> <input type="checkbox" id="renewAllSupported" onchange="setAutoRenewIfCASupportMode(this.checked);"> <label>Renew All Certs</label> </div><br> <button id="renewNowBtn" onclick="renewNow();" class="ui basic right floated button" style="margin-top: -2em;"><i class="yellow refresh icon"></i> Renew Now</button> <div class="ui horizontal divider"> OR </div> <p>Select the certificates to automatic renew in the list below</p> <div style="width: 100%; overflow-x: auto; margin-bottom: 1em;"> <table id="domainCertFileTable" class="ui very compact unstackable basic disabled table"> <thead> <tr> <th>Domain Name</th> <th>Match Rule</th> <th>Auto-Renew</th> </tr> </thead> <tbody id="domainTableBody"></tbody> </table> </div> <small><i class="ui red info circle icon"></i> Domain in red are expired</small><br> <div class="ui yellow message"> Certificate Renew only works on the certification authority (CA) supported by Zoraxy. Check Zoraxy wiki for more information on supported list of CAs. </div> <button class="ui basic right floated button" onclick="saveAutoRenewPolicy();"><i class="blue save icon"></i> Save Changes</button> <button id="renewSelectedButton" onclick="renewNow();" class="ui basic right floated disabled button"><i class="yellow refresh icon"></i> Renew Selected</button> <br><br> </div> </div> </div> <div class="ui divider"></div> <h3>Generate New Certificate</h3> <p>Enter a new / existing domain(s) to request new certificate(s)</p> <div class="ui form"> <div class="field"> <label>Domain(s)</label> <input id="domainsInput" type="text" placeholder="example.com" onkeyup="handlePostInputAutomation();"> <small>If you have more than one domain in a single certificate, enter the domains separated by commas (e.g. s1.dev.example.com,s2.dev.example.com) <span id="caNoDNSSupportWarning" style="color: #ffaf2e; display:none;"><br> <i class="exclamation triangle icon"></i> Current selected CA do not support DNS challenge</span> </small> </div> <div class="field multiDomainOnly" style="display:none;"> <label>Matching Rule</label> <input id="filenameInput" type="text" placeholder="Enter filename (no file extension)"> <small>Matching rule to let Zoraxy pick which certificate to use (Also be used as filename). Usually is the longest common suffix of the entered addresses. (e.g. dev.example.com)</small> </div> <div class="field multiDomainOnly" style="display:none;"> <button class="ui basic fluid button" onclick="autoDetectMatchingRules();">Auto Detect Matching Rule</button> </div> <div class="field"> <label>Certificate Authority (CA)</label> <div class="ui selection dropdown" id="ca"> <input type="hidden" name="ca"> <i class="dropdown icon"></i> <div class="default text">Let's Encrypt</div> <div class="menu"> <div class="item" data-value="Let's Encrypt">Let's Encrypt</div> <div class="item" data-value="Buypass">Buypass</div> <div class="item" data-value="ZeroSSL">ZeroSSL</div> <div class="item" data-value="Custom ACME Server">Custom ACME Server</div> </div> </div> </div> <div class="field" id="dnsChallenge"> <div class="ui checkbox"> <input type="checkbox" id="useDnsChallenge" onchange="toggleDnsChallenge()"> <label>Use a DNS Challenge<br> </div> </div> <div class="field dnsChallengeOnly" style="display:none;"> <label>DNS Provider</label> <div class="ui search selection dropdown" id="dnsProvider"> <input type="hidden" name="dnsProvider" value=""> <i class="dropdown icon"></i> <div class="default text">Pick a DNS Provider</div> <div class="menu" id="dnsProviderList"> <!-- Auto populate moved to acmedns module and initDNSProviderList() --> </div> </div> </div> <div class="field dnsChallengeOnly" style="display:none;"> <div class="ui divider"></div> <p>DNS Credentials</p> <div id="dnsProviderAPIFields"> <p><i class="ui loading circle notch icon"></i> Generating WebForm</p> </div> <h4><i class="yellow exclamation triangle icon"></i> Notes & FAQ</h4> <div class="ui bulleted list"> <div class="item">Domain DNS credentials are stored separately. For each new subdomain, you will need to enter a new DNS credentials.</div> <div class="item">For some DNS providers like CloudFlare, you do not need to fill in all fields.</div> <div class="item">If you are not sure what to fill in, check out the documentation from <a href="https://go-acme.github.io/lego/dns/" target="_blank">lego (DNS challenge library)</a></div> </div> <!-- <label>Credentials File Content</label> <textarea id="dnsCredentials" placeholder=""></textarea> <small>For more information on the supported DNS Providers and their attirbutes look <a href="https://go-acme.github.io/lego/dns/" target="_blank">here</a>! </small> <div class="ui negative message"> <i class="icon exclamation triangle"></i> These credentials will be stored as plaintext in the database and in environment variables! </div> --> </div> <div class="field dnsChallengeOnly" style="display:none;"> <label>Domain Name Server (optional)</label> <input id="dnsInput" type="text" placeholder="ns.example.com"> <small>If you have more than one DNS server, enter them separated by commas (e.g. ns1.example.com,ns2.example.com)</small> </div> <div class="field" id="caInput" style="display:none;"> <label>ACME Server URL</label> <input id="caURL" type="text" placeholder="https://example.com/acme/dictionary"> </div> <div class="field" id="kidInput" style="display:none;"> <label>EAB Credentials (KID) for current provider</label> <input id="eab_kid" type="text" placeholder="Leave this field blank to keep the current configuration"> </div> <div class="field" id="hmacInput" style="display:none;"> <label>EAB HMAC Key for current provider</label> <input id="eab_hmac" type="text" placeholder="Leave this field blank to keep the current configuration"> </div> <div class="field" id="skipTLS" style="display:none;"> <div class="ui checkbox"> <input type="checkbox" id="skipTLSCheckbox"> <label>Ignore TLS/SSL Verification Error<br><small>E.g. self-signed, expired certificate (Not Recommended)</small></label> </div> </div> <button id="obtainButton" class="ui basic button" type="submit"><i class="yellow refresh icon"></i> Get Certificate</button> </div> <div class="ui divider"></div> <small>First time setting up HTTPS?<br>Try out our <a href="../tools/https.html" target="_blank">wizard</a></small> <button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Cancel</button> <br><br><br><br> </div> <script> let expiredDomains = []; let enableTrigerOnChangeEvent = true; $(".accordion").accordion(); $(".dropdown").dropdown(); $(".checkbox").checkbox(); function setAutoRenewIfCASupportMode(useAutoMode = true){ if (useAutoMode){ $("#domainCertFileTable").addClass("disabled"); $("#renewNowBtn").removeClass("disabled"); $("#renewSelectedButton").addClass("disabled"); }else{ $("#domainCertFileTable").removeClass("disabled"); $("#renewNowBtn").addClass("disabled"); $("#renewSelectedButton").removeClass("disabled"); } } function initRenewerConfigFromFile(){ //Set the renew switch state $.get("/api/acme/autoRenew/enable", function(data){ if (data == true){ $("#enableCertAutoRenew").parent().checkbox("set checked"); } $("#enableCertAutoRenew").on("change", function(){ if (!enableTrigerOnChangeEvent){ return; } toggleAutoRenew(); }) }); //Load the email from server side $.get("/api/acme/autoRenew/email", function(data){ if (data != "" && data != undefined && data != null){ $("#caRegisterEmail").val(data); } }); //Load the domain selection options $.get("/api/acme/autoRenew/renewPolicy", function(data){ if (data == true){ $("#renewAllSupported").parent().checkbox("set checked"); }else{ $("#renewAllSupported").parent().checkbox("set unchecked"); } }); } initRenewerConfigFromFile(); function saveEmailToConfig(btn){ $.cjax({ url: "/api/acme/autoRenew/email", method: "POST", data: {set: $("#caRegisterEmail").val()}, success: function(data){ if (data.error != undefined){ parent.msgbox(data.error, false, 5000); }else{ parent.msgbox("Email updated"); $(btn).html(`<i class="green check icon"></i>`); $(btn).addClass("disabled"); setTimeout(function(){ $(btn).html(`<i class="blue save icon"></i>`); $(btn).removeClass("disabled"); }, 3000); } } }); } function toggleAutoRenew(){ var enabled = $("#enableCertAutoRenew").parent().checkbox("is checked"); $.cjax({ url: "/api/acme/autoRenew/enable", method: "POST", data: {"enable": enabled}, success: function(data){ if (data.error){ parent.msgbox(data.error, false, 5000); if (enabled){ enableTrigerOnChangeEvent = false; $("#enableCertAutoRenew").parent().checkbox("set unchecked"); enableTrigerOnChangeEvent = true; } if (parent && parent.setACMEEnableStates){ parent.setACMEEnableStates(!enabled); } }else{ $("#enableToggleSucc").stop().finish().fadeIn("fast").delay(3000).fadeOut("fast"); if (parent && parent.setACMEEnableStates){ parent.setACMEEnableStates(enabled); } } } }); } //Render the domains table that exists in this zoraxy host function renderDomainTable(domainFileList) { // Get the table body element var tableBody = $('#domainTableBody'); // Clear the table body tableBody.empty(); // Iterate over the domain names var counter = 0; for (const [srcfile, domains] of Object.entries(domainFileList)) { // Create a table row var row = $('<tr>'); // Create the domain name cell var domainClass = "validDomain"; for (var i = 0; i < domains.length; i++){ let thisDomain = domains[i]; if (expiredDomains.includes(thisDomain)){ domainClass = "expiredDomain"; } } var domainCell = $('<td class="' + domainClass +'">').html(domains.join("<br>")); row.append(domainCell); var srcFileCell = $('<td>').text(srcfile); row.append(srcFileCell); // Create the auto-renew checkbox cell let domainsEncoded = encodeURIComponent(JSON.stringify(domains)); var checkboxCell = $(`<td domain="${domainsEncoded}" srcfile="${srcfile}">`); var checkbox = $(`<input name="${srcfile}">`).attr('type', 'checkbox'); checkboxCell.append(checkbox); row.append(checkboxCell); // Add the row to the table body tableBody.append(row); counter++; } if (Object.keys(domainFileList).length == 0){ //No certificate in this system tableBody.append(`<tr> <td colspan="3"><i class="ui green circle check icon"></i> No certificate in use</td> </tr>`); } } //Initiate domain table. If you needs to update the expired domain as well //call from initDomainFileList() instead function initDomainTable(){ $.get("/api/cert/listdomains?compact=true", function(data){ if (data.error != undefined){ parent.msgbox(data.error, false); }else{ renderDomainTable(data); } initAutoRenewPolicy(); }) } function initDomainFileList() { $.ajax({ url: "/api/acme/listExpiredDomains", method: "GET", success: function(response) { // Render domain table expiredDomains = response.domain; initDomainTable(); //renderDomainTable(response.domain); }, error: function(error) { console.log("Failed to fetch expired domains:", error); } }); } initDomainFileList(); // Button click event handler for obtaining certificate $("#obtainButton").click(function() { $("#obtainButton").addClass("loading").addClass("disabled"); updateCertificateEAB(function(succ){ if (succ){ //Continue to next step updateCertificateDNS(function(succ){ if (succ){ obtainCertificate(function(succ){ $("#obtainButton").removeClass("loading").removeClass("disabled"); }); }else{ $("#obtainButton").removeClass("loading").removeClass("disabled"); console.log("update Certificate DNS process halted"); } }); }else{ console.log("Update Certificate EAB process halted"); $("#obtainButton").removeClass("loading").removeClass("disabled"); } }); }); //On CA change in dropdown $("input[name=ca]").on('change', function() { if(this.value == "Custom ACME Server") { $("#caInput").show(); $("#kidInput").show(); $("#hmacInput").show(); $("#skipTLS").show(); $("#dnsChallenge").hide(); $(".dnsChallengeOnly").hide(); } else if (this.value == "ZeroSSL") { $("#kidInput").show(); $("#hmacInput").show(); $("#dnsChallenge").hide(); $(".dnsChallengeOnly").hide(); $("#skipTLS").hide(); } else if (this.value == "Buypass") { $("#kidInput").show(); $("#hmacInput").show(); $("#dnsChallenge").hide(); $(".dnsChallengeOnly").hide(); $("#skipTLS").hide(); }else { $("#caInput").hide(); $("#skipTLS").hide(); $("#kidInput").hide(); $("#hmacInput").hide(); $("#dnsChallenge").show(); if ($("#useDnsChallenge")[0].checked){ $(".dnsChallengeOnly").show(); } } }) //On DNS provider dropdown change $("input[name=dnsProvider]").on('change', function() { let newProviderName = $("#dnsProvider").find("input").val(); $.get("/api/acme/dns/providers?name=" + newProviderName, function(data){ console.log("Loaded required config", data); $("#dnsProviderAPIFields").html(""); //Generate a form for this config let booleanFieldsHTML = ""; let optionalFieldsHTML = ""; for (const [key, datatype] of Object.entries(data)) { if (datatype == "int"){ let defaultValue = 10; if (key == "HTTPTimeout"){ defaultValue = 300; } $("#dnsProviderAPIFields").append(`<div class="ui fluid labeled dnsConfigField input typeint" key="${key}" style="margin-top: 0.2em;"> <div class="ui basic blue label" style="font-weight: 300;"> ${key} </div> <input type="number" value="${defaultValue}"> </div>`); }else if (datatype == "bool"){ booleanFieldsHTML += (`<div class="ui checkbox dnsConfigField" key="${key}" style="margin-top: 1em !important; padding-left: 0.4em;"> <input type="checkbox"> <label>${key}</label> </div>`); }else if (datatype == "time.Duration"){ let defaultIntValue = 120; let defaultMinValue = 30; if (key == "PollingInterval"){ defaultIntValue = 2; defaultMinValue = 1; }else if (key == "PropagationTimeout"){ defaultIntValue = 120; defaultMinValue = 30; } optionalFieldsHTML += (`<div class="ui fluid labeled dnsConfigField small input" key="${key}" style="margin-top: 0.2em;"> <div class="ui basic blue label" style="font-weight: 300;"> ${key} </div> <input type="number" min="${defaultMinValue}" value="${defaultIntValue}"> <div class="ui basic label" style="font-weight: 300;"> secs </div> </div>`); }else{ //Default to string $("#dnsProviderAPIFields").append(`<div class="ui fluid labeled input dnsConfigField" key="${key}" style="margin-top: 0.2em;"> <div class="ui basic label" style="font-weight: 300;"> ${key} </div> <input type="text"> </div>`); } } //Append the boolean fields at the bottom, if exists $("#dnsProviderAPIFields").append(booleanFieldsHTML); if (booleanFieldsHTML != ""){ $(".dnsConfigField.checkbox").checkbox(); } //Append the optional fields at the bottom, if exists $("#dnsProviderAPIFields").append(optionalFieldsHTML); }); }); // Get filename form domains and input function getFilename() { var domains = $("#domainsInput").val(); var filename = $("#filenameInput").val(); if (filename.trim() == "" && !domains.includes(",")){ //Zoraxy filename are the matching name for domains. //Use the same as domains filename = domains; }else if (filename != "" && !domains.includes(",")){ //Invalid settings. Force the filename to be same as domain //if there are only 1 domain filename = domains; }else if (filename == "" && domains.includes(",")){ parent.msgbox("Filename cannot be empty for certs containing multiple domains.", false, 5000); $("#obtainButton").removeClass("loading").removeClass("disabled"); return; } //Filename cannot contain wildcards, and wildcards are possible with DNS challenges filename = filename.replace("*", "_"); return filename; } // Update EAB values for autorenewal function updateCertificateEAB(callback=undefined) { var ca = $("#ca").dropdown("get value"); var caURL = ""; if (ca == "Custom ACME Server") { ca = "custom"; caURL = $("#caURL").val(); }else if(ca == "Buypass") { caURL = "https://api.buypass.com/acme/directory"; }else if(ca == "ZeroSSL") { caURL = "https://acme.zerossl.com/v2/DV90"; } if(caURL == "") { //Skip update if (callback != undefined){ callback(true); } return; } var kid = $("#eab_kid").val(); var hmac = $("#eab_hmac").val(); if(kid == "" || hmac == "") { //Skip update if (callback != undefined){ callback(true); } return; } console.log(caURL + " " + kid + " " + hmac); $.ajax({ url: "/api/acme/autoRenew/setEAB", method: "GET", data: { acmeDirectoryURL: caURL, kid: kid, hmacEncoded: hmac, }, success: function(response) { //$("#obtainButton").removeClass("loading").removeClass("disabled"); if (response.error) { console.log("Error:", response.error); // Show error message parent.msgbox(response.error, false, 12000); if (callback != undefined){ callback(false); } } else { console.log("Certificate EAB updated successfully"); // Show success message parent.msgbox("Certificate EAB updated successfully"); // Renew the parent certificate list parent.initManagedDomainCertificateList(); if (callback != undefined){ callback(true); } } }, error: function(error) { //$("#obtainButton").removeClass("loading").removeClass("disabled"); console.log("Failed to update EAB configuration:", error); parent.msgbox("Failed to update EAB configuration"); if (callback != undefined){ callback(false); } } }); } //Read DNS credential from form and generate a key value structure that looks like // the old DNSCredential TextArea input function readDnsCredentials(){ let dnsCredentials = {}; $(".dnsConfigField").each(function(){ let thisKey = $(this).attr("key"); let value = ""; if ($(this).hasClass("checkbox")){ //Boolean option let checked = $(this).find("input")[0].checked; dnsCredentials[thisKey] = checked; }else if ($(this).hasClass("typeint")){ //Int options let value = $(this).find("input").val(); dnsCredentials[thisKey] = parseInt(value); }else{ //String options let value = $(this).find("input").val().trim(); dnsCredentials[thisKey] = value; } }); return dnsCredentials; } // Update DNS values for autorenewal function updateCertificateDNS(callback=undefined) { var dns = $("#useDnsChallenge")[0].checked; var dnsProvider = ""; var dnsCredentials = ""; if (!dns) { if (callback != undefined){ callback(true); } return; } //Check if all fields is empty. If yes, do not update the config let allFieldsEmpty = true; $(".dnsConfigField").each(function(){ if ($(this).find("input").val().trim() != ""){ allFieldsEmpty = false; } }); if (allFieldsEmpty){ //Do not update config on server side if (callback != undefined){ callback(true); } return; } dnsProvider = $("#dnsProvider").dropdown("get value"); //dnsCredentials = $("#dnsCredentials").val(); dnsCredentials = readDnsCredentials(); if(dnsProvider == "") { parent.msgbox("DNS Provider cannot be empty", false, 5000); $("#obtainButton").removeClass("loading").removeClass("disabled"); if (callback != undefined){ callback(false); } return; } var filename = getFilename(); if (filename == '') { parent.msgbox("Domain to renew cannot be empty", false, 5000); if (callback != undefined){ callback(false); } return; } $.cjax({ url: "/api/acme/autoRenew/setDNS", method: "POST", data: { filename: filename, dnsProvider: dnsProvider, dnsCredentials: JSON.stringify(dnsCredentials), }, success: function(response) { //$("#obtainButton").removeClass("loading").removeClass("disabled"); if (response.error) { console.log("Error:", response.error); // Show error message parent.msgbox(response.error, false, 12000); if (callback != undefined){ callback(false); } } else { console.log("Certificate DNS Credentials updated successfully"); // Show success message parent.msgbox("Certificate DNS Credentials updated successfully"); if (callback != undefined){ callback(true); } } }, error: function(error) { //$("#obtainButton").removeClass("loading").removeClass("disabled"); console.log("Failed to update DNS configuration:", error); parent.msgbox("Failed to update DNS configuration"); if (callback != undefined){ callback(false); } } }); } // Obtain certificate from API function obtainCertificate(callback=undefined) { var domains = $("#domainsInput").val(); var filename = getFilename(); if (filename == '') { if (callback != undefined){ parent.msgbox("Domain to obtain certificate cannot be empty", false) callback(false); } return; } var email = $("#caRegisterEmail").val(); if (email == ""){ parent.msgbox("ACME renew email is not set", false) if (callback != undefined){callback(false);} return; } var ca = $("#ca").dropdown("get value"); var caURL = ""; if (ca == "Custom ACME Server") { ca = "custom"; caURL = $("#caURL").val(); } var dns = $("#useDnsChallenge")[0].checked; var skipTLSValue = $("#skipTLSCheckbox")[0].checked; var dnsServers = $("#dnsInput").val(); // Erfassen der DNS-Server $.ajax({ url: "/api/acme/obtainCert", method: "GET", data: { domains: domains, filename: filename, email: email, ca: ca, caURL: caURL, skipTLS: skipTLSValue, dns: dns, dnsServers: dnsServers // DNS-Server in die Anfrage einfügen }, success: function(response) { $("#obtainButton").removeClass("loading").removeClass("disabled"); if (response.error) { console.log("Error:", response.error); // Show error message parent.msgbox(response.error, false, 12000); if (callback != undefined){callback(false);} } else { console.log("Certificate renewed successfully"); // Show success message parent.msgbox("Certificate renewed successfully"); // Renew the parent certificate list parent.initManagedDomainCertificateList(); if (callback != undefined){callback(true);} } }, error: function(error) { $("#obtainButton").removeClass("loading").removeClass("disabled"); console.log("Failed to renewed certificate:", error); if (callback != undefined){callback(false);} } }); } //Check if the entered domain contains multiple domains function checkIfInputDomainIsMultiple(){ var inputDomains = $("#domainsInput").val(); if (inputDomains.includes(",")){ $(".multiDomainOnly").show(); }else{ $(".multiDomainOnly").hide(); } } //Validate if the current combinations of domain and CA supports DNS challenge function validateDNSChallengeSupport(){ if ($("#domainsInput").val().includes("*")){ var ca = $("#ca").dropdown("get value"); if (ca == "Let's Encrypt" || ca == ""){ $("#caNoDNSSupportWarning").hide(); }else{ $("#caNoDNSSupportWarning").show(); } }else{ $("#caNoDNSSupportWarning").hide(); } } //call to validateDNSChallengeSupport() on #ca value change $("#ca").dropdown({ onChange: function(value, text, $selectedItem) { validateDNSChallengeSupport(); } }); //Handle the input change event on domain input function handlePostInputAutomation(){ checkIfInputDomainIsMultiple(); validateDNSChallengeSupport(); } function toggleDnsChallenge(){ if ( $("#useDnsChallenge")[0].checked){ $(".dnsChallengeOnly").show(); setTimeout(function(){ $("#dnsProvider").dropdown("set text", "Cloudflare"); }, 500); }else{ $(".dnsChallengeOnly").hide(); } } //Grab the longest common suffix of all domains //not that smart technically function autoDetectMatchingRules(){ var domainsString = $("#domainsInput").val(); if (!domainsString.includes(",")){ return domainsString; } let domains = domainsString.split(","); //Clean out any spacing between commas for (var i = 0; i < domains.length; i++){ domains[i] = domains[i].trim(); } function getLongestCommonSuffix(strings) { if (strings.length === 0) { return ''; // Return an empty string if the array is empty } var sortedStrings = strings.slice().sort(); // Create a sorted copy of the array var firstString = sortedStrings[0]; var lastString = sortedStrings[sortedStrings.length - 1]; var suffix = ''; var minLength = Math.min(firstString.length, lastString.length); for (var i = 0; i < minLength; i++) { if (firstString[firstString.length - 1 - i] !== lastString[lastString.length - 1 - i]) { break; // Stop iterating if characters don't match } suffix = firstString[firstString.length - 1 - i] + suffix; } return suffix; } let longestSuffix = getLongestCommonSuffix(domains); //Check if the suffix is a valid domain if (longestSuffix.substr(0,1) == "."){ //Trim off the first dot longestSuffix = longestSuffix.substr(1); } if (!longestSuffix.includes(".")){ parent.msgbox("Auto Detect failed: Multiple Domains", false, 5000); return; } $("#filenameInput").val(longestSuffix); } //Handle the renew now btn click function renewNow(){ $.get("/api/acme/autoRenew/renewNow", function(data){ if (data.error != undefined){ parent.msgbox(data.error, false, 6000); }else{ parent.msgbox(data) } }) } function initAutoRenewPolicy(){ $.get("/api/acme/autoRenew/listDomains", function(data){ if (data.error != undefined){ parent.msgbox(data.error, false) }else{ if (data[0] == "*"){ //Auto select and renew is enabled $("#renewAllSupported").parent().checkbox("set checked"); }else{ //This is a list of domain files data.forEach(function(name) { $('#domainTableBody input[type="checkbox"][name="' + name + '"]').prop('checked', true); }); $("#domainCertFileTable").removeClass("disabled"); $("#renewNowBtn").addClass("disabled"); $("#renewSelectedButton").removeClass("disabled"); } } }) } function saveAutoRenewPolicy(){ let autoRenewAll = $("#renewAllSupported").parent().checkbox("is checked"); if (autoRenewAll == true){ $.cjax({ url: "/api/acme/autoRenew/setDomains", method: "POST", data: {opr: "setAuto"}, success: function(data){ parent.msgbox("Renew policy rule updated") } }); }else{ let checkedNames = []; $('#domainTableBody input[type="checkbox"]:checked').each(function() { checkedNames.push($(this).attr('name')); }); $.cjax({ url: "/api/acme/autoRenew/setDomains", method: "POST", data: {opr: "setSelected", domains: JSON.stringify(checkedNames)}, success: function(data){ parent.msgbox("Renew policy rule updated") } }); } } //Load the json map and create the dropdown for DNS provider names let dnsProviderNameMap = {}; function initDNSProviderList(){ $.get("dnsnames.json", function(namemap){ dnsProviderNameMap = namemap; //Load a list of supported DNS provider from backend $("#dnsProviderList").html(""); $.get("/api/acme/dns/providers", function(providerList){ providerList.sort(); providerList.forEach(providerid => { let providerName = providerid; if (dnsProviderNameMap[providerid] != undefined){ providerName = dnsProviderNameMap[providerid]; } $("#dnsProviderList").append(`<div class="item" data-value="${providerid}">${providerName}</div>`); }); $("#dnsProvider").dropdown(); setTimeout(function(){ //The dropdown is large, it takes some time to load $("#dnsProvider").dropdown("set selected", "cloudflare"); }, 300) }); }); } initDNSProviderList(); //Clear up the input field when page load $("#filenameInput").val(""); </script> </body> </html>