123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509 |
- <!DOCTYPE html>
- <html>
- <head>
- <!-- Notes: This should be open in its original path-->
- <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>
- <style>
- .disabled.table{
- opacity: 0.5;
- pointer-events: none;
-
- }
- .expiredDomain{
- color: rgb(238, 31, 31);
- }
- .validDomain{
- color: rgb(49, 192, 113);
- }
- </style>
- </head>
- <body>
- <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="[email protected]">
- <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" style="background-color: #f7f7f7; border-radius: 1em;">
- <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>
- <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>
- <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="checkIfInputDomainIsMultiple();">
- <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)</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 class="item" data-value="Google">Google</div> -->
- </div>
- </div>
- </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="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){
- $.ajax({
- url: "/api/acme/autoRenew/email",
- 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");
- $.post("/api/acme/autoRenew/enable?enable=" + enabled, function(data){
- if (data.error){
- parent.msgbox(data.error, false, 5000);
- if (enabled){
- enableTrigerOnChangeEvent = false;
- $("#enableCertAutoRenew").parent().checkbox("set unchecked");
- enableTrigerOnChangeEvent = true;
- }
- }else{
- $("#enableToggleSucc").stop().finish().fadeIn("fast").delay(3000).fadeOut("fast");
- }
- });
- }
- //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");
- obtainCertificate();
- });
- $("input[name=ca]").on('change', function() {
- if(this.value == "Custom ACME Server") {
- $("#caInput").show();
- $("#skipTLS").show();
- } else {
- $("#caInput").hide();
- $("#skipTLS").hide();
- }
- })
- // Obtain certificate from API
- function obtainCertificate() {
- var domains = $("#domainsInput").val();
- var filename = $("#filenameInput").val();
- var email = $("#caRegisterEmail").val();
- if (email == ""){
- parent.msgbox("ACME renew email is not set", false)
- $("#obtainButton").removeClass("loading").removeClass("disabled");
- return;
- }
- 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{
- parent.msgbox("Filename cannot be empty for certs containing multiple domains.")
- return;
- }
-
- var ca = $("#ca").dropdown("get value");
- var caURL = "";
- if (ca == "Custom ACME Server") {
- ca = "custom";
- caURL = $("#caURL").val();
- }
- var skipTLSValue = $("#skipTLSCheckbox")[0].checked;
- $.ajax({
- url: "/api/acme/obtainCert",
- method: "GET",
- data: {
- domains: domains,
- filename: filename,
- email: email,
- ca: ca,
- caURL: caURL,
- skipTLS: skipTLSValue,
- },
- 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);
- } else {
- console.log("Certificate renewed successfully");
- // Show success message
- parent.msgbox("Certificate renewed successfully");
-
- // Renew the parent certificate list
- parent.initManagedDomainCertificateList();
- }
- },
- error: function(error) {
- $("#obtainButton").removeClass("loading").removeClass("disabled");
- console.log("Failed to renewed certificate:", error);
- }
- });
- }
- function checkIfInputDomainIsMultiple(){
- var inputDomains = $("#domainsInput").val();
- if (inputDomains.includes(",")){
- $(".multiDomainOnly").show();
- }else{
- $(".multiDomainOnly").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){
- $.ajax({
- url: "/api/acme/autoRenew/setDomains",
- 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'));
- });
- $.ajax({
- url: "/api/acme/autoRenew/setDomains",
- data: {opr: "setSelected", domains: JSON.stringify(checkedNames)},
- success: function(data){
- parent.msgbox("Renew policy rule updated")
- }
- });
- }
- }
- //Clear up the input field when page load
- $("#filenameInput").val("");
- </script>
- </body>
- </html>
|