diff --git a/client/WEB-INF/classes/resources/messages.properties b/client/WEB-INF/classes/resources/messages.properties index a0205e125c7..475fe6c95f1 100644 --- a/client/WEB-INF/classes/resources/messages.properties +++ b/client/WEB-INF/classes/resources/messages.properties @@ -313,6 +313,7 @@ label.add.firewall=Add firewall rule label.add.guest.network=Add guest network label.add.host=Add Host label.add.ingress.rule=Add Ingress Rule +label.add.intermediate.certificate=Add intermediate certificate label.add.ip.range=Add IP Range label.add.load.balancer=Add Load Balancer label.add.more=Add More @@ -431,7 +432,7 @@ label.cancel=Cancel label.capacity=Capacity label.capacity.bytes=Capacity Bytes label.capacity.iops=Capacity IOPS -label.certificate=Certificate +label.certificate=Server certificate label.change.service.offering=Change service offering label.change.value=Change value label.character=Character @@ -678,6 +679,7 @@ label.instance.limits=Instance Limits label.instance.name=Instance Name label.instance=Instance label.instances=Instances +label.intermediate.certificate=Intermediate certificate {0} label.internal.dns.1=Internal DNS 1 label.internal.dns.2=Internal DNS 2 label.internal.name=Internal name @@ -1020,6 +1022,7 @@ label.retry.interval=Retry Interval label.review=Review label.revoke.project.invite=Revoke invitation label.role=Role +label.root.certificate=Root certificate label.root.disk.controller=Root disk controller label.root.disk.offering=Root Disk Offering label.round.robin=Round-robin @@ -1820,7 +1823,7 @@ message.tooltip.reserved.system.netmask=The network prefix that defines the pod message.tooltip.zone.name=A name for the zone. message.update.os.preference=Please choose a OS preference for this host. All virtual instances with similar preferences will be first allocated to this host before choosing another. message.update.resource.count=Please confirm that you want to update resource counts for this account. -message.update.ssl=Please submit a new X.509 compliant SSL certificate to be updated to each console proxy and secondary storage virtual instance\: +message.update.ssl=Please submit a new X.509 compliant SSL certificate chain to be updated to each console proxy and secondary storage virtual instance\: message.validate.instance.name=Instance name can not be longer than 63 characters. Only ASCII letters a~z, A~Z, digits 0~9, hyphen are allowed. Must start with a letter and end with a letter or a digit. message.virtual.network.desc=A dedicated virtualized network for your account. The broadcast domain is contained within a VLAN and all public network access is routed out by a virtual router. message.vm.create.template.confirm=Create Template will reboot the VM automatically. diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 23681a76426..6e649f7a8e8 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -4053,7 +4053,7 @@ Dialogs*/ display: inline-block; } -.ui-dialog div.form-container div.value input { +.ui-dialog div.form-container div.value input, textarea { width: 98%; font-size: 14px; padding: 4px; @@ -12984,3 +12984,21 @@ div.gpugroups div.list-view { padding: 12px 12px 5px; } +.ui-dialog .ui-button.add { + background: linear-gradient(to bottom, #f7f7f7 1%, #eaeaea 100%) repeat scroll 0px 0px transparent; + font-size: 12px; + height: 12px; + width: auto; + margin: 0px 0px 12px; + padding: 5px 7px 5px 6px; +} + +.ui-dialog .ui-button.add:hover { + background: none repeat scroll 0px 0px #e5e5e5; + box-shadow: 0px 0px 5px #c3c3c3 inset; +} + +.ui-dialog .ui-button.add span { + background: url("../images/icons.png") no-repeat scroll -626px -209px transparent; + padding: 0 0 3px 18px; +} \ No newline at end of file diff --git a/ui/dictionary.jsp b/ui/dictionary.jsp index 10aeaf9c1bf..3b85b1f7320 100644 --- a/ui/dictionary.jsp +++ b/ui/dictionary.jsp @@ -1898,7 +1898,10 @@ dictionary = { 'message.confirm.enable.vpc.offering': '', 'message.enabling.vpc.offering': '', 'message.confirm.remove.vpc.offering': '', -'message.confirm.disable.vpc.offering': '' +'message.confirm.disable.vpc.offering': '', +'label.root.certificate': '', +'label.intermediate.certificate': '', +'label.add.intermediate.certificate': '' }; diff --git a/ui/scripts/ui-custom/physicalResources.js b/ui/scripts/ui-custom/physicalResources.js index ac379b462fe..110945e5fb8 100644 --- a/ui/scripts/ui-custom/physicalResources.js +++ b/ui/scripts/ui-custom/physicalResources.js @@ -96,72 +96,165 @@ form: { title: 'label.update.ssl', desc: 'message.update.ssl', + preFilter: function (args) { + var $form = args.$form; + + // insert the "Add intermediate certificate" button + var $addButton = $('
') + .addClass('add ui-button') + .append( + $('').html(_l('label.add.intermediate.certificate')) + ); + var $servercertificate = $form.find('.form-item[rel=certificate]'); + $addButton.insertBefore($servercertificate); + var count = 0; + var $intermediatecertificate = $form.find('.form-item[rel=intermediatecertificate]'); + + $addButton.click(function() { + // clone the template intermediate certificate and make it visible + var $newcertificate = $intermediatecertificate.clone().attr('id','intermediate'+count); + $newcertificate.insertBefore($addButton); + $newcertificate.css('display', 'inline-block'); + $newcertificate.addClass('sslcertificate'); + count++; + // change label + var $label = $newcertificate.find('label'); + $label.html($label.html().replace('{0}', count)); // 'Intermediate certificate ' + count + ':' + }); + }, fields: { + rootcertificate: { + label: 'label.root.certificate', + isTextarea: true, + validation: { required: true } + }, + intermediatecertificate: { // this is the template 'intermediate certificate', always hidden + label: 'label.intermediate.certificate', + isTextarea: true, + isHidden: true + }, certificate: { label: 'label.certificate', - isTextarea: true + isTextarea: true, + validation: { required: true } }, privatekey: { label: 'label.privatekey', - isTextarea: true + isTextarea: true, + validation: { required: true } }, domainsuffix: { - label: 'label.domain.suffix' + label: 'label.domain.suffix', + validation: { required: true } } } }, after: function(args) { var $loading = $('
').addClass('loading-overlay'); $('.system-dashboard-view:visible').prepend($loading); - $.ajax({ - type: "POST", - url: createURL('uploadCustomCertificate'), - data: { - certificate: args.data.certificate, - privatekey: args.data.privatekey, + + // build a list with all certificates that need to be uploaded + var certificates = []; + certificates.push(args.data.rootcertificate); + if ($.isArray(args.data.intermediatecertificate)) + { + $.merge(certificates, args.data.intermediatecertificate); + } + else + { + certificates.push(args.data.intermediatecertificate); + } + certificates.push(args.data.certificate); + + // Recursively uploads certificates. + // When the upload succeeds, proceeds to uploading the next certificate. + // When the upload fails, stops and reports failure. + var uploadCertificate = function(index) { + if (index >= certificates.length) + { + return; + } + if ( !$.trim(certificates[index])) // skip empty certificate + { + uploadCertificate(index + 1); + return; + } + + // build certificate data + var certificateData = { + id: index + 1, // id start from 1 + certificate: certificates[index], domainsuffix: args.data.domainsuffix - }, - dataType: 'json', - success: function(json) { - var jid = json.uploadcustomcertificateresponse.jobid; - var uploadCustomCertificateIntervalID = setInterval(function() { - $.ajax({ - url: createURL("queryAsyncJobResult&jobId=" + jid), - dataType: "json", - success: function(json) { - var result = json.queryasyncjobresultresponse; - if (result.jobstatus == 0) { - return; //Job has not completed - } else { - clearInterval(uploadCustomCertificateIntervalID); - if (result.jobstatus == 1) { - cloudStack.dialog.notice({ - message: 'Update SSL Certificate succeeded' - }); - } else if (result.jobstatus == 2) { - cloudStack.dialog.notice({ - message: 'Failed to update SSL Certificate. ' + _s(result.jobresult.errortext) - }); + }; + switch (index) { + case (0): //first certificate is the root certificate + certificateData['name'] = 'root'; + break; + case (certificates.length - 1): // last certificate is the server certificate + certificateData['privatekey'] = args.data.privatekey; + break; + default: // intermediate certificates + certificateData['name'] = 'intermediate' + index; + } + + $.ajax({ + type: "POST", + url: createURL('uploadCustomCertificate'), + data: certificateData, + dataType: 'json', + success: function(json) { + var jid = json.uploadcustomcertificateresponse.jobid; + var uploadCustomCertificateIntervalID = setInterval(function() { + $.ajax({ + url: createURL("queryAsyncJobResult&jobId=" + jid), + dataType: "json", + success: function(json) { + var result = json.queryasyncjobresultresponse; + if (result.jobstatus == 0) { + return; //Job has not completed + } else { + clearInterval(uploadCustomCertificateIntervalID); + if (result.jobstatus == 1) { + if (index == certificates.length - 1) // last one, report success + { + cloudStack.dialog.notice({ + message: 'Update SSL Certificates succeeded' + }); + $loading.remove(); + } + else // upload next certificate + { + uploadCertificate(index + 1); + } + } else if (result.jobstatus == 2) { + cloudStack.dialog.notice({ + message: 'Failed to update SSL Certificate. ' + _s(result.jobresult.errortext) + }); + $loading.remove(); + } } + }, + error: function(XMLHttpResponse) { + cloudStack.dialog.notice({ + message: 'Failed to update SSL Certificate. ' + parseXMLHttpResponse(XMLHttpResponse) + }); $loading.remove(); } - }, - error: function(XMLHttpResponse) { - cloudStack.dialog.notice({ - message: 'Failed to update SSL Certificate. ' + parseXMLHttpResponse(XMLHttpResponse) - }); - $loading.remove(); - } + }); + }, g_queryAsyncJobResultInterval); + }, + error: function(XMLHttpResponse) { + cloudStack.dialog.notice({ + message: 'Failed to update SSL Certificate. ' + parseXMLHttpResponse(XMLHttpResponse) }); - }, g_queryAsyncJobResultInterval); - }, - error: function(XMLHttpResponse) { - cloudStack.dialog.notice({ - message: 'Failed to update SSL Certificate. ' + parseXMLHttpResponse(XMLHttpResponse) - }); - $loading.remove(); - } - }); + $loading.remove(); + } + }); + return; + }; + + // start uploading the certificates + uploadCertificate(0); }, context: {} });