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: {}
});