<div class="standardContainer"> <div class="ui basic segment"> <h2>TLS / SSL Certificates</h2> <p>Setup TLS cert for different domains of your reverse proxy server names</p> </div> <div class="ui divider"></div> <h4>Default Certificates</h4> <small>When there are no matching certificate for the requested server name, reverse proxy router will always fallback to this one.<br>Note that you need both of them uploaded for it to fallback properly</small></p> <table class="ui very basic unstackable celled table"> <thead> <tr><th class="no-sort">Key Type</th> <th class="no-sort">Exists</th> </tr></thead> <tbody> <tr> <td><i class="globe icon"></i> Default Public Key</td> <td id="pubkeyExists"></td> </tr> <tr> <td><i class="lock icon"></i> Default Private Key</td> <td id="prikeyExists"></td> </tr> </tbody> </table> <p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p> <div class="ui buttons"> <button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button> <button class="ui basic black button" onclick="uploadPrivateKey();"><i class="black lock icon"></i> Private Key</button> </div> <div class="ui divider"></div> <h4>Sub-domain Certificates</h4> <p>Provide certificates for multiple domains reverse proxy</p> <div class="ui fluid form"> <div class="three fields"> <div class="field"> <label>Server Name (Domain)</label> <input type="text" id="certdomain" placeholder="example.com / blog.example.com"> </div> <div class="field"> <label>Public Key (.pem)</label> <input type="file" id="pubkeySelector" onchange="handleFileSelect(event, 'pub')"> </div> <div class="field"> <label>Private Key (.key)</label> <input type="file" id="prikeySelector" onchange="handleFileSelect(event, 'pri')"> </div> </div> <button class="ui basic button" onclick="handleDomainUploadByKeypress();"><i class="ui teal upload icon"></i> Upload</button><br> <small>You have intermediate certificate? <a style="cursor:pointer;" onclick="showSideWrapper('snippet/intermediateCertConv.html');">Open Conversion Tool</a></small> </div> <div id="certUploadSuccMsg" class="ui green message" style="display:none;"> <i class="ui checkmark icon"></i> Certificate for domain <span id="certUploadingDomain"></span> uploaded. </div> <br> <div> <table class="ui sortable unstackable celled table"> <thead> <tr><th>Domain</th> <th>Last Update</th> <th>Expire At</th> <th class="no-sort">Remove</th> </tr></thead> <tbody id="certifiedDomainList"> </tbody> </table> <button class="ui basic button" onclick="initManagedDomainCertificateList();"><i class="green refresh icon"></i> Refresh List</button> <button class="ui basic button" onclick="openACMEManager();"><i class="yellow refresh icon"></i> Auto Renew (ACME) Settings</button> </div> <div class="ui message"> <h4><i class="info circle icon"></i> Sub-domain Certificates</h4> If you have 3rd or even 4th level subdomains like <code>blog.example.com</code> or <code>en.blog.example.com</code> , depending on your certificates coverage, you might need to setup them one by one (i.e. having two seperate certificate for <code>a.example.com</code> and <code>b.example.com</code>).<br> If you have a wildcard certificate that covers <code>*.example.com</code>, you can just enter <code>example.com</code> as server name in the form below to add a certificate. </div> </div> <script> var uploadPendingPublicKey = undefined; var uploadPendingPrivateKey = undefined; //Delete the certificate by its domain function deleteCertificate(domain){ if (confirm("Confirm delete certificate for " + domain + " ?")){ $.ajax({ url: "/api/cert/delete", method: "POST", data: {domain: domain}, success: function(data){ if (data.error != undefined){ msgbox(data.error, false, 5000); }else{ initManagedDomainCertificateList(); initDefaultKeypairCheck(); } } }); } } //List the stored certificates function initManagedDomainCertificateList(){ $.get("/api/cert/list?date=true", function(data){ if (data.error != undefined){ msgbox(data.error, false, 5000); }else{ $("#certifiedDomainList").html(""); data.sort((a,b) => { return a.Domain > b.Domain }); data.forEach(entry => { $("#certifiedDomainList").append(`<tr> <td>${entry.Domain}</td> <td>${entry.LastModifiedDate}</td> <td>${entry.ExpireDate} (${entry.RemainingDays} days left)</td> <td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td> </tr>`); }); if (data.length == 0){ $("#certifiedDomainList").append(`<tr> <td colspan="4"><i class="ui times circle icon"></i> No valid keypairs found</td> </tr>`); } } }) } initManagedDomainCertificateList(); function openACMEManager(){ showSideWrapper('snippet/acme.html'); } function handleDomainUploadByKeypress(){ handleDomainKeysUpload(function(){ $("#certUploadingDomain").text($("#certdomain").val().trim()); //After uploaded, reset the file selector document.getElementById('pubkeySelector').value = ''; document.getElementById('prikeySelector').value = ''; document.getElementById('certdomain').value = ''; uploadPendingPublicKey = undefined; uploadPendingPrivateKey = undefined; //Show succ $("#certUploadSuccMsg").stop().finish().slideDown("fast").delay(3000).slideUp("fast"); initManagedDomainCertificateList(); }); } //Handle domain keys upload function handleDomainKeysUpload(callback=undefined){ let domain = $("#certdomain").val(); if (domain.trim() == ""){ msgbox("Missing domain", false, 5000); return; } if (uploadPendingPublicKey && uploadPendingPrivateKey && typeof uploadPendingPublicKey === 'object' && typeof uploadPendingPrivateKey === 'object') { const publicKeyForm = new FormData(); publicKeyForm.append('file', uploadPendingPublicKey, 'publicKey'); const privateKeyForm = new FormData(); privateKeyForm.append('file', uploadPendingPrivateKey, 'privateKey'); const publicKeyRequest = new XMLHttpRequest(); publicKeyRequest.open('POST', '/api/cert/upload?ktype=pub&domain=' + domain); publicKeyRequest.onreadystatechange = function() { if (publicKeyRequest.readyState === XMLHttpRequest.DONE) { if (publicKeyRequest.status !== 200) { msgbox('Error uploading public key: ' + publicKeyRequest.statusText, false, 5000); } if (callback != undefined){ callback(); } } }; publicKeyRequest.send(publicKeyForm); const privateKeyRequest = new XMLHttpRequest(); privateKeyRequest.open('POST', '/api/cert/upload?ktype=pri&domain=' + domain); privateKeyRequest.onreadystatechange = function() { if (privateKeyRequest.readyState === XMLHttpRequest.DONE) { if (privateKeyRequest.status !== 200) { msgbox('Error uploading private key: ' + privateKeyRequest.statusText, false, 5000); } if (callback != undefined){ callback(); } } }; privateKeyRequest.send(privateKeyForm); } else { msgbox('One or both of the files is missing or not a file object'); } } //Handlers for selecting domain based key pairs //ktype = {"pub" / "pri"} function handleFileSelect(event, ktype="pub") { const file = event.target.files[0]; //const fileNameInput = document.getElementById('selected-file-name'); if (ktype == "pub"){ uploadPendingPublicKey = file; }else if (ktype == "pri"){ uploadPendingPrivateKey = file; } //fileNameInput.value = file.name; } //Check if the default keypairs exists function initDefaultKeypairCheck(){ $.get("/api/cert/checkDefault", function(data){ let tick = `<i class="ui green checkmark icon"></i>`; let cross = `<i class="ui red times icon"></i>`; $("#pubkeyExists").html(data.DefaultPubExists?tick:cross); $("#prikeyExists").html(data.DefaultPriExists?tick:cross); }); } initDefaultKeypairCheck(); function uploadPrivateKey(){ // create file input element const input = document.createElement('input'); input.type = 'file'; // add change listener to file input input.addEventListener('change', () => { // create form data object const formData = new FormData(); // add selected file to form data formData.append('file', input.files[0]); // send form data to server fetch('/api/cert/upload?ktype=pri', { method: 'POST', body: formData }) .then(response => { initDefaultKeypairCheck(); if (response.ok) { msgbox('File upload successful!'); } else { response.text().then(text => { msgbox(text, false, 5000); }); //console.log(response.text()); //alert('File upload failed!'); } }) .catch(error => { msgbox('An error occurred while uploading the file.', false, 5000); console.error(error); }); }); // click file input to open file selector input.click(); } function uploadPublicKey() { // create file input element const input = document.createElement('input'); input.type = 'file'; // add change listener to file input input.addEventListener('change', () => { // create form data object const formData = new FormData(); // add selected file to form data formData.append('file', input.files[0]); // send form data to server fetch('/api/cert/upload?ktype=pub', { method: 'POST', body: formData }) .then(response => { if (response.ok) { msgbox('File upload successful!'); initDefaultKeypairCheck(); } else { response.text().then(text => { msgbox(text, false, 5000); }); //console.log(response.text()); //alert('File upload failed!'); } }) .catch(error => { msgbox('An error occurred while uploading the file.', false, 5000); console.error(error); }); }); // click file input to open file selector input.click(); } </script>