diff --git a/magnum_ui/api/rest/magnum.py b/magnum_ui/api/rest/magnum.py index d31cc972..d4a0ba00 100644 --- a/magnum_ui/api/rest/magnum.py +++ b/magnum_ui/api/rest/magnum.py @@ -126,8 +126,8 @@ class Clusters(generic.View): @urls.register -class Certificates(generic.View): - """API for Magnum Certificates""" +class Certificate(generic.View): + """API for retrieving a single certificate""" url_regex = r'container_infra/certificates/(?P[^/]+)$' @rest_utils.ajax() @@ -139,6 +139,12 @@ class Certificates(generic.View): ca = magnum.certificate_show(request, cluster_id) return ca.to_dict() + +@urls.register +class Certificates(generic.View): + """API for Magnum Certificates""" + url_regex = r'container_infra/certificates/$' + @rest_utils.ajax(data_required=True) def post(self, request): """Create a new Certificate. diff --git a/magnum_ui/static/dashboard/container-infra/clusters/actions.module.js b/magnum_ui/static/dashboard/container-infra/clusters/actions.module.js index 25eef828..6bd04153 100644 --- a/magnum_ui/static/dashboard/container-infra/clusters/actions.module.js +++ b/magnum_ui/static/dashboard/container-infra/clusters/actions.module.js @@ -31,6 +31,7 @@ 'horizon.dashboard.container-infra.clusters.create.service', 'horizon.dashboard.container-infra.clusters.delete.service', 'horizon.dashboard.container-infra.clusters.show-certificate.service', + 'horizon.dashboard.container-infra.clusters.sign-certificate.service', 'horizon.dashboard.container-infra.clusters.resourceType', ]; @@ -40,6 +41,7 @@ createClusterService, deleteClusterService, showCertificateService, + signCertificateService, resourceType) { var clusterResourceType = registry.getResourceType(resourceType); @@ -58,6 +60,13 @@ template: { text: gettext('Show Certificate') } + }) + .append({ + id: 'signCertificateAction', + service: signCertificateService, + template: { + text: gettext('Sign Certificate') + } }); clusterResourceType.batchActions diff --git a/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-modal.controller.js b/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-modal.controller.js new file mode 100644 index 00000000..7cd28266 --- /dev/null +++ b/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-modal.controller.js @@ -0,0 +1,68 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc controller + * @name horizon.dashboard.container-infra.clusters.signCertificateController + * @ngController + * + * @description + * Controller for the container-infra cluster in sign certificate modal + */ + angular + .module('horizon.dashboard.container-infra.clusters') + .controller('horizon.dashboard.container-infra.clusters.signCertificateController', signCertificateController); + + signCertificateController.$inject = [ + 'horizon.app.core.openstack-service-api.magnum', + 'horizon.dashboard.container-infra.clusters.sign-certificate-model' + ]; + + function signCertificateController(magnum, model) { + var ctrl = this; + ctrl.changeFile = changeFile; + ctrl.model = model; + ctrl.form = null; + magnum.getCluster(model.newCertificateSpec.cluster_uuid).success(onGetCluster); + + function onGetCluster(response) { + ctrl.model.cluster_name = response.name; + } + + function changeFile(files) { + // NOTE: this uses on-file-changed directive in Swift-UI included Horizon. + if (files.length) { + // load csr file and set into model + var reader = new FileReader(); + reader.readAsText(files[0]); + reader.onload = function(ev){ + model.newCertificateSpec.csr = reader.result; + ctrl.model.csrfile = files[0]; + ctrl.form.$setDirty(); + } + // Note that a $scope.$digest() is now needed for the change to the ngModel to be + // reflected in the page (since this callback is fired from inside a DOM event) + // but the on-file-changed directive currently does a digest after this callback + // is invoked. + } else { + model.newCertificateSpec.csr = ""; + ctrl.model.csrfile = null; + ctrl.form.$setPristine(); + } + } + } +})(); diff --git a/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-modal.html b/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-modal.html new file mode 100644 index 00000000..de7f3fd8 --- /dev/null +++ b/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-modal.html @@ -0,0 +1,39 @@ + + +
+ + + +
\ No newline at end of file diff --git a/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-model.js b/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-model.js new file mode 100644 index 00000000..435ca311 --- /dev/null +++ b/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-model.js @@ -0,0 +1,66 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function() { + 'use strict'; + + angular + .module('horizon.dashboard.container-infra.clusters') + .factory('horizon.dashboard.container-infra.clusters.sign-certificate-model', CertificateModel); + + CertificateModel.$inject = [ + 'horizon.app.core.openstack-service-api.magnum' + ]; + + function CertificateModel(magnum) { + var model = { + newClusterSpec: {}, + cluster_name: "", + csrfile: null, + + // API methods + init: init, + signCertificate: signCertificate + }; + + function init(cluster_id) { + // Reset the new Certificate spec + model.newCertificateSpec = { + cluster_uuid: cluster_id, + csr: "" + }; + model.cluster_name = ""; + model.csrfile = null; + } + + function signCertificate() { + var finalSpec = angular.copy(model.newCertificateSpec); + + cleanNullProperties(finalSpec); + + return magnum.signCertificate(finalSpec); + } + + function cleanNullProperties(finalSpec) { + // Initially clean fields that don't have any value. + for (var key in finalSpec) { + if (finalSpec.hasOwnProperty(key) && finalSpec[key] === null) { + delete finalSpec[key]; + } + } + } + + return model; + } +})(); diff --git a/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-workflow.service.js b/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-workflow.service.js new file mode 100644 index 00000000..a1175ae7 --- /dev/null +++ b/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate-workflow.service.js @@ -0,0 +1,58 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function() { + 'use strict'; + + angular + .module('horizon.dashboard.container-infra.clusters') + .controller('horizon.dashboard.container-infra.clusters.sign-certificate-modal', SignCertificateModal); + + SignCertificateModal.$inject = [ + '$modal', + 'horizon.app.core.workflow.factory', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.widgets.modal-wait-spinner.service', + 'horizon.framework.widgets.toast.service', + 'horizon.dashboard.container-infra.basePath' + ]; + + function SignCertificateModal(modal, gettext, spinner, toast, basePath) { + var ctrl = this; + + ctrl.model = { + cluster: model.cluster_id, + view_file: null, // file object managed by angular form ngModel + upload_file: null, // file object from the DOM element with the actual upload + DELIMETER: model.DELIMETER + }; + ctrl.form = null; // set by the HTML + ctrl.changeFile = changeFile; + + /////////// + + function changeFile(files) { + if (files.length) { + // update the upload file & its name + ctrl.model.upload_file = files[0]; + ctrl.model.name = files[0].name; + ctrl.form.name.$setDirty(); + + // Note that a $scope.$digest() is now needed for the change to the ngModel to be + // reflected in the page (since this callback is fired from inside a DOM event) + // but the on-file-changed directive currently does a digest after this callback + // is invoked. + } + } } +})(); diff --git a/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate.service.js b/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate.service.js new file mode 100644 index 00000000..b52c10dd --- /dev/null +++ b/magnum_ui/static/dashboard/container-infra/clusters/sign-certificate/sign-certificate.service.js @@ -0,0 +1,93 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @name horizon.dashboard.container-infra.clusters.sign-certificate.service + * @description Service for the container-infra cluster sign certificate modal + */ + angular + .module('horizon.dashboard.container-infra.clusters') + .factory('horizon.dashboard.container-infra.clusters.sign-certificate.service', signCertificateService); + + signCertificateService.$inject = [ + '$modal', + 'horizon.app.core.openstack-service-api.magnum', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.toast.service', + 'horizon.dashboard.container-infra.clusters.basePath', + 'horizon.dashboard.container-infra.clusters.resourceType', + 'horizon.dashboard.container-infra.clusters.sign-certificate-model' + ]; + + function signCertificateService( + $modal, magnum, actionResult, gettext, $qExtensions, toast, basePath, resourceType, model + ) { + + var message = { + success: gettext('Certificate %s was successfully signed.') + }; + + var service = { + initScope: initScope, + perform: perform, + allowed: allowed + }; + + return service; + + ////////////// + + function initScope($scope) { + } + + function signCertificateModal(html, $modal) { + var localSpec = { + backdrop: 'static', + controller: 'horizon.dashboard.container-infra.clusters.signCertificateController as ctrl', + templateUrl: html + }; + return $modal.open(localSpec).result; + } + + function perform(selected) { + model.init(selected.id); + return signCertificateModal(basePath + 'sign-certificate/sign-certificate-modal.html', $modal) + .then(submit); + } + + function allowed() { + return $qExtensions.booleanAsPromise(true); + } + + function submit(){ + return model.signCertificate().then(success); + } + + function success(response) { + magnum.downloadTextAsFile(response.data.pem, model.cluster_name + "_cert.pem"); + + response.data.id = response.data.uuid; + toast.add('success', interpolate(message.success, [response.data.id])); + var result = actionResult.getActionResult() + .created(resourceType, response.data.id); + return result.result; + } + } +})();