From 70d412bad8e39bbd56ad38f1d662cded9462a0cb Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 15 Feb 2017 13:15:04 +0000 Subject: [PATCH] Helion Service Manager Integration (view only) (#975) * WIP: HSM Support * Small improvements. Tidy ups. Lint fixes. Unit tests now run okay. * Many improvements and refinements * Added configure instance support. Refactor cerate instance dialog. Tidy ups. * Added upgrade support * Minor improvements * Allow only admins to see HSM endpoints * Tweaks * Fix broken redirect with a single HSM * Allow components to be shown or hidden * Don't show inline upgrade messages since they are distracting * Apply update to text and ensure write operations are turned off by default. * Updated icon * Tweaks + changes - Now rely on the base state fetch of instances and services. Leaf states will refer to these when applicable. The instance will still be updated on poll, covering both local and instances array entries. The instances array will also manually be updated on create, delete and reconfigure. - Instance and service arrays are now pre-sorted to avoid table sort flibble - Instances table state icons now match that of the instance detail icon (except their larger table located versions) - Handle create instance edge cases. Also improve error messages when creating a service. - Ensure secret params and empty string defaults are shown in the create instance param list - Show secret params as password input boxes - Always show the 'delete instance' option if not deleting/deleted. There's some edge cases where we can get stuck in 'creating'/interesting state - Changes some anchors to use ui-sref - Removed some comments + unrequired code * Small translation changes, comments + a catch around readAsText * Address Richard's review comments * Some extra fixes - Fixed alignment of first letter in instance state popover - Fixed HSM nav icon visibility for unregister - Make an info call for HSM service instances in user service instance list - Fixed a few Helion Service Manager references - For the HSM details screen show just 'Service Manager' for the name. This aligns how we handle the same breadcrumb/name in the CF endpoints pages (HCF/Clusters - HSM/Service Manager) - Fixed an issue where disconnected a HSM would not change the total upgrade number (if there are two registered) * Fix pause when deleting endpoint + table blibble on load - Don't wait for updated user service instance list on removal of endpoint (revert from previous commit) - Fixed lint error - Sort table without reverse (show cloud foundry first). This avoids having to hack st-table to sort by reverse 'type' then normal 'name' - Fix position of 'register +' link in register slideout when screen is thin * Fixed lint error and wizard nav bar button issue. Shortened nav bar HSM name --- .../widgets/file-drop/file-drop.directive.js | 49 +++ .../file-drop/prevent-drop.directive.js | 32 ++ .../widgets/file-read/file-read.directive.js | 32 ++ .../json-tree-view.directive.js | 74 ++++ .../json-tree-view/json-tree-view.html | 21 + .../json-tree-view/json-tree-view.scss | 33 ++ .../json-tree-view/recursion-helper.js | 50 +++ .../select-input/select-input.directive.js | 2 +- .../show-table-inline-message.directive.js | 8 + .../table-inline-message.directive.js | 28 +- .../table-inline-message.html | 3 +- framework/src/widgets/widgets.scss | 1 + .../scss/style/components/_detail_view.scss | 19 +- framework/theme/scss/style/index.scss | 1 + framework/theme/scss/style/utils.scss | 7 + .../scss/theme/components/_detail_view.scss | 3 +- framework/theme/svg/Service_manager.svg | 31 ++ oem/brands/hpe/oem_config.json | 1 + oem/config-defaults.json | 1 + .../serviceInstance/serviceInstance.api.js | 26 +- .../serviceInstance.api.spec.js | 4 +- src/app/event/event.service.js | 3 +- src/app/model/navigation/navigation.model.js | 21 +- .../serviceInstance/serviceInstance.model.js | 25 +- .../user/userServiceInstance.model.js | 12 +- src/app/view/application.html | 2 +- .../clusters/cluster/router.module.js | 2 + .../dashboard/endpoints-dashboard.html | 2 +- .../dashboard/endpoints-dashboard.module.js | 10 +- ...ints-dashboard-service-instance.service.js | 102 +++-- .../register/register-service-details.html | 6 + .../register/register-service-type.html | 17 + .../endpoints/register/register-service.scss | 9 +- .../register/register-service.service.js | 8 + src/app/view/navbar/navbar.scss | 31 ++ .../view/navbar/navigation/navigation.html | 7 +- .../view/util/filters/appStateIcon.filter.js | 3 + .../util/filters/smallToLargeIcon.filter.js | 26 ++ src/plugins/service-manager/api/HsmApi.js | 184 +++++++++ .../model/service-manager.model.js | 304 ++++++++++++++ src/plugins/service-manager/plugin.config.js | 13 + .../service-manager/service-manager.module.js | 90 +++++ .../service-manager/service-manager.scss | 356 +++++++++++++++++ src/plugins/service-manager/version.utils.js | 84 ++++ .../manage-instance-form.directive.js | 249 ++++++++++++ .../manage-instance/manage-instance-form.html | 136 +++++++ .../view/manage-instance/manage-instance.html | 3 + .../manage-instance.service.js | 115 ++++++ .../view/service/detail/detail.html | 90 +++++ .../view/service/detail/detail.module.js | 52 +++ .../instances/service-manager.instances.html | 72 ++++ .../service-manager.instances.module.js | 33 ++ .../services/service-manager.services.html | 68 ++++ .../service-manager.services.module.js | 33 ++ .../instance-detail/instance-components.html | 46 +++ .../instance-detail/instance-detail.html | 124 ++++++ .../instance-detail/instance-detail.module.js | 377 ++++++++++++++++++ .../instance-detail/instance-parameters.html | 43 ++ .../instance-detail/instance-scaling.html | 36 ++ .../instance-detail/instance-services.html | 37 ++ .../instance-detail/instance-versions.html | 29 ++ .../view/service/sdl-detail/sdl-detail.html | 28 ++ .../service/sdl-detail/sdl-detail.module.js | 137 +++++++ .../service-detail/service-detail.html | 113 ++++++ .../service-detail/service-detail.module.js | 98 +++++ .../view/service/service-manager.module.js | 91 +++++ .../manager/service-manager-tile.directive.js | 102 +++++ .../tiles/manager/service-manager-tile.html | 23 ++ .../view/tiles/service-manager-tiles.html | 34 ++ .../tiles/service-manager-tiles.module.js | 143 +++++++ .../service-manager/view/view.module.js | 90 +++++ 71 files changed, 4047 insertions(+), 98 deletions(-) create mode 100644 framework/src/widgets/file-drop/file-drop.directive.js create mode 100644 framework/src/widgets/file-drop/prevent-drop.directive.js create mode 100644 framework/src/widgets/file-read/file-read.directive.js create mode 100644 framework/src/widgets/json-tree-view/json-tree-view.directive.js create mode 100644 framework/src/widgets/json-tree-view/json-tree-view.html create mode 100644 framework/src/widgets/json-tree-view/json-tree-view.scss create mode 100644 framework/src/widgets/json-tree-view/recursion-helper.js create mode 100644 framework/theme/scss/style/utils.scss create mode 100644 framework/theme/svg/Service_manager.svg create mode 100644 src/plugins/cloud-foundry/view/util/filters/smallToLargeIcon.filter.js create mode 100644 src/plugins/service-manager/api/HsmApi.js create mode 100644 src/plugins/service-manager/model/service-manager.model.js create mode 100644 src/plugins/service-manager/plugin.config.js create mode 100644 src/plugins/service-manager/service-manager.module.js create mode 100644 src/plugins/service-manager/service-manager.scss create mode 100644 src/plugins/service-manager/version.utils.js create mode 100644 src/plugins/service-manager/view/manage-instance/manage-instance-form.directive.js create mode 100644 src/plugins/service-manager/view/manage-instance/manage-instance-form.html create mode 100644 src/plugins/service-manager/view/manage-instance/manage-instance.html create mode 100644 src/plugins/service-manager/view/manage-instance/manage-instance.service.js create mode 100644 src/plugins/service-manager/view/service/detail/detail.html create mode 100644 src/plugins/service-manager/view/service/detail/detail.module.js create mode 100644 src/plugins/service-manager/view/service/detail/instances/service-manager.instances.html create mode 100644 src/plugins/service-manager/view/service/detail/instances/service-manager.instances.module.js create mode 100644 src/plugins/service-manager/view/service/detail/services/service-manager.services.html create mode 100644 src/plugins/service-manager/view/service/detail/services/service-manager.services.module.js create mode 100644 src/plugins/service-manager/view/service/instance-detail/instance-components.html create mode 100644 src/plugins/service-manager/view/service/instance-detail/instance-detail.html create mode 100644 src/plugins/service-manager/view/service/instance-detail/instance-detail.module.js create mode 100644 src/plugins/service-manager/view/service/instance-detail/instance-parameters.html create mode 100644 src/plugins/service-manager/view/service/instance-detail/instance-scaling.html create mode 100644 src/plugins/service-manager/view/service/instance-detail/instance-services.html create mode 100644 src/plugins/service-manager/view/service/instance-detail/instance-versions.html create mode 100644 src/plugins/service-manager/view/service/sdl-detail/sdl-detail.html create mode 100644 src/plugins/service-manager/view/service/sdl-detail/sdl-detail.module.js create mode 100644 src/plugins/service-manager/view/service/service-detail/service-detail.html create mode 100644 src/plugins/service-manager/view/service/service-detail/service-detail.module.js create mode 100644 src/plugins/service-manager/view/service/service-manager.module.js create mode 100644 src/plugins/service-manager/view/tiles/manager/service-manager-tile.directive.js create mode 100644 src/plugins/service-manager/view/tiles/manager/service-manager-tile.html create mode 100644 src/plugins/service-manager/view/tiles/service-manager-tiles.html create mode 100644 src/plugins/service-manager/view/tiles/service-manager-tiles.module.js create mode 100644 src/plugins/service-manager/view/view.module.js diff --git a/framework/src/widgets/file-drop/file-drop.directive.js b/framework/src/widgets/file-drop/file-drop.directive.js new file mode 100644 index 0000000000..196a5d7fff --- /dev/null +++ b/framework/src/widgets/file-drop/file-drop.directive.js @@ -0,0 +1,49 @@ +(function () { + 'use strict'; + + angular + .module('helion.framework.widgets') + .directive('fileDrop', fileDrop); + + function fileDrop() { + var directive = { + link: link, + restrict: 'A', + scope: { + fileDrop: '=' + } + }; + return directive; + + function link(scope, element) { + + element[0].addEventListener('dragenter', function (evt) { + element.addClass('file-drop-active'); + evt.preventDefault(); + evt.stopPropagation(); + }); + + element[0].addEventListener('dragleave', function () { + element.removeClass('file-drop-active'); + }); + element[0].addEventListener('dragover', function (evt) { + evt.preventDefault(); + evt.stopPropagation(); + element.addClass('file-drop-active'); + }); + + element[0].addEventListener('drop', function (evt) { + evt.stopPropagation(); + evt.preventDefault(); + element.removeClass('file-drop-active'); + + var files = evt.dataTransfer.files; + if (files.length > 0) { + scope.$apply(function () { + scope.fileDrop = files[0]; + }); + } + }); + } + } +})(); diff --git a/framework/src/widgets/file-drop/prevent-drop.directive.js b/framework/src/widgets/file-drop/prevent-drop.directive.js new file mode 100644 index 0000000000..13991be4a1 --- /dev/null +++ b/framework/src/widgets/file-drop/prevent-drop.directive.js @@ -0,0 +1,32 @@ +(function () { + 'use strict'; + + angular + .module('helion.framework.widgets') + .directive('preventDrop', preventDrop); + + preventDrop.$inject = [ + '$window' + ]; + + function preventDrop($window) { + var directive = { + link: link, + restrict: 'A' + }; + return directive; + + function link() { + + $window.addEventListener('dragover', function (evt) { + evt.preventDefault(); + evt.stopPropagation(); + }); + + $window.addEventListener('drop', function (evt) { + evt.preventDefault(); + evt.stopPropagation(); + }); + } + } +})(); diff --git a/framework/src/widgets/file-read/file-read.directive.js b/framework/src/widgets/file-read/file-read.directive.js new file mode 100644 index 0000000000..a95cb5253f --- /dev/null +++ b/framework/src/widgets/file-read/file-read.directive.js @@ -0,0 +1,32 @@ +(function () { + 'use strict'; + + angular + .module('helion.framework.widgets') + .directive('fileread', fileRead); + + function fileRead() { + var directive = { + link: link, + restrict: 'A', + scope: { + fileread: '=' + } + }; + return directive; + + function link(scope, element) { + element.bind('change', function (changeEvent) { + scope.$apply(function () { + scope.fileread = changeEvent.target.files[0]; + }); + }); + + scope.$watch('fileread', function (nv, ov) { + if (ov && ov.name && nv && !nv.name) { + element.val(null); + } + }); + } + } +})(); diff --git a/framework/src/widgets/json-tree-view/json-tree-view.directive.js b/framework/src/widgets/json-tree-view/json-tree-view.directive.js new file mode 100644 index 0000000000..951b4366a3 --- /dev/null +++ b/framework/src/widgets/json-tree-view/json-tree-view.directive.js @@ -0,0 +1,74 @@ +(function () { + 'use strict'; + + angular + .module('helion.framework.widgets') + .directive('jsonTreeView', jsonTreeView); + + jsonTreeView.$inject = [ + 'helion.framework.basePath', + 'RecursionHelper' + ]; + + /** + * @name jsonTreeView + * @description A directive that displays JSON. + * @param {string} path - the framework base path + * @param {object} RecursionHelper - RecursionHelper + * @returns {object} The json-tree-view directive definition object + */ + function jsonTreeView(path, RecursionHelper) { + return { + bindToController: { + json: '=' + }, + controller: JsonTreeViewController, + controllerAs: 'jtvCtrl', + restrict: 'E', + scope: {}, + templateUrl: path + 'widgets/json-tree-view/json-tree-view.html', + compile: function (element) { + return RecursionHelper.compile(element); + } + }; + } + + JsonTreeViewController.$inject = ['$scope']; + + /** + * @namespace helion.framework.widgets.ActionsMenuController + * @memberof helion.framework.widgets + * @name ActionsMenuController + * @constructor + * @param {object} $scope - the angular $scope service + * @property {string} icon - the actions menu icon + * @property {boolean} position - the actions menu position + * @property {boolean} open - flag whether actions menu should be visible + * @property {boolean} buttonMode - do not show the drop down instead the single action as a button + */ + function JsonTreeViewController() { + this.array = _.isArray(this.json); + } + + angular.extend(JsonTreeViewController.prototype, { + + isArray: function () { + return _.isArray(this.json); + }, + + getTypeOf: function (value) { + if (_.isArray(value)) { + return 'array'; + } else if (_.isObject(value)) { + return 'object'; + } else if (_.isBoolean(value)) { + return 'bool'; + } else if (_.isNumber(value)) { + return 'number'; + } + + return 'string'; + } + }); + +})(); diff --git a/framework/src/widgets/json-tree-view/json-tree-view.html b/framework/src/widgets/json-tree-view/json-tree-view.html new file mode 100644 index 0000000000..a74370b799 --- /dev/null +++ b/framework/src/widgets/json-tree-view/json-tree-view.html @@ -0,0 +1,21 @@ +
+ {{key}} : +
+
+ { + +
}
+
+
+
+ [ + +
]
+
+ [] +
+ {{ value }} + {{ value }} + "{{ value }}" +
+
\ No newline at end of file diff --git a/framework/src/widgets/json-tree-view/json-tree-view.scss b/framework/src/widgets/json-tree-view/json-tree-view.scss new file mode 100644 index 0000000000..b7410fe531 --- /dev/null +++ b/framework/src/widgets/json-tree-view/json-tree-view.scss @@ -0,0 +1,33 @@ +json-tree-view { + font-size: 11pt; + font-family: "Source Code Pro", monospace; +} + +.json-view-item, .json-view-array, .json-view-object { + display: inline; + + json-tree-view { + display: block; + margin-left: 12px; + } +} + +.json-view-key { + color: #007B47; +} + +.json-view-string { + color: red; +} + +.json-view-number { + color: #0000DD; +} + +.json-view-bool { + color: #0000DD; +} + +.json-view-array-wtih-values { + display: inline; +} \ No newline at end of file diff --git a/framework/src/widgets/json-tree-view/recursion-helper.js b/framework/src/widgets/json-tree-view/recursion-helper.js new file mode 100644 index 0000000000..928b37a893 --- /dev/null +++ b/framework/src/widgets/json-tree-view/recursion-helper.js @@ -0,0 +1,50 @@ +(function () { + 'use strict'; + + angular + .module('helion.framework.widgets') + .factory('RecursionHelper', RecursionHelper); + + RecursionHelper.$inject = [ + '$compile' + ]; + + function RecursionHelper($compile) { + return { + /** + * Manually compiles the element, fixing the recursion loop. + * @param {object} element - Element + * @param {function} [link] - A post-link function, or an object with function(s) registered via pre and post properties. + * @returns {object} An object containing the linking functions. + */ + compile: function (element, link) { + // Normalize the link parameter + if (angular.isFunction(link)) { + link = { post: link }; + } + + // Break the recursion loop by removing the contents + var contents = element.contents().remove(); + var compiledContents; + return { + pre: link && link.pre ? link.pre : null, + post: function (scope, element) { + // Compile the contents + if (!compiledContents) { + compiledContents = $compile(contents); + } + // Re-add the compiled contents to the element + compiledContents(scope, function (clone) { + element.append(clone); + }); + + // Call the post-linking function, if any + if (link && link.post) { + link.post.apply(null, arguments); + } + } + }; + } + }; + } +})(); diff --git a/framework/src/widgets/select-input/select-input.directive.js b/framework/src/widgets/select-input/select-input.directive.js index 6bbf68ad5e..20113274df 100644 --- a/framework/src/widgets/select-input/select-input.directive.js +++ b/framework/src/widgets/select-input/select-input.directive.js @@ -191,7 +191,7 @@ setLabel: function (modelValue) { if (angular.isDefined(modelValue) && modelValue !== null) { var initialValue = _.find(this.inputOptions, {value: modelValue}); - this.modelLabel = initialValue.label; + this.modelLabel = initialValue ? initialValue.label : null; } else { this.modelLabel = null; } diff --git a/framework/src/widgets/show-table-inline-message/show-table-inline-message.directive.js b/framework/src/widgets/show-table-inline-message/show-table-inline-message.directive.js index 5ba93d058b..2bbf581e44 100644 --- a/framework/src/widgets/show-table-inline-message/show-table-inline-message.directive.js +++ b/framework/src/widgets/show-table-inline-message/show-table-inline-message.directive.js @@ -28,6 +28,7 @@ setMessage(attrs.showTableInlineMessage, true); setStatus(attrs.tableInlineStatus, true); setColSpan(attrs.inlineMessageColspan, true); + setLink(attrs.inlineMessageLink, true); element.after(elem); $compile(elem)(scope); @@ -36,6 +37,7 @@ attrs.$observe('showTableInlineMessage', setMessage); attrs.$observe('tableInlineStatus', setStatus); attrs.$observe('inlineMessageColspan', setColSpan); + attrs.$observe('inlineMessageLink', setLink); function setMessage(message, skipCompile) { if (message === elem.attr('message')) { @@ -69,6 +71,12 @@ } } + function setLink(newLink, skipCompile) { + elem.attr('link', newLink); + if (!skipCompile) { + $compile(elem)(scope); + } + } } } diff --git a/framework/src/widgets/table-inline-message/table-inline-message.directive.js b/framework/src/widgets/table-inline-message/table-inline-message.directive.js index e7f7b47cd6..02ce44673a 100644 --- a/framework/src/widgets/table-inline-message/table-inline-message.directive.js +++ b/framework/src/widgets/table-inline-message/table-inline-message.directive.js @@ -23,15 +23,37 @@ bindToController: { message: '@', status: '@', + link: '@?', colSpan: '@?' }, - controller: function () { - this.statusClass = 'hpe-popover-alert-' + (this.status ? this.status : 'warning'); - }, + controller: InlineMessageController, controllerAs: 'tableInlineMessageCtrl', scope: {}, templateUrl: path + 'widgets/table-inline-message/table-inline-message.html' }; } + InlineMessageController.$inject = [ + '$state' + ]; + + function InlineMessageController($state) { + + this.statusClass = 'hpe-popover-alert-' + (this.status ? this.status : 'warning'); + + this.openLink = function () { + var link = this.link; + var parts = link.split('/'); + var params = {}; + var state = parts[0]; + if (parts.length > 1) { + _.each(parts[1].split(','), function (p) { + var kv = p.split(':'); + params[kv[0]] = kv[1]; + }); + } + + $state.go(state, params); + }; + } })(); diff --git a/framework/src/widgets/table-inline-message/table-inline-message.html b/framework/src/widgets/table-inline-message/table-inline-message.html index b3931b06a7..dfc7898d61 100644 --- a/framework/src/widgets/table-inline-message/table-inline-message.html +++ b/framework/src/widgets/table-inline-message/table-inline-message.html @@ -2,7 +2,8 @@
- {{ tableInlineMessageCtrl.message }} + {{ tableInlineMessageCtrl.message }} + {{ tableInlineMessageCtrl.message }}
diff --git a/framework/src/widgets/widgets.scss b/framework/src/widgets/widgets.scss index 8d28604c99..ba7940ed4e 100644 --- a/framework/src/widgets/widgets.scss +++ b/framework/src/widgets/widgets.scss @@ -10,6 +10,7 @@ @import "flyout/flyout"; @import "gallery-card/gallery-card"; @import "global-spinner/global-spinner"; +@import "json-tree-view/json-tree-view"; @import "log-viewer/logs-viewer"; @import "paginator/paginator"; @import "password-reveal/password-reveal"; diff --git a/framework/theme/scss/style/components/_detail_view.scss b/framework/theme/scss/style/components/_detail_view.scss index 76f90f54a6..2e830b9bf2 100644 --- a/framework/theme/scss/style/components/_detail_view.scss +++ b/framework/theme/scss/style/components/_detail_view.scss @@ -22,6 +22,21 @@ $detail-view-margin: $hpe-unit-space * 4; } } } + &.detail-view-two-fields { + + > .modal-dialog { + width: $detail_view_two_fields_width; + left: 100%; + } + + &.fade.in { + .modal-dialog { + left: calc(100% - #{$detail_view_two_fields_width}); + transition: left 0.25s ease-out; + } + } + } + .detail-view-content { @@ -145,8 +160,8 @@ $detail-view-margin: $hpe-unit-space * 4; padding-top: $hpe-unit-space * 3 / 4; background: $white; - .btn:not(:last-child) { - margin-right: $hpe-unit-space / 2; + .btn { + margin-left: $hpe-unit-space / 2; } } } diff --git a/framework/theme/scss/style/index.scss b/framework/theme/scss/style/index.scss index f1976025a2..447b2f0667 100644 --- a/framework/theme/scss/style/index.scss +++ b/framework/theme/scss/style/index.scss @@ -3,4 +3,5 @@ @import "scaffolding"; @import "components/index"; @import "ie_fixes"; +@import "utils"; diff --git a/framework/theme/scss/style/utils.scss b/framework/theme/scss/style/utils.scss new file mode 100644 index 0000000000..b7cdf3849f --- /dev/null +++ b/framework/theme/scss/style/utils.scss @@ -0,0 +1,7 @@ +.first-letter-uppercase { + text-transform: lowercase; +} + +.first-letter-uppercase *:first-letter { + text-transform: uppercase; +} diff --git a/framework/theme/scss/theme/components/_detail_view.scss b/framework/theme/scss/theme/components/_detail_view.scss index ae0a890514..ef976fd647 100644 --- a/framework/theme/scss/theme/components/_detail_view.scss +++ b/framework/theme/scss/theme/components/_detail_view.scss @@ -1 +1,2 @@ -$detail_view_thin_width: $hpe-unit-space*10 + 324px \ No newline at end of file +$detail_view_thin_width: $hpe-unit-space * 10 + 324px; +$detail_view_two_fields_width: $hpe-unit-space * 8 + 648px; \ No newline at end of file diff --git a/framework/theme/svg/Service_manager.svg b/framework/theme/svg/Service_manager.svg new file mode 100644 index 0000000000..aa510593e1 --- /dev/null +++ b/framework/theme/svg/Service_manager.svg @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file diff --git a/oem/brands/hpe/oem_config.json b/oem/brands/hpe/oem_config.json index da123c121e..de98d34004 100644 --- a/oem/brands/hpe/oem_config.json +++ b/oem/brands/hpe/oem_config.json @@ -5,6 +5,7 @@ "PRODUCT_FAMILY_LOGO_HREF": "images/brand_product_family_logo.png", "CODE_ENGINE": "Helion Code Engine", "CLOUD_FOUNDRY": "Cloud Foundry", + "SERVICE_MANAGER": "Helion Service Manager", "TERMS_OF_USE_HREF": "http://docs.hpcloud.com/permalink/helion-openstack/3.0/eula", "PRIVACY_HREF": "https://www.hpe.com/us/en/legal/privacy.html" } \ No newline at end of file diff --git a/oem/config-defaults.json b/oem/config-defaults.json index 54ab98646d..9b5fb0e67b 100644 --- a/oem/config-defaults.json +++ b/oem/config-defaults.json @@ -12,6 +12,7 @@ "PRIVACY": "Privacy", "CODE_ENGINE": "Code Engine", "CLOUD_FOUNDRY": "Cloud Foundry", + "SERVICE_MANAGER": "Service Manager", "COMPANY_LOGO_HREF": "images/brand_company_logo.png", "PRODUCT_FAMILY_LOGO_HREF": "", "ABOUT_COMPANY_LOGO_HREF": "images/brand_company_logo.png" diff --git a/src/app/api/serviceInstance/serviceInstance.api.js b/src/app/api/serviceInstance/serviceInstance.api.js index 00eb44498f..4fe426a8e9 100644 --- a/src/app/api/serviceInstance/serviceInstance.api.js +++ b/src/app/api/serviceInstance/serviceInstance.api.js @@ -45,37 +45,19 @@ * @param {string} url - the service instance endpoint * @param {string} name - the service instance friendly name * @param {boolean} skipSslValidation - whether to skip SSL validation for this endpoint + * @param {string} serviceType - the type of the service instance to create * @returns {promise} A resolved/rejected promise * @public */ - create: function (url, name, skipSslValidation) { + create: function (url, name, skipSslValidation, serviceType) { var config = { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }; + serviceType = serviceType || 'hcf'; var data = this.$httpParamSerializer({ api_endpoint: url, cnsi_name: name, skip_ssl_validation: skipSslValidation }); - return this.$http.post('/pp/v1/register/hcf', data, config); - }, - - /** - * @function createHce - * @memberof app.api.serviceInstance.ServiceInstanceApi - * @description Create a HCE service instance - * @param {string} url - the service instance endpoint - * @param {string} name - the service instance friendly name - * @param {boolean} skipSslValidation - whether to skip SSL validation for this endpoint - * @returns {promise} A resolved/rejected promise - * @public - */ - createHce: function (url, name, skipSslValidation) { - var config = { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }; - var data = this.$httpParamSerializer({ api_endpoint: url, cnsi_name: name, skip_ssl_validation: skipSslValidation }); - return this.$http.post('/pp/v1/register/hce', data, config); + return this.$http.post('/pp/v1/register/' + serviceType, data, config); }, /** diff --git a/src/app/api/serviceInstance/serviceInstance.api.spec.js b/src/app/api/serviceInstance/serviceInstance.api.spec.js index cebd48a47f..f3536b2851 100644 --- a/src/app/api/serviceInstance/serviceInstance.api.spec.js +++ b/src/app/api/serviceInstance/serviceInstance.api.spec.js @@ -37,10 +37,10 @@ $httpBackend.flush(); }); - it('should send POST request for createHCE', function () { + it('should send POST request for create for HCE', function () { var data = { api_endpoint: 'url', cnsi_name: 'name' }; $httpBackend.expectPOST('/pp/v1/register/hce', $httpParamSerializer(data)).respond(200, ''); - serviceInstanceApi.createHce('url', 'name'); + serviceInstanceApi.create('url', 'name', undefined, 'hce'); $httpBackend.flush(); }); diff --git a/src/app/event/event.service.js b/src/app/event/event.service.js index 2131a66a79..7dca762102 100644 --- a/src/app/event/event.service.js +++ b/src/app/event/event.service.js @@ -19,7 +19,8 @@ MODAL_INTERACTION_END : 'MODAL_INTERACTION_END', APP_ERROR_NOTIFY : 'APP_ERROR_NOTIFY', APP_ERROR_CLEAR : 'APP_ERROR_CLEAR', - VCS_OAUTH_CANCELLED : 'vcs.OAUTH_CANCELLED' + VCS_OAUTH_CANCELLED : 'vcs.OAUTH_CANCELLED', + ENDPOINT_CONNECT_CHANGE : 'ENDPOINT_CONNECTION_STATUS_CHANGED' }; angular diff --git a/src/app/model/navigation/navigation.model.js b/src/app/model/navigation/navigation.model.js index 935b5fab17..a965d9c5c2 100644 --- a/src/app/model/navigation/navigation.model.js +++ b/src/app/model/navigation/navigation.model.js @@ -65,6 +65,7 @@ $rootScope.$on('$stateChangeSuccess', function (event, toState) { // eslint-disable-line angular/on-watch // Activate the correct menu entry or deactivate all menu entries if none match that.menu.currentState = _.get(toState, 'data.activeMenuState', ''); + // Scroll to the console-view's top after a state transition var consoleViewScrollPanel = angular.element(document).find('#console-view-scroll-panel'); if (consoleViewScrollPanel[0]) { @@ -75,6 +76,7 @@ } angular.extend(NavigationModel.prototype, { + /** * @function onLogin * @memberof app.model.NavigationModel @@ -125,6 +127,18 @@ Menu.prototype = []; angular.extend(Menu.prototype, { + + /** + * @function getMenuItem + * @memberof app.model.navigation.Menu + * @description Gets the menu item with the specified name + * @param {string} name - the name/ID of the menu item + * @returns {object} Menu item for the specified item + */ + getMenuItem: function (name) { + return _.find(this, {name: name}); + }, + /** * @function addMenuItem * @memberof app.model.navigation.Menu @@ -202,7 +216,12 @@ _.each(sorted, function (item) { that.push(item); }); - return this; + + item.svgIcon = item.icon.indexOf('svg://') === 0; + if (item.svgIcon) { + item.icon = '/svg/' + item.icon.substr(6); + } + return item; }, /** diff --git a/src/app/model/serviceInstance/serviceInstance.model.js b/src/app/model/serviceInstance/serviceInstance.model.js index a4051679bf..b44503cf31 100644 --- a/src/app/model/serviceInstance/serviceInstance.model.js +++ b/src/app/model/serviceInstance/serviceInstance.model.js @@ -49,34 +49,15 @@ create: function (type, url, name, skipSslValidation) { var that = this; var serviceInstanceApi = this.apiManager.retrieve('app.api.serviceInstance'); - var promise = type === 'hcf' - ? serviceInstanceApi.create(url, name, !!skipSslValidation) - : serviceInstanceApi.createHce(url, name, !!skipSslValidation); + type = type || 'hcf'; + + var promise = serviceInstanceApi.create(url, name, !!skipSslValidation, type); return promise.then(function (response) { that.serviceInstances.push(response.data); return response.data; }); }, - /** - * @function createHce - * @memberof app.model.serviceInstance.ServiceInstance - * @description Create an HCE service instance - * @param {string} url - the service instance API endpoint - * @param {string} name - the service instance friendly name - * @param {boolean} skipSslValidation - whether to skip SSL validation for this endpoint - * @returns {promise} A resolved/rejected promise - * @public - */ - createHce: function (url, name, skipSslValidation) { - var that = this; - var serviceInstanceApi = this.apiManager.retrieve('app.api.serviceInstance'); - return serviceInstanceApi.createHce(url, name, !!skipSslValidation) - .then(function (response) { - that.serviceInstances.push(response.data); - }); - }, - /** * @function disconnect * @memberof app.model.serviceInstance.ServiceInstance diff --git a/src/app/model/serviceInstance/user/userServiceInstance.model.js b/src/app/model/serviceInstance/user/userServiceInstance.model.js index 150cfb0eac..8251cefb59 100644 --- a/src/app/model/serviceInstance/user/userServiceInstance.model.js +++ b/src/app/model/serviceInstance/user/userServiceInstance.model.js @@ -97,6 +97,8 @@ }); }, + /* eslint-disable complexity */ + // NOTE - Complexity of 11, left in to improve readability. /** * @function list * @memberof app.model.serviceInstance.user.UserServiceInstance @@ -109,6 +111,7 @@ var serviceInstanceApi = this.apiManager.retrieve('app.api.serviceInstance.user'); var cfInfoApi = this.apiManager.retrieve('cloud-foundry.api.Info'); var hceInfoApi = this.apiManager.retrieve('cloud-foundry.api.HceInfoApi'); + var hsmApi = this.apiManager.retrieve('service-manager.api.HsmApi'); return serviceInstanceApi.list().then(function (response) { var items = response.data; @@ -116,17 +119,21 @@ var hcfGuids = _.map(_.filter(items, {cnsi_type: 'hcf'}) || [], 'guid') || []; var hcfCfg = {headers: {'x-cnap-cnsi-list': hcfGuids.join(',')}}; var hceGuids = _.map(_.filter(items, {cnsi_type: 'hce'}) || [], 'guid') || []; + var hsmGuids = _.map(_.filter(items, {cnsi_type: 'hsm'}) || [], 'guid') || []; var tasks = []; - // call /v2/info to refresh tokens, then list + // make a request on each service type to refresh the oauth token if (hcfGuids.length > 0) { tasks.push(cfInfoApi.GetInfo({}, hcfCfg).then(function (response) { - return response.data ? response.data : {}; + return response.data || {}; })); } if (hceGuids.length > 0) { tasks.push(hceInfoApi.info(hceGuids.join(','))); } + if (hsmGuids.length > 0) { + tasks.push(hsmApi.info(hsmGuids.join(','))); + } if (tasks.length === 0) { that.onList(response); @@ -146,6 +153,7 @@ }); }, + /* eslint-enable complexity */ /** * @function onConnect diff --git a/src/app/view/application.html b/src/app/view/application.html index ec60b852e7..80a4275773 100644 --- a/src/app/view/application.html +++ b/src/app/view/application.html @@ -1,7 +1,7 @@ - + diff --git a/src/app/view/endpoints/clusters/cluster/router.module.js b/src/app/view/endpoints/clusters/cluster/router.module.js index c3180e8b6c..dbb7f1c29c 100644 --- a/src/app/view/endpoints/clusters/cluster/router.module.js +++ b/src/app/view/endpoints/clusters/cluster/router.module.js @@ -10,6 +10,8 @@ ]; function registerRoute($stateProvider) { + + // Cloud Foundry $stateProvider.state('endpoint.clusters.router', { url: '', template: '', diff --git a/src/app/view/endpoints/dashboard/endpoints-dashboard.html b/src/app/view/endpoints/dashboard/endpoints-dashboard.html index 03b130b6e9..8b5205f704 100644 --- a/src/app/view/endpoints/dashboard/endpoints-dashboard.html +++ b/src/app/view/endpoints/dashboard/endpoints-dashboard.html @@ -66,7 +66,7 @@ Name Connection - Type + Type Address diff --git a/src/app/view/endpoints/dashboard/endpoints-dashboard.module.js b/src/app/view/endpoints/dashboard/endpoints-dashboard.module.js index c49205c5c1..b881627755 100644 --- a/src/app/view/endpoints/dashboard/endpoints-dashboard.module.js +++ b/src/app/view/endpoints/dashboard/endpoints-dashboard.module.js @@ -180,15 +180,9 @@ callForAllProviders('createEndpointEntries'); _updateWelcomeMessage(); - // Pre-sort the array by reverse type to avoid initial smart-table flicker + // Pre-sort the array to avoid initial smart-table flicker that.endpoints.sort(function (e1, e2) { - if (e1.type < e2.type) { - return 1; - } - if (e1.type > e2.type) { - return -1; - } - return 0; + return e1.type !== e2.type ? e1.type.localeCompare(e2.type) : e1.name.localeCompare(e2.name); }); that.initialised = true; diff --git a/src/app/view/endpoints/dashboard/services/endpoints-dashboard-service-instance.service.js b/src/app/view/endpoints/dashboard/services/endpoints-dashboard-service-instance.service.js index cbbfcf520d..b30532d197 100644 --- a/src/app/view/endpoints/dashboard/services/endpoints-dashboard-service-instance.service.js +++ b/src/app/view/endpoints/dashboard/services/endpoints-dashboard-service-instance.service.js @@ -16,7 +16,8 @@ 'app.error.errorService', 'app.view.notificationsService', 'app.view.credentialsDialog', - 'helion.framework.widgets.dialog.confirm' + 'helion.framework.widgets.dialog.confirm', + 'app.event.eventService' ]; /** @@ -35,10 +36,11 @@ * @param {app.view.notificationsService} notificationsService - the toast notification service * @param {app.view.credentialsDialog} credentialsDialog - the credentials dialog service * @param {helion.framework.widgets.dialog.confirm} confirmDialog - the confirmation dialog service + * @param {app.event.eventService} eventService - the event service * @returns {object} the service instance service */ function cnsiServiceFactory($q, $state, $interpolate, modelManager, dashboardService, vcsService, utilsService, errorService, - notificationsService, credentialsDialog, confirmDialog) { + notificationsService, credentialsDialog, confirmDialog, eventService) { var that = this; var endpointPrefix = 'cnsi_'; @@ -95,6 +97,24 @@ }); } + function _setEndpointVisit(isValid, serviceInstance, endpoint) { + // Some service types have more detail available + if (isValid) { + switch (serviceInstance.cnsi_type) { + case 'hcf': + endpoint.visit = function () { + return $state.href('endpoint.clusters.cluster.detail.organizations', {guid: serviceInstance.guid}); + }; + break; + case 'hsm': + endpoint.visit = function () { + return $state.href('sm.endpoint.detail.instances', {guid: serviceInstance.guid}); + }; + break; + } + } + } + /** * @function createEndpointEntries * @memberOf app.view.endpoints.dashboard.cnsiService @@ -106,6 +126,7 @@ var activeEndpointsKeys = []; var serviceInstanceModel = modelManager.retrieve('app.model.serviceInstance'); var userServiceInstanceModel = modelManager.retrieve('app.model.serviceInstance.user'); + var userAccount = modelManager.retrieve('app.model.account'); var endpoints = dashboardService.endpoints; // Create the generic 'endpoint' object used to populate the dashboard table _.forEach(serviceInstanceModel.serviceInstances, function (serviceInstance) { @@ -121,21 +142,37 @@ var eKey = endpointPrefix + serviceInstance.guid; var endpoint = _.find(endpoints, function (e) { return e.key === eKey; }); var reuse = !!endpoint; + var hide = false; if (!reuse) { endpoint = { - key: eKey, - type: serviceInstance.cnsi_type === 'hcf' ? utilsService.getOemConfiguration().CLOUD_FOUNDRY : utilsService.getOemConfiguration().CODE_ENGINE + key: eKey }; - endpoints.push(endpoint); + switch (serviceInstance.cnsi_type) { + case 'hcf': + endpoint.type = utilsService.getOemConfiguration().CLOUD_FOUNDRY; + break; + case 'hce': + endpoint.type = utilsService.getOemConfiguration().CODE_ENGINE; + break; + case 'hsm': + endpoint.type = utilsService.getOemConfiguration().SERVICE_MANAGER; + // Only Console admins can see HSM endpoints + hide = !userAccount.isAdmin(); + break; + default: + endpoint.type = gettext('Unknown'); + } + if (!hide) { + endpoints.push(endpoint); + } } else { delete endpoint.error; } activeEndpointsKeys.push(endpoint.key); endpoint.actions = _createInstanceActions(isValid, hasExpired); - endpoint.visit = isValid && serviceInstance.cnsi_type === 'hcf' ? function () { - return $state.href('endpoint.clusters.cluster.detail.organizations', {guid: serviceInstance.guid}); - } : undefined; + endpoint.visit = undefined; + _setEndpointVisit(isValid, serviceInstance, endpoint); endpoint.url = utilsService.getClusterEndpoint(serviceInstance); endpoint.actionsTarget = serviceInstance; endpoint.name = serviceInstance.name; @@ -249,25 +286,31 @@ no: gettext('Cancel') }, callback: function () { - var serviceInstanceModel = modelManager.retrieve('app.model.serviceInstance'); - serviceInstanceModel.remove(serviceInstance).then(function () { - notificationsService.notify('success', gettext('Successfully unregistered endpoint \'{{name}}\''), { - name: serviceInstance.name - }); - updateInstances().then(function () { - createEndpointEntries(); - switch (serviceInstance.cnsi_type) { - case 'hcf': - authModel.remove(serviceInstance.guid); - break; - case 'hce': - dashboardService.refreshCodeEngineVcses().then(function () { - vcsService.createEndpointEntries(); - }); - break; - } + modelManager.retrieve('app.model.serviceInstance').remove(serviceInstance) + .then(function () { + notificationsService.notify('success', gettext('Successfully unregistered endpoint \'{{name}}\''), { + name: serviceInstance.name + }); + updateInstances().then(function () { + createEndpointEntries(); + switch (serviceInstance.cnsi_type) { + case 'hcf': + authModel.remove(serviceInstance.guid); + break; + case 'hce': + dashboardService.refreshCodeEngineVcses().then(function () { + vcsService.createEndpointEntries(); + }); + break; + } + }); + }) + .then(function () { + // Ensure that the user service instance list is updated before sending change notification + return modelManager.retrieve('app.model.serviceInstance.user').list().then(function () { + eventService.$emit(eventService.events.ENDPOINT_CONNECT_CHANGE, true); + }); }); - }); } }); } @@ -301,6 +344,7 @@ }); break; } + eventService.$emit(eventService.events.ENDPOINT_CONNECT_CHANGE, true); }); } }); @@ -334,6 +378,12 @@ }); break; } + }) + .then(function () { + // Ensure that the user service instance list is updated before sending change notification + return modelManager.retrieve('app.model.serviceInstance.user').list().then(function () { + eventService.$emit(eventService.events.ENDPOINT_CONNECT_CHANGE, true); + }); }); } } diff --git a/src/app/view/endpoints/register/register-service-details.html b/src/app/view/endpoints/register/register-service-details.html index 6ea144d14c..cbb2b562a6 100644 --- a/src/app/view/endpoints/register/register-service-details.html +++ b/src/app/view/endpoints/register/register-service-details.html @@ -18,6 +18,12 @@

When registering, choose a unique, recognizable name so that your developers can easily know which {{ OEM_CONFIG.CODE_ENGINE }} endpoint they are working with.

+
+

{{ OEM_CONFIG.SERVICE_MANAGER }} supports the full lifecycle for deploying, managing and upgrading services deployed in the Control Plane.

+

Register an existing {{ OEM_CONFIG.SERVICE_MANAGER }} to allow Console administrators to view services.

+

When registering, choose a unique, recognizable name so that your developers can easily + know which {{ OEM_CONFIG.SERVICE_MANAGER }} endpoint they are working with.

+

{{step.title}}

diff --git a/src/app/view/endpoints/register/register-service-type.html b/src/app/view/endpoints/register/register-service-type.html index df3f96fec9..c5384d4df7 100644 --- a/src/app/view/endpoints/register/register-service-type.html +++ b/src/app/view/endpoints/register/register-service-type.html @@ -43,6 +43,23 @@

{{ OEM_CONFIG.CODE_ENGINE }}

+
  • +
    +
    + +
    +
    +
    +

    {{ OEM_CONFIG.SERVICE_MANAGER }}

    +

    {{ OEM_CONFIG.SERVICE_MANAGER }} allows you to view the services deployed in the Control Plane

    +
    + +
    +
    +
  • diff --git a/src/app/view/endpoints/register/register-service.scss b/src/app/view/endpoints/register/register-service.scss index 32fc5152c4..19e969ae77 100644 --- a/src/app/view/endpoints/register/register-service.scss +++ b/src/app/view/endpoints/register/register-service.scss @@ -48,9 +48,12 @@ } } } - - .media-body { - width: 0; + &.register-type-hsm { + svg { + width: 72px; + .st0 {fill:#000} + .st1 {stroke: #000} + } } .media-right { diff --git a/src/app/view/endpoints/register/register-service.service.js b/src/app/view/endpoints/register/register-service.service.js index 4b078a5888..02752fb27e 100644 --- a/src/app/view/endpoints/register/register-service.service.js +++ b/src/app/view/endpoints/register/register-service.service.js @@ -79,6 +79,14 @@ step.nameOfUrlInput = 'hceUrl'; step.urlHint = $interpolate(gettext('{{ endpoint }} endpoint'))(scope); break; + case 'hsm': + scope.endpoint = utilsService.getOemConfiguration().SERVICE_MANAGER; + step.product = utilsService.getOemConfiguration().SERVICE_MANAGER; + step.title = $interpolate(gettext('Register a {{ endpoint }} Endpoint'))(scope); + step.nameOfNameInput = 'hsmName'; + step.nameOfUrlInput = 'hsmUrl'; + step.urlHint = $interpolate(gettext('{{ endpoint }} endpoint'))(scope); + break; default: step.product = gettext('Endpoint'); step.title = gettext('Register Endpoint'); diff --git a/src/app/view/navbar/navbar.scss b/src/app/view/navbar/navbar.scss index 86253a9757..2bfa59dc13 100644 --- a/src/app/view/navbar/navbar.scss +++ b/src/app/view/navbar/navbar.scss @@ -148,6 +148,14 @@ navbar > .main-nav-menu { font-size: 26px; } } + + > svg { + width: 32px; + vertical-align: middle; + + .st0 {fill:#FFF} + .st1 {stroke: #FFF} + } } .navitem-label { @@ -156,6 +164,29 @@ navbar > .main-nav-menu { letter-spacing: 1px; } } + + .menu-item-badge { + position: absolute; + left: 32px; + margin: 6px; + width: 24px; + height: 24px; + background-color: #00b0d7; + color: #fff; + text-align: center; + line-height: 24px; + border-radius: 50%; + font-size: 14px; + font-weight: 700; + pointer-events: none; + padding: 0; + opacity: 0; + transition: opacity 0.6s linear; + + &.badge-active { + opacity: 1; + } + } } } diff --git a/src/app/view/navbar/navigation/navigation.html b/src/app/view/navbar/navigation/navigation.html index 36f8b528a9..198a4ba95c 100644 --- a/src/app/view/navbar/navigation/navigation.html +++ b/src/app/view/navbar/navigation/navigation.html @@ -1,13 +1,18 @@ diff --git a/src/plugins/cloud-foundry/view/util/filters/appStateIcon.filter.js b/src/plugins/cloud-foundry/view/util/filters/appStateIcon.filter.js index 1d64241e0a..69943635ba 100644 --- a/src/plugins/cloud-foundry/view/util/filters/appStateIcon.filter.js +++ b/src/plugins/cloud-foundry/view/util/filters/appStateIcon.filter.js @@ -39,6 +39,9 @@ case 'error': icon = 'helion-icon-Critical_S text-danger'; break; + case 'deleted': + icon = 'helion-icon-Trash text-success'; + break; default: icon = ''; } diff --git a/src/plugins/cloud-foundry/view/util/filters/smallToLargeIcon.filter.js b/src/plugins/cloud-foundry/view/util/filters/smallToLargeIcon.filter.js new file mode 100644 index 0000000000..4e76dd97d5 --- /dev/null +++ b/src/plugins/cloud-foundry/view/util/filters/smallToLargeIcon.filter.js @@ -0,0 +1,26 @@ +(function () { + 'use strict'; + + angular + .module('app.view') + .filter('smallToLargeIcon', appStateIcon); + + /** + * @namespace app.view.smallToLargeIcon + * @memberof app.view + * @name smallToLargeIcon + * @description converts helion-icon-_S to helion-icon-_L + * @returns {function} The filter function + */ + function appStateIcon() { + return function (input) { + if (_.isNil(input)) { + return ''; + } + + return input.replace('_S', '_L'); + + }; + } + +})(); diff --git a/src/plugins/service-manager/api/HsmApi.js b/src/plugins/service-manager/api/HsmApi.js new file mode 100644 index 0000000000..a31dda56f8 --- /dev/null +++ b/src/plugins/service-manager/api/HsmApi.js @@ -0,0 +1,184 @@ + +(function () { + 'use strict'; + + //https://github.com/hpcloud/hsm/blob/develop/docs/swagger-spec/api.yml + + angular + .module('service-manager.api', []) + .run(registerApi); + + registerApi.$inject = [ + '$http', + 'app.api.apiManager' + ]; + + function registerApi($http, apiManager) { + apiManager.register('service-manager.api.HsmApi', new HsmApi($http)); + } + + /** + * @constructor + * @name HsmApi + * @description HSM API + * @param {object} $http - the Angular $http service + * @property {object} $http - the Angular $http service + * @property {string} baseUrl - the API base URL + */ + function HsmApi($http) { + this.$http = $http; + this.baseUrl = '/pp/v1/proxy/v1/'; + } + + angular.extend(HsmApi.prototype, { + + _get: function (path, guid, httpConfigOptions) { + return this._request('GET', path, guid, undefined, httpConfigOptions); + }, + + _post: function (path, guid, data, httpConfigOptions) { + return this._request('POST', path, guid, data, httpConfigOptions); + }, + + _request: function (method, path, guid, data, httpConfigOptions) { + var url = this.baseUrl + path; + var headers = { + 'x-cnap-cnsi-list': guid, + 'x-cnap-passthrough': 'true' + }; + + var config = { + method: method, + data: data, + url: url, + params: {}, + headers: headers + }; + + angular.forEach(httpConfigOptions, function (optionConfig, option) { + if (option === 'headers') { + angular.extend(config[option], optionConfig); + } else { + config[option] = optionConfig; + } + }); + + return this.$http(config).then(function (response) { + return response.data; + }); + }, + + getTemplate: function (guid, url) { + var i = url.indexOf('/v1/'); + if (i > 0) { + url = url.substr(i + 4); + } + return this._get(url, guid); + }, + + /** + * @name instances + * @description Get the instances for the HSM instance + * @param {string} guid - the HCE GUID + * @param {object} httpConfigOptions - additional config options + * @returns {promise} A resolved/rejected promise + */ + instances: function (guid, httpConfigOptions) { + return this._get('instances', guid, httpConfigOptions); + }, + + /** + * @name instance + * @description Get the instances for the HSM instance + * @param {string} guid - the HCE GUID + * @param {string} id - the id of the instance + * @param {object} httpConfigOptions - additional config options + * @returns {promise} A resolved/rejected promise + */ + instance: function (guid, id, httpConfigOptions) { + return this._get('instances/' + id, guid, httpConfigOptions); + }, + + /** + * @name services + * @description Get the services for the HSM instance + * @param {string} guid - the HCE instance GUID + * @param {object} httpConfigOptions - additional config options + * @returns {promise} A resolved/rejected promise + */ + services: function (guid, httpConfigOptions) { + return this._get('services', guid, httpConfigOptions); + }, + + /** + * @name service + * @description Get the services for the HSM instance + * @param {string} guid - the HCE instance GUID + * @param {string} id - the id of the service to get + * @param {object} httpConfigOptions - additional config options + * @returns {promise} A resolved/rejected promise + */ + service: function (guid, id, httpConfigOptions) { + return this._get('services/' + id, guid, httpConfigOptions); + }, + + serviceSdl: function (guid, id, productVersion, sdlVersion, httpConfigOptions) { + return this._get('services/' + id + '/product_versions/' + productVersion + '/sdl_versions/' + sdlVersion, guid, httpConfigOptions); + }, + + serviceProduct: function (guid, id, productVersion, httpConfigOptions) { + return this._get('services/' + id + '/product_versions/' + productVersion, guid, httpConfigOptions); + }, + + createInstance: function (guid, id, productVersion, sdlVersion, instanceId, params, httpConfigOptions) { + var instanceRequest = { + service_id: id, + product_version: productVersion, + sdl_version: sdlVersion, + parameters: [] + }; + + if (instanceId) { + instanceRequest.instance_id = instanceId; + } + + _.each(params, function (value, key) { + instanceRequest.parameters.push({ + name: key, + value: value + }); + }); + return this._post('instances', guid, instanceRequest, httpConfigOptions); + }, + + deleteInstance: function (guid, id, httpConfigOptions) { + return this._request('DELETE', 'instances/' + id, guid, undefined, httpConfigOptions); + }, + + // Note this is used for both upgrade and configure + configureInstance: function (guid, instance, params, httpConfigOptions) { + var instanceRequest = { + instance_id: instance.instance_id, + service_id: instance.service_id, + vendor: instance.vendor, + product_version: instance.product_version, + sdl_version: instance.sdl_version, + parameters: [] + }; + + _.each(params, function (value, key) { + instanceRequest.parameters.push({ + name: key, + value: value + }); + }); + + return this._request('PUT', 'instances/' + instance.instance_id, guid, instanceRequest, httpConfigOptions); + }, + + info: function (guids, httpConfigOptions) { + return this._get('info', guids, _.set(httpConfigOptions || {}, 'headers.x-cnap-passthrough', 'false')); + } + + }); +})(); diff --git a/src/plugins/service-manager/model/service-manager.model.js b/src/plugins/service-manager/model/service-manager.model.js new file mode 100644 index 0000000000..972a68ddc1 --- /dev/null +++ b/src/plugins/service-manager/model/service-manager.model.js @@ -0,0 +1,304 @@ +(function () { + 'use strict'; + + /** + * @namespace service-manager.model + * @name service-manager.model + * @description Helion Service Manager model + */ + angular + .module('service-manager.model', []) + .run(registerServiceManagerModel); + + registerServiceManagerModel.$inject = [ + '$q', + '$timeout', + 'app.model.modelManager', + 'app.api.apiManager', + 'app.event.eventService' + ]; + + function registerServiceManagerModel($q, $timeout, modelManager, apiManager, eventService) { + modelManager.register('service-manager.model', new ServiceManagerModel($q, $timeout, apiManager, modelManager, + eventService)); + } + + /** + * @memberof cloud-foundry.model.hce + * @name ServiceManagerModel + * @param {object} $q - the Angular $q service + * @param {object} $timeout - the Angular $timeout service + * @param {app.api.apiManager} apiManager - the application API manager + * @param {app.model.modelManager} modelManager - the Model management service + * @param {app.event.eventService} eventService - the application event service + * @property {object} $q - the Angular $q service + * @property {app.model.modelManager} modelManager - the Model management service + * @property {app.api.apiManager} apiManager - the application API manager + * @property {app.event.eventService} eventService - the application event service + * @property {object} data - the Helion Code Engine data + * @class + */ + function ServiceManagerModel($q, $timeout, apiManager, modelManager, eventService) { + this.$q = $q; + this.$timeout = $timeout; + this.apiManager = apiManager; + this.eventService = eventService; + this.modelManager = modelManager; + + this.hsmApi = this.apiManager.retrieve('service-manager.api.HsmApi'); + + // Instances that have upgrades available + this.upgrades = {}; + + // Upgrades to ignore + this.ignoreUpgrades = {}; + + // Model - key for each HSM Service (Endpoint guid) + this.model = {}; + + // Last loaded instance + this.instance = {}; + } + + angular.extend(ServiceManagerModel.prototype, { + + // Can the user perform destructive/write operations and/or create new instances in HSM? + // Implemented as method here so we can base this off of the user in the future + canWrite: function () { + return false; + }, + + instanceStateIndicator: function (instanceState) { + switch (instanceState) { + case 'running': + return 'ok'; + case 'creating': + return 'busy'; + case 'deleting': + return 'busy'; + case 'modifying': + return 'busy'; + case 'deleted': + return 'deleted'; + case 'degraded': + return 'warning'; + case 'failed': + return 'error'; + case '404': + return 'error'; + } + + return 'tentative'; + }, + + getInstance: function (guid, id) { + var that = this; + return this.hsmApi.instance(guid, id).then(function (data) { + that.instance = data; + // Also update the root instances collection + var instances = _.get(that, 'model[' + guid + '].instances'); + if (instances) { + for (var i = 0; i < instances.length; i++) { + if (instances[i].instance_id === data.instance_id) { + instances[i] = data; + break; + } + } + } + return data; + }); + }, + + getInstances: function (guid, noCache, noFetch) { + var that = this; + var loadPromise; + + if (noFetch && this.model[guid] && this.model[guid].instances) { + loadPromise = this.$q.resolve(); + } else { + loadPromise = this.hsmApi.instances(guid).then(function (data) { + that.model[guid] = that.model[guid] || {}; + that.model[guid].instances = _.sortBy(data, 'instance_id'); + that._checkUpgrade(guid); + return data; + }); + } + return this.model[guid] && this.model[guid].instances && !noCache ? this.$q.resolve(this.model[guid].instances) : loadPromise; + }, + + getService: function (guid, id) { + return this.hsmApi.service(guid, id); + }, + + getServices: function (guid, noCache) { + var that = this; + var loadPromise = this.hsmApi.services(guid).then(function (data) { + that.model[guid] = that.model[guid] || {}; + that.model[guid].services = _.sortBy(data, 'id'); + return data; + }); + return this.model[guid] && this.model[guid].services && !noCache ? this.$q.resolve(this.model[guid].services) : loadPromise; + }, + + getServiceSdl: function (guid, id, productVersion, sdlVersion) { + return this.hsmApi.serviceSdl(guid, id, productVersion, sdlVersion); + }, + + getTemplate: function (guid, sdl) { + var templateUrl = sdl.templates['sdl.json']; + return this.hsmApi.getTemplate(guid, templateUrl); + }, + + getServiceProduct: function (guid, id, productVersion) { + return this.hsmApi.serviceProduct(guid, id, productVersion); + }, + + getModel: function (guid, noCache) { + var that = this; + return this.$q.all([ + this.getInstances(guid, noCache), + this.getServices(guid, noCache) + ]).then(function () { + that._checkUpgrade(guid); + return that.model[guid]; + }); + }, + + createInstance: function (guid, id, productVersion, sdlVersion, instanceId, params) { + var that = this; + return this.hsmApi.createInstance(guid, id, productVersion, sdlVersion, instanceId, params) + .then(function (response) { + // Async update the model instances collection + that.getInstance(guid, response.instance_id).then(function (instance) { + that.model[guid].instances.push(instance); + }); + return response; + }) + .catch(function (response) { + if (response.status === 500) { + // Sometimes the request can fail but the actual action can start. In these cases update the core instances + // collection just in case + that.$timeout(function () { + that.getInstances(guid); + }, 5000); + } + return that.$q.reject(response); + }); + }, + + deleteInstance : function (guid, id) { + var that = this; + return this.hsmApi.deleteInstance(guid, id).then(function (response) { + // Update the model instances collection + _.remove(that.model[guid].instances, { instance_id: id }); + return response; + }); + }, + + configureInstance: function (serviceManagerGuid, instance, params) { + var that = this; + return this.hsmApi.configureInstance(serviceManagerGuid, instance, params).then(function (response) { + // Async update the model instances collection + that.getInstance(serviceManagerGuid, instance.instance_id); + return response; + }); + }, + + getHsmEndpoints: function () { + var userServices = this.modelManager.retrieve('app.model.serviceInstance.user'); + return _.filter(userServices.serviceInstances, {cnsi_type: 'hsm'}); + }, + + getUpgradeCount: function () { + var count = 0; + _.each(this.upgrades, function (svc) { + count += Object.keys(svc).length; + }); + return count; + }, + + setUpgradesAvailable: function () { + var that = this; + var count = 0; + var menu = this.modelManager.retrieve('app.model.navigation').menu; + var menuItem = menu.getMenuItem('sm.list'); + if (menuItem) { + _.each(this.upgrades, function (instances, guid) { + _.each(instances, function (value, id) { + count = that.hasUpgrade(guid, id) ? count + 1 : count; + }); + }); + + if (count > 0) { + menuItem.badge = { + value: count + }; + } else { + delete menuItem.badge; + } + } + }, + + hasUpgrade: function (guid, instanceId) { + return this.upgrades[guid] && this.upgrades[guid][instanceId] && + !(this.ignoreUpgrades[guid] && this.ignoreUpgrades[guid][instanceId]); + }, + + hasUpgradeAvailable: function (guid, instanceId) { + return this.upgrades[guid] && this.upgrades[guid][instanceId]; + }, + + endpointHasUpgrades: function (guid) { + return this.upgrades[guid] && Object.keys(this.upgrades[guid]).length && + !(this.ignoreUpgrades[guid] && Object.keys(this.upgrades[guid]).length === Object.keys(this.ignoreUpgrades[guid]).length); + }, + + clearUpgrades: function (guid, instanceId) { + var that = this; + this.ignoreUpgrades[guid] = this.ignoreUpgrades[guid] || {}; + if (instanceId) { + this.ignoreUpgrades[guid][instanceId] = true; + } else { + _.each(this.upgrades[guid], function (value, id) { + that.ignoreUpgrades[guid][id] = true; + }); + } + this.setUpgradesAvailable(); + }, + + _checkUpgrade: function (guid, instanceData) { + var upgrades = {}; + var data = instanceData ? instanceData : this.model[guid].instances; + _.each(data, function (instance) { + if (instance.available_upgrades && instance.available_upgrades.length) { + upgrades[instance.instance_id] = true; + } + }); + this.upgrades[guid] = upgrades; + }, + + checkHsmForUpgrade: function (guid) { + var that = this; + return that.getInstances(guid).then(function () { + that._checkUpgrade(guid); + }); + }, + + checkForUpgrades: function () { + var that = this; + var promises = []; + this.upgrades = {}; + + _.each(this.getHsmEndpoints(), function (hsm) { + var p = that.getInstances(hsm.guid).then(function (data) { + that._checkUpgrade(hsm.guid, data); + }); + promises.push(p); + }); + + return this.$q.all(promises).then(function () { + return that.getUpgradeCount(); + }); + } + }); +})(); diff --git a/src/plugins/service-manager/plugin.config.js b/src/plugins/service-manager/plugin.config.js new file mode 100644 index 0000000000..acfe6f780c --- /dev/null +++ b/src/plugins/service-manager/plugin.config.js @@ -0,0 +1,13 @@ +(function () { + 'use strict'; + + // register this plugin application to the platform + if (env && env.registerApplication) { + env.registerApplication( + 'serviceManager', // plugin application identity + 'service-manager', // plugin application's root angular module name + 'plugins/service-manager/' // plugin application's base path + ); + } + +}()); diff --git a/src/plugins/service-manager/service-manager.module.js b/src/plugins/service-manager/service-manager.module.js new file mode 100644 index 0000000000..32deb7dbcd --- /dev/null +++ b/src/plugins/service-manager/service-manager.module.js @@ -0,0 +1,90 @@ +(function () { + 'use strict'; + + angular + .module('service-manager', [ + 'service-manager.api', + 'service-manager.view', + 'service-manager.model', + 'service-manager.utils' + ]) + .run(register); + + register.$inject = [ + '$state', + '$location', + 'app.event.eventService', + 'app.model.modelManager', + 'app.view.notificationsService', + 'app.utils.utilsService' + ]; + + function register($state, $location, eventService, modelManager, notificationService, utils) { + return new ServiceManager($state, $location, eventService, modelManager, notificationService, utils); + } + + function ServiceManager($state, $location, eventService, modelManager, notificationService, utils) { + var that = this; + this.eventService = eventService; + this.modelManager = modelManager; + this.$state = $state; + this.$location = $location; + this.notificationService = notificationService; + this.utils = utils; + this.eventService.$on(this.eventService.events.LOGIN, function (ev, preventRedirect) { + that.onLoggedIn(preventRedirect); + }); + this.eventService.$on(this.eventService.events.LOGOUT, function () { + that.onLoggedOut(); + }); + + this.eventService.$on(this.eventService.events.ENDPOINT_CONNECT_CHANGE, function () { + that.update(); + }); + } + + angular.extend(ServiceManager.prototype, { + update: function (services) { + var that = this; + // Check to see if we have any services + var userServices = this.modelManager.retrieve('app.model.serviceInstance.user'); + services = services || userServices.serviceInstances; + var hsmServices = _.filter(services, {cnsi_type: 'hsm'}); + this.menuItem.hidden = hsmServices.length === 0; + if (!this.menuItem.hidden) { + var smModel = this.modelManager.retrieve('service-manager.model'); + smModel.checkForUpgrades().then(function (upgrades) { + that.setUpgradesAvailable(upgrades); + }); + } + }, + onLoggedIn: function () { + this.registerNavigation(); + this.update(); + }, + + setUpgradesAvailable: function (upgrades) { + if (upgrades > 0) { + this.menuItem.badge = { + value: upgrades + }; + } else { + delete this.menuItem.badge; + } + }, + + onLoggedOut: function () { + }, + + registerNavigation: function () { + var menu = this.modelManager.retrieve('app.model.navigation').menu; + // Keep the short label of HSM to ensure nav bar text does not wrap + this.menuItem = menu.addMenuItem('sm.list', 'sm.list', gettext('Service Manager'), 1, + 'svg://Service_manager.svg'); + // + // Hide to start with until we know if we have HSM Services connected + this.menuItem.hidden = true; + } + }); + +})(); diff --git a/src/plugins/service-manager/service-manager.scss b/src/plugins/service-manager/service-manager.scss new file mode 100644 index 0000000000..d0531c25df --- /dev/null +++ b/src/plugins/service-manager/service-manager.scss @@ -0,0 +1,356 @@ +.hsm .cluster-detail { + width: 100% !important; +} + +.product-version { + margin-right: 12px; + background-color: #f7f7f7; + padding: 4px 8px; + border-radius: 5px; +} + +.hsm-label-category { + margin-left: 12px; + background-color: #f7f7f7; + padding: 2px 8px; + border-radius: 2px; +} + +.upgrade-version { + margin: 4px 0px; +} + +.tab-count-indicator { + color: #aaa; + margin-left: 4px; +} + +.sdl-latest-version { + font-weight: bold; +} + +.card.panel .panel-body .dl-horizontal > dd.hsm-state-label { + text-transform: uppercase; +} + +.hsm-instance-header, .hsm-service-header { + display: flex; + align-items: center; + margin: $hpe-unit-space 0; + + h3 { + display: inline-block; + margin: 0; + height: auto; + } + + app-state-icon { + height: auto; + line-height: 1; + margin-left: $hpe-unit-space * 0.5; + } +} + +.hsm-service-header { + h3 { + flex: 1; + } + + button { + font-size: 16px; + height: auto; + padding: 4px 24px; + } +} + +.hpe-popover-alert.popover.hpe-popover-alert-info { + a { + color: #fff; + text-decoration: none; + + &:hover { + color: #eee; + } + } +} + +.product-version { + &.hilight { + background-color: $brand-primary; + color: #fff; + } + + &.pointer { + cursor: pointer; + } +} + +.content { + table.table > thead > tr { + > th.table-sort-filter { + padding: $hpe-unit-space * 0.5; + + .form-group { + width: auto; + text-align: left; + height: $hpe-unit-space * 1.5; + + i { + position: absolute; + margin-left: -12px; + height: $hpe-unit-space * 1.5;; + vertical-align: middle; + line-height: $hpe-unit-space * 1.5;; + font-size: 18px; + } + + input { + height: 100%; + padding: 0; + margin: 0; + margin-left: $hpe-unit-space; + font-weight: normal; + } + } + } + } + + table.table > tbody > tr > td.hsm-actions { + a { + color: #01a982; + &:hover, &:focus { + text-decoration: none; + color: #555; + } + } + + .add-icon { + border: 2px solid #01a982; + font-size: 12px; + border-radius: 50%; + padding: 4px; + margin-left: 6px; + font-size: 14px; + } + } +} + +.hsm-service-heading { + display: flex; + + > span { + flex: 1; + } + + > img { + height: 48px; + } +} + +.hsm-description { + text-transform: none; + font-weight: 500; +} + +fieldset.fieldset-horizontal { + display: block; + + .form-group { + display: inline-block; + margin-left: 0; + margin-right: -1px; + float: left; + } + +} + +.full-width-field { + .form-group { + width: 100%; + } +} + +.two-width-fields { + .form-group { + width: 647px; + } +} + +table tr td.table-inline-message { + .popover.hpe-popover-alert-upgrade { + > .arrow { + display: none; + } + + > .popover-content { + border-top: 1px solid #eee; + margin: 0px 24px; + padding: 12px 0; + + a { + color: #888; + &:hover, &:focus { + text-decoration: none; + color: #01a982; + } + } + } + } +} + +table.table > tbody > tr > td.table-inline-message.hsm-upgrade, div.hsm-upgrade { + padding: 0; + margin: 0; + text-align: left; + + > div { + display: flex; + margin: 0; + border-top: 1px solid #eee; + padding: 12px 0; + + > div.upgrade-message { + text-align: left; + flex: 1; + color: #888; + display: flex; + align-items: center; + + > i.helion-icon { + font-size: 18px; + margin-right: 8px; + } + + a { + color: #888; + &:hover, &:focus { + text-decoration: none; + color: #01a982; + } + } + } + + > div.upgrade-acknowledge { + > i { + font-size: 16px; + } + color: #888; + cursor: pointer; + &:hover, &:focus { + color: #01a982; + } + } + } +} + +div.hsm-upgrade > div { + padding-bottom: 0px; +} + +.upgrade-indicator { + > i { + font-size: 16px; + } + color: #888; + cursor: pointer; + &:hover, &:focus { + color: #01a982; + } +} + +form.hsm-filter-box { + margin-bottom: $hpe-unit-space * 0.5; + width: $hpe-unit-space * 15; + + .form-group { + width: auto; + text-align: left; + height: $hpe-unit-space * 1.5; + + i { + position: absolute; + margin-left: -12px; + height: $hpe-unit-space * 1.5;; + vertical-align: middle; + line-height: $hpe-unit-space * 1.5;; + font-size: 18px; + } + + input { + height: 100%; + padding: 0; + margin: 0; + margin-left: $hpe-unit-space; + font-weight: normal; + } + } +} + +fieldset.fieldset-horizontal .form-group.file-drop-active { + border-color: red; + background-color: green; + opacity: 0.75; +} + +.hsm-file-drop-target { + display: flex; + position: absolute; + right: 0; + top: 0; + height: 53px; + align-content: center; + align-items: center; + text-align: center; + + > div { + border: 1px dashed #ccc; + padding: 4px 8px; + margin-right: 6px; + color: #666; + } + + &.file-drop-active > div { + border-color: $hpe-primary; + background-color: #f0f0f0; + color: #333; + } +} + +form.file-drop-active { + opacity: 0.5; +} + +#hsm-choose-instance-file { + display: none; +} + +.hsm-create-instance-intro { + display: flex; + + > div { + margin-left: $hpe-unit-space * 0.5; + } +} + +.hsm-params-selector { + display: flex; + align-items: center; + + > h4 { + flex: 1; + } +} + +.sdl-upgrade-version { + font-weight: bold; +} + +.hide-components { + .checkbox-input { + text-align: right; + float: right; + text-transform: none; + font-weight: normal; + margin-right: 12px; + } +} diff --git a/src/plugins/service-manager/version.utils.js b/src/plugins/service-manager/version.utils.js new file mode 100644 index 0000000000..da4d6c28ae --- /dev/null +++ b/src/plugins/service-manager/version.utils.js @@ -0,0 +1,84 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.utils', []) + .factory('service-manager.utils.version', versionUtilsServiceFactory); + + versionUtilsServiceFactory.$inject = [ + ]; + + function versionUtilsServiceFactory() { + return { + sort: sort, + sortByProperty: sortByProperty, + compare: compare, + parse: parse + }; + } + + function parse(versionString) { + var parts = versionString.split('-'); + var versionParts = parts[0].split('.'); + return { + major: versionParts.length > 0 ? parseInt(versionParts[0], 10) : undefined, + minor: versionParts.length > 1 ? parseInt(versionParts[1], 10) : undefined, + revision: versionParts.length > 2 ? parseInt(versionParts[2], 10) : undefined, + tag: parts.length > 1 ? parts[1] : undefined + }; + } + + function compare(v1s, v2s) { + var v1 = parse(v1s); + var v2 = parse(v2s); + if (v1.major === v2.major) { + if (v1.minor === v2.minor) { + if (v1.revision === v2.revision) { + return 0; + } else { + return v1.revision > v2.revision ? 1 : -1; + } + } else { + return v1.minor > v2.minor ? 1 : -1; + } + } else { + return v1.major > v2.major ? 1 : -1; + } + } + + function compareReverse(v1, v2) { + var res = compare(v1, v2); + if (res === 0) { + return res; + } else { + return res === 1 ? -1 : 1; + } + } + + function compareByProperty(property, reverse) { + if (!reverse) { + return function (v1, v2) { + return compare(v1[property], v2[property]); + }; + } else { + return function (v1, v2) { + return compareReverse(v1[property], v2[property]); + }; + } + } + + function sort(array) { + if (!array) { + return; + } + return array.sort(compare); + } + + function sortByProperty(array, property, reverse) { + if (!array) { + return; + } + return array.sort(compareByProperty(property, reverse)); + } + +})(); diff --git a/src/plugins/service-manager/view/manage-instance/manage-instance-form.directive.js b/src/plugins/service-manager/view/manage-instance/manage-instance-form.directive.js new file mode 100644 index 0000000000..5965cefaf8 --- /dev/null +++ b/src/plugins/service-manager/view/manage-instance/manage-instance-form.directive.js @@ -0,0 +1,249 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.view.manage-instance.form', []) + .directive('manageInstanceForm', manageInstanceForm); + + manageInstanceForm.$inject = [ + ]; + + /** + * @name application + * @returns {object} The manage instance form directive definition object + */ + function manageInstanceForm() { + return { + controller: ManageInstanceForm, + controllerAs: 'ciFormCtrl', + templateUrl: 'plugins/service-manager/view/manage-instance/manage-instance-form.html', + scope: { + data: '=' + }, + bindToController: true + }; + } + + ManageInstanceForm.$inject = [ + '$scope', + '$q', + '$window', + 'app.model.modelManager', + 'service-manager.utils.version' + ]; + + /** + * @name ManageInstanceForm + * @param {object} $scope - the Angular $scope service + * @param {object} $q - the Angular $q promise service + * @param {object} $window - the Angular $window service + * @param {app.model.modelManager} modelManager - the Model management service + * @param {object} versionUtils - version utils service + * @class + */ + function ManageInstanceForm($scope, $q, $window, modelManager, versionUtils) { + var that = this; + this.$q = $q; + this.hsmModel = modelManager.retrieve('service-manager.model'); + this.versionUtils = versionUtils; + this.data.form = $scope.form; + this.data.params = {}; + + this.sdlOptions = []; + + this.instanceFile = {}; + + this.FileReader = $window.FileReader; + + $scope.$watch('form', function (f) { + that.data.form = f; + }); + + $scope.$watch(function () { + return that.instanceFile; + }, function () { + if (that.instanceFile.name) { + that.read(); + } + }); + + this.serviceOptions = []; + + _.each(this.data.services, function (svc, name) { + that.serviceOptions.push({ + label: name, + value: name + }); + }); + + if (this.data.mode === 'create') { + this.serviceChanged(this.data.productVersion, this.data.sdlVersion); + } + + if (this.data.mode === 'upgrade') { + this.getUpgradeMetadata(); + } + + if (this.data.instance) { + this.shownParams = this.data.instance.parameters; + _.each(this.shownParams, function (p) { + p.default = p.value; + }); + } + } + + angular.extend(ManageInstanceForm.prototype, { + readInstanceFile: function (file, encoding) { + var deferred = this.$q.defer(); + var reader = new this.FileReader(); + reader.onload = function () { + deferred.resolve(reader.result); + }; + reader.onerror = function () { + deferred.reject(reader.result); + }; + + try { + reader.readAsText(file, encoding); + } catch (exception) { + deferred.reject(exception); + } + + return deferred.promise; + }, + + read: function () { + var that = this; + this.readInstanceFile(this.instanceFile).then(function (text) { + // Possible improvement - Add general error handling + messages to cover unexpected file content/syntax or + // missing required properties. At the moment these silently fail. + + // Exceptions will get caught later down the promise chain + var json = angular.fromJson(text); + if (json.instance_id) { + that.data.instanceId = json.instance_id; + } + if (json.name) { + that.data.serviceId = json.name; + that.serviceChanged(json.product_version, json.sdl_version); + } + if (json.parameters) { + that.data.params = {}; + _.each(json.parameters, function (p) { + that.data.params[p.name] = p.value; + }); + } +// }).catch(function (err) { + }); + }, + + serviceChanged: function (productVersion, sdlVersion) { + var that = this; + var service = this.data.services[this.data.serviceId]; + this.service = service; + this.productVersions = []; + this.sdlVersions = {}; + + if (service) { + _.each(service.product_versions, function (version) { + that.productVersions.push({ + label: version.product_version, + value: version.product_version + }); + var sdl = []; + that.sdlVersions[version.product_version] = sdl; + _.each(version.sdl_versions, function (url, sdlVersion) { + sdl.push({ + label: sdlVersion, + value: sdlVersion, + latest: sdlVersion === version.latest + }); + }); + that.versionUtils.sortByProperty(sdl, 'value', true); + }); + + this.versionUtils.sortByProperty(that.productVersions, 'value', true); + + if (productVersion) { + this.data.product = productVersion; + } else { + this.data.product = this.productVersions[0].value; + } + } + this.productChanged(sdlVersion); + }, + + productChanged: function (sdlVersion) { + this.data.sdl = undefined; + this.sdlOptions = this.sdlVersions[this.data.product] || []; + var found = _.find(this.sdlOptions, {latest: true}); + if (!sdlVersion) { + this.data.sdl = found ? found.value : undefined; + } else { + this.data.sdl = sdlVersion; + if (!_.find(this.sdlOptions, {value: sdlVersion})) { + this.data.sdl = found ? found.value : undefined; + } + } + this.sdlChanged(); + }, + + isParamRequired: function (param) { + return param.required && !param.generator && !(param.default || _.isString(param.default)); + }, + + sdlChanged: function () { + var that = this; + if (this.data.sdl) { + this.hsmModel.getServiceSdl(this.data.guid, this.service.id, this.data.product, this.data.sdl).then(function (sdl) { + that.parameters = sdl.parameters; + _.each(that.parameters, function (param) { + param.notSupplied = that.isParamRequired(param); + }); + that.showAllParams(false); + }); + } else { + this.parameters = undefined; + } + }, + + showAllParams: function (showAll) { + var that = this; + if (!showAll) { + that.shownParams = _.filter(that.parameters, function (param) { + return that.isParamRequired(param); + }); + } else { + that.shownParams = that.parameters; + } + }, + + getUpgradeMetadata: function () { + var that = this; + this.productVersions = []; + this.sdlVersions = {}; + var upgrades = this.data.instance.available_upgrades; + _.each(upgrades, function (version) { + that.productVersions.push({ + label: version.product_version, + value: version.product_version + }); + var sdl = []; + that.sdlVersions[version.product_version] = sdl; + _.each(version.sdl_versions, function (sdlVersion) { + sdl.push({ + label: sdlVersion.sdl_version, + value: sdlVersion.sdl_version, + latest: sdlVersion.is_latest + }); + }); + that.versionUtils.sortByProperty(sdl, 'value', true); + }); + this.versionUtils.sortByProperty(that.productVersions, 'value', true); + this.data.product = that.productVersions[0].value; + this.sdlOptions = this.sdlVersions[this.data.product] || []; + var found = _.find(this.sdlOptions, {latest: true}); + this.data.sdl = found ? found.value : undefined; + } + }); +})(); diff --git a/src/plugins/service-manager/view/manage-instance/manage-instance-form.html b/src/plugins/service-manager/view/manage-instance/manage-instance-form.html new file mode 100644 index 0000000000..c9a62c70dc --- /dev/null +++ b/src/plugins/service-manager/view/manage-instance/manage-instance-form.html @@ -0,0 +1,136 @@ +
    + +
    +

    + You can upload a pre-prepared instance JSON file, by either selecting the file below or by + dragging and dropping it onto this area. +

    +
    + + +
    +
    + +
    +
    +
    Instance ID
    +
    {{ ciFormCtrl.data.instance.instance_id}}
    +
    Service ID
    +
    {{ ciFormCtrl.data.serviceId}}
    +
    Product Version
    +
    {{ ciFormCtrl.data.instance.product_version }}
    +
    SDL Version
    +
    {{ ciFormCtrl.data.instance.sdl_version }}
    +
    +
    + +
    +

    Choose Product Version and SDL Version to upgrade to

    +
    +
    + + +
    +
    + + +
    +
    +
    + +
    +

    Service

    +
    +
    + + +
    +
    + +

    Choose Product Version, SDL Version and an Instance ID

    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    + +
    + +
    +

    Service Parameters

    +
    + Show: + Required + All +
    +
    + +
    +
    + + +
    +
    +
    + This service does not have any parameters that must be provided. +
    +
    +
    diff --git a/src/plugins/service-manager/view/manage-instance/manage-instance.html b/src/plugins/service-manager/view/manage-instance/manage-instance.html new file mode 100644 index 0000000000..12ff1f0774 --- /dev/null +++ b/src/plugins/service-manager/view/manage-instance/manage-instance.html @@ -0,0 +1,3 @@ +
    + +
    \ No newline at end of file diff --git a/src/plugins/service-manager/view/manage-instance/manage-instance.service.js b/src/plugins/service-manager/view/manage-instance/manage-instance.service.js new file mode 100644 index 0000000000..f394790278 --- /dev/null +++ b/src/plugins/service-manager/view/manage-instance/manage-instance.service.js @@ -0,0 +1,115 @@ +(function () { + + 'use strict'; + + angular + .module('service-manager.view.manage-instance', ['service-manager.view.manage-instance.form']) + .factory('service-manager.view.manage-instance.dialog', ManageInstanceDialogFactory); + + ManageInstanceDialogFactory.$inject = [ + '$q', + 'app.model.modelManager', + 'helion.framework.widgets.asyncTaskDialog' + ]; + + /** + * @name ManageInstanceDialogFactory + * @constructor + * @param {object} $q - the Angular $q service + * @param {app.model.modelManager} modelManager - the Model management service + * @param {object} asyncTaskDialog - our async dialog service + */ + function ManageInstanceDialogFactory($q, modelManager, asyncTaskDialog) { + + this.show = function (mode, serviceManagerGuid, services, serviceId, productVersion, sdlVersion) { + + function create(data) { + var hsmModel = modelManager.retrieve('service-manager.model'); + return hsmModel.createInstance(serviceManagerGuid, data.serviceId, data.product, data.sdl, data.instanceId, + data.params); + } + + function configure(data) { + var hsmModel = modelManager.retrieve('service-manager.model'); + return hsmModel.configureInstance(serviceManagerGuid, data.instance, data.params); + } + + function upgrade(data) { + var hsmModel = modelManager.retrieve('service-manager.model'); + // Update the product and sdl versions for the instance + data.instance.product_version = data.product; + data.instance.sdl_version = data.sdl; + return hsmModel.configureInstance(serviceManagerGuid, data.instance, data.params); + } + + var title, submitButtonText, instance; + switch (mode) { + case 'upgrade': + title = gettext('Upgrade Instance'); + submitButtonText = gettext('Upgrade'); + break; + case 'configure': + title = gettext('Configure Instance'); + submitButtonText = gettext('Configure'); + break; + default: + title = gettext('Create Instance'); + submitButtonText = gettext('Create'); + } + + if (!services && angular.isObject(serviceId)) { + instance = serviceId; + serviceId = instance.service_id; + productVersion = instance.product_version; + sdlVersion = instance.sdl_version; + } + + var asyncContext = { + data: { + mode: mode, + guid: serviceManagerGuid, + instance: instance, + serviceId: serviceId, + services: services, + productVersion: productVersion, + sdlVersion: sdlVersion + }, + invalidityCheck: function (data) { + return data.form && data.form.createInstanceForm ? data.form.createInstanceForm.$invalid : true; + } + }; + + function execute(data) { + var result; + switch (mode) { + case 'upgrade': + result = upgrade(data); + break; + case 'configure': + result = configure(data); + break; + default: + result = create(data); + break; + } + + return result.catch(function (response) { + asyncContext.errorMsg = _.get(response, 'data.message'); + return $q.reject(response); + }); + } + + return asyncTaskDialog( + { + title: title, + templateUrl: 'plugins/service-manager/view/manage-instance/manage-instance.html', + class: 'detail-view-two-fields', + buttonTitles: { + submit: submitButtonText + } + }, asyncContext, execute); + }; + + return this; + } +})(); diff --git a/src/plugins/service-manager/view/service/detail/detail.html b/src/plugins/service-manager/view/service/detail/detail.html new file mode 100644 index 0000000000..fd91f18a66 --- /dev/null +++ b/src/plugins/service-manager/view/service/detail/detail.html @@ -0,0 +1,90 @@ +
    + +
    +
    +

    Service Manager : {{ smCtrl.endpoint.name }}

    +
    + +
    +
    +
    +
    +
    + Summary +
    + +
    +
    +
    +
    Instances
    +
    + {{ smCtrl.model.instances.length }} +
    +
    Services
    +
    + {{ smCtrl.model.services.length }} +
    +
    Organizations
    +
    + {{ svcsCtrl.orgCount }} +
    +
    Users
    +
    + {{ svcsCtrl.userCount }} +
    +
    +
    +
    +
    +
    INSTANCE ADDRESS
    +
    + {{ smCtrl.getEndpoint() }} +
    + +
    Account Username
    +
    + {{ svcsCtrl.userName }} +
    +
    Account Status
    +
    + {{ svcsCtrl.isAdmin ? 'administrator' : 'user' }} +
    +
    +
    +
    +
    +
    +
    + + Upgrade(s) are available for one or more instances +
    +
    + +
    +
    +
    +
    + + + +
    +
    diff --git a/src/plugins/service-manager/view/service/detail/detail.module.js b/src/plugins/service-manager/view/service/detail/detail.module.js new file mode 100644 index 0000000000..6a7ec5ed9d --- /dev/null +++ b/src/plugins/service-manager/view/service/detail/detail.module.js @@ -0,0 +1,52 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.view.service.detail', []) + .config(registerRoute); + + registerRoute.$inject = [ + '$stateProvider' + ]; + + function registerRoute($stateProvider) { + $stateProvider.state('sm.endpoint.detail', { + url: '/detail', + abstract: true, + templateUrl: 'plugins/service-manager/view/service/detail/detail.html', + // ncyBreadcrumb: { + // label: '{{ smCtrl.endpoint.name || "..." }}', + // parent: 'sm.tiles' + // }, + controller: ServiceManagerDetailController, + controllerAs: 'svcSDetailCtrl' + }); + } + + ServiceManagerDetailController.$inject = [ + '$state', + '$rootScope', + '$scope', + 'app.model.modelManager' + ]; + + function ServiceManagerDetailController($state, $rootScope, $scope, modelManager) { + var that = this; + this.hsmModel = modelManager.retrieve('service-manager.model'); + if ($state.current.name === 'sm.endpoint.detail') { + var activeTab = hsmModel.detailActiveTab ? '' : 'sm.endpoint.detail.instances'; + $state.go(activeTab); + } + + this.removeStateChangeListener = $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState) { + if (toState.name.indexOf('sm.endpoint.detail') === 0 && fromState.name.indexOf('sm.endpoint.detail') === 0) { + that.hsmModel.detailActiveTab = toState.name; + } + }); + + $scope.$on('$destroy', function () { + that.removeStateChangeListener(); + }); + + } +})(); diff --git a/src/plugins/service-manager/view/service/detail/instances/service-manager.instances.html b/src/plugins/service-manager/view/service/detail/instances/service-manager.instances.html new file mode 100644 index 0000000000..14cc257ec9 --- /dev/null +++ b/src/plugins/service-manager/view/service/detail/instances/service-manager.instances.html @@ -0,0 +1,72 @@ +
    + +
    +
    +
    An error occurred retrieving instances
    +
    + diff --git a/src/plugins/service-manager/view/service/detail/instances/service-manager.instances.module.js b/src/plugins/service-manager/view/service/detail/instances/service-manager.instances.module.js new file mode 100644 index 0000000000..fb45f8826f --- /dev/null +++ b/src/plugins/service-manager/view/service/detail/instances/service-manager.instances.module.js @@ -0,0 +1,33 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.view.service.detail.instances', []) + .config(registerRoute); + + registerRoute.$inject = [ + '$stateProvider' + ]; + + function registerRoute($stateProvider) { + $stateProvider.state('sm.endpoint.detail.instances', { + url: '/instances', + templateUrl: 'plugins/service-manager/view/service/detail/instances/service-manager.instances.html', + controller: ServiceManagerInstancesController, + controllerAs: 'instancesCtrl', + ncyBreadcrumb: { + label: '{{ smCtrl.endpoint.name || "..." }}', + parent: 'sm.tiles' + } + }); + } + + ServiceManagerInstancesController.$inject = [ + '$state' + ]; + + function ServiceManagerInstancesController($state) { + this.$state = $state; + } + +})(); diff --git a/src/plugins/service-manager/view/service/detail/services/service-manager.services.html b/src/plugins/service-manager/view/service/detail/services/service-manager.services.html new file mode 100644 index 0000000000..451bee6162 --- /dev/null +++ b/src/plugins/service-manager/view/service/detail/services/service-manager.services.html @@ -0,0 +1,68 @@ +
    + +
    +
    +
    An error occurred retrieving services
    +
    + diff --git a/src/plugins/service-manager/view/service/detail/services/service-manager.services.module.js b/src/plugins/service-manager/view/service/detail/services/service-manager.services.module.js new file mode 100644 index 0000000000..e434014629 --- /dev/null +++ b/src/plugins/service-manager/view/service/detail/services/service-manager.services.module.js @@ -0,0 +1,33 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.view.service.detail.services', []) + .config(registerRoute); + + registerRoute.$inject = [ + '$stateProvider' + ]; + + function registerRoute($stateProvider) { + $stateProvider.state('sm.endpoint.detail.services', { + url: '/services', + templateUrl: 'plugins/service-manager/view/service/detail/services/service-manager.services.html', + controller: ServiceManagerServicesController, + controllerAs: 'servicesCtrl', + ncyBreadcrumb: { + label: '{{ smCtrl.endpoint.name || "..." }}', + parent: 'sm.tiles' + } + }); + } + + ServiceManagerServicesController.$inject = [ + '$state' + ]; + + function ServiceManagerServicesController($state) { + this.$state = $state; + } + +})(); diff --git a/src/plugins/service-manager/view/service/instance-detail/instance-components.html b/src/plugins/service-manager/view/service/instance-detail/instance-components.html new file mode 100644 index 0000000000..340970a989 --- /dev/null +++ b/src/plugins/service-manager/view/service/instance-detail/instance-components.html @@ -0,0 +1,46 @@ +
    +
    No components available.
    +
    + + + + + + + + + + + + + + + + + + + +
    Component NameStateVolumes
    +
    +
    + + +
    +
    +
    + + +
    {{ component.name }}{{ component.state.phase }} + {{ volume.name }} +
    diff --git a/src/plugins/service-manager/view/service/instance-detail/instance-detail.html b/src/plugins/service-manager/view/service/instance-detail/instance-detail.html new file mode 100644 index 0000000000..b51bb20393 --- /dev/null +++ b/src/plugins/service-manager/view/service/instance-detail/instance-detail.html @@ -0,0 +1,124 @@ +
    + +
    +
    +

    Instance : {{ instanceCtrl.id }}

    + + + +
    + +
    +
    +
    + Summary +
    + +
    +
    +
    +
    State
    +
    + {{ instanceCtrl.instance.state }} +
    +
    Product Version
    +
    + {{ instanceCtrl.instance.product_version }} +
    +
    SDL Version
    +
    + + {{ instanceCtrl.instance.sdl_version }} + +
    +
    Service ID
    +
    + + {{ instanceCtrl.instance.service_id }} + +
    +
    +
    +
    +
    +
    Last Updated
    +
    + {{ instanceCtrl.instance.last_updated | amDateFormat: 'L - LTS' }} +
    +
    INSTANCE ADDRESS
    +
    + {{ smCtrl.getEndpoint() }} +
    + +
    Account Username
    +
    + {{ svcsCtrl.userName }} +
    +
    Account Status
    +
    + {{ svcsCtrl.isAdmin ? 'administrator' : 'user' }} +
    +
    +
    +
    +
    +
    + +
    +
    Instance has been deleted.
    +
    + +
    +
    Instance not found.
    +
    + + +
    +
    + diff --git a/src/plugins/service-manager/view/service/instance-detail/instance-detail.module.js b/src/plugins/service-manager/view/service/instance-detail/instance-detail.module.js new file mode 100644 index 0000000000..02921aa118 --- /dev/null +++ b/src/plugins/service-manager/view/service/instance-detail/instance-detail.module.js @@ -0,0 +1,377 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.view.service.instance-detail', []) + .config(registerRoute); + + registerRoute.$inject = [ + '$stateProvider' + ]; + + function registerRoute($stateProvider) { + + // Abstract detail route + $stateProvider.state('sm.endpoint.instance', { + url: '/instance/:id', + templateUrl: 'plugins/service-manager/view/service/instance-detail/instance-detail.html', + controller: ServiceManagerInstanceDetailController, + controllerAs: 'instanceCtrl', + abstract: true, + ncyBreadcrumb: { + label: '{{ instanceCtrl.id || "..." }}', + parent: 'sm.endpoint.detail.instances' + }, + data: { + activeMenuState: 'sm.list' + } + }); + + // Services Tab + $stateProvider.state('sm.endpoint.instance.services', { + url: '', + templateUrl: 'plugins/service-manager/view/service/instance-detail/instance-services.html', + ncyBreadcrumb: { + label: '{{ instanceCtrl.id || "..." }}', + parent: 'sm.endpoint.detail.instances' + }, + data: { + activeMenuState: 'sm.list' + } + }); + + // Components Tab + $stateProvider.state('sm.endpoint.instance.components', { + url: '/components', + templateUrl: 'plugins/service-manager/view/service/instance-detail/instance-components.html', + ncyBreadcrumb: { + label: '{{ instanceCtrl.id || "..." }}', + parent: 'sm.endpoint.detail.instances' + }, + data: { + activeMenuState: 'sm.list' + } + }); + + // Parameters Tab + $stateProvider.state('sm.endpoint.instance.parameters', { + url: '/parameters', + templateUrl: 'plugins/service-manager/view/service/instance-detail/instance-parameters.html', + ncyBreadcrumb: { + label: '{{ instanceCtrl.id || "..." }}', + parent: 'sm.endpoint.detail.instances' + }, + data: { + activeMenuState: 'sm.list' + } + }); + + // Scaling Tab + $stateProvider.state('sm.endpoint.instance.scaling', { + url: '/scaling', + templateUrl: 'plugins/service-manager/view/service/instance-detail/instance-scaling.html', + ncyBreadcrumb: { + label: '{{ instanceCtrl.id || "..." }}', + parent: 'sm.endpoint.detail.instances' + }, + data: { + activeMenuState: 'sm.list' + } + }); + + // Upgrades Tab + $stateProvider.state('sm.endpoint.instance.versions', { + url: '/versions', + templateUrl: 'plugins/service-manager/view/service/instance-detail/instance-versions.html', + ncyBreadcrumb: { + label: '{{ instanceCtrl.id || "..." }}', + parent: 'sm.endpoint.detail.instances' + }, + controller: UpgradeController, + controllerAs: 'instanceUpgradeCtrl' + }); + } + + ServiceManagerInstanceDetailController.$inject = [ + '$scope', + '$timeout', + '$state', + '$stateParams', + 'app.utils.utilsService', + 'app.model.modelManager', + 'helion.framework.widgets.dialog.confirm', + 'service-manager.view.manage-instance.dialog', + 'service-manager.utils.version' + ]; + + function ServiceManagerInstanceDetailController($scope, $timeout, $state, $stateParams, utils, modelManager, + confirmDialog, manageInstanceDialog, versionUtils) { + var that = this; + + this.initialized = false; + this.guid = $stateParams.guid; + this.userServiceInstanceModel = modelManager.retrieve('app.model.serviceInstance.user'); + this.confirmDialog = confirmDialog; + this.manageInstanceDialog = manageInstanceDialog; + this.$timeout = $timeout; + this.$state = $state; + this.versionUtils = versionUtils; + + this.hsmModel = modelManager.retrieve('service-manager.model'); + this.stackatoInfo = modelManager.retrieve('app.model.stackatoInfo'); + + this.getEndpoint = function () { + return utils.getClusterEndpoint(that.endpoint); + }; + + this.guid = $state.params.guid; + this.id = $state.params.id; + + this.interestingState = false; + + this.actions = [ + { id: 'upgrade', name: gettext('Upgrade Instance'), + execute: function () { + return that.upgradeInstance(that.id); + } + }, + { id: 'configure', name: gettext('Configure Instance'), + execute: function () { + return that.configureInstance(that.id); + } + }, + { id: 'delete', name: gettext('Delete Instance'), + execute: function () { + return that.deleteInstance(that.id); + } + } + ]; + + this.actionsMap = _.keyBy(this.actions, 'id'); + + this.highlight = function (id, on) { + if (on) { + angular.element('.hsm-volume-' + id).addClass('hilight'); + } else { + angular.element('.hsm-volume-' + id).removeClass('hilight'); + } + }; + + // Poll for updates + $scope.$on('$destroy', function () { + that.$timeout.cancel(that.pollTimer); + }); + + $scope.$watch(function () { + return that.hsmModel.hideCompletedComponents; + }, function () { + that._filterComponents(); + }); + + if (angular.isUndefined(that.hsmModel.hideCompletedComponents)) { + that.hsmModel.hideCompletedComponents = true; + } + + this.fetch().then(function () { + that.poll(); + }); + } + + angular.extend(ServiceManagerInstanceDetailController.prototype, { + + updateActions: function () { + var isDelete = this.deleting || this.deleted || this.instance.state === 'deleting' || + this.instance.state === 'deleted'; + var isBusy = isDelete || this.interestingState; + this.actionsMap.configure.disabled = isBusy; + this.actionsMap.delete.disabled = isDelete; + + var hasUpgrades = this.instance && this.instance.available_upgrades && this.instance.available_upgrades.length > 0; + this.actionsMap.upgrade.disabled = isBusy || !hasUpgrades; + }, + + poll: function (fetchNow) { + var that = this; + if (that.deleted || that.notFound) { + return; + } + + // Cancel timer if one is outstanding + if (this.pollTimer) { + this.$timeout.cancel(this.pollTimer); + } + + var pollTime = that.interestingState ? 2500 : 5000; + pollTime = fetchNow ? 0 : pollTime; + + this.pollTimer = this.$timeout(function () { + that.fetch().finally(function () { + delete that.pollTimer; + that.poll(); + }); + }, pollTime); + }, + + fetch: function () { + var that = this; + return this.hsmModel.getInstance(this.guid, this.id).then(function (data) { + that.instance = data; + that._filterComponents(); + that._setStateIndicator(); + that._sortUpgrades(); + }).catch(function (err) { + if (that.deleting) { + that.deleted = true; + that.instance.state = 'deleted'; + that._setStateIndicator(); + } else if (err.status === 404) { + that.notFound = true; + that.instance = that.instance || {}; + that.instance.state = '404'; + that._setStateIndicator(); + } + }); + }, + + _filterComponents: function () { + var that = this; + if (!that.instance) { + this.components = []; + } else { + this.components = _.filter(that.instance.components, function (item) { + var inactive = item.state.phase === 'Failed' || item.state.phase === 'Succeeded'; + return that.hsmModel.hideCompletedComponents ? !inactive : true; + }); + } + }, + + _sortUpgrades: function () { + var that = this; + this.versionUtils.sortByProperty(this.instance.available_upgrades, 'product_version', true); + _.each(this.instance.available_upgrades, function (product) { + that.versionUtils.sortByProperty(product.sdl_versions, 'sdl_version', true); + }); + }, + + _setStateIndicator: function () { + this.stateIndicator = this.hsmModel.instanceStateIndicator(this.instance.state); + this.interestingState = false; + switch (this.instance.state) { + case 'creating': + this.interestingState = true; + break; + case 'deleting': + this.interestingState = true; + break; + case 'modifying': + this.interestingState = true; + break; + } + this.updateActions(); + }, + + deleteInstance: function (id) { + var that = this; + var dialog = this.confirmDialog({ + title: gettext('Delete Instance'), + description: function () { + return gettext('Are you sure that you want to delete instance "' + id + '" ?'); + }, + moment: moment, + buttonText: { + yes: gettext('Delete'), + no: gettext('Cancel') + } + }); + dialog.result.then(function () { + that.hsmModel.deleteInstance(that.guid, id).then(function () { + that.deleting = true; + that.poll(true); + }); + }); + }, + + upgradeInstance: function () { + var that = this; + that.manageInstanceDialog.show('upgrade', that.guid, null, that.instance).result.then(function () { + that.poll(true); + }); + }, + + configureInstance: function () { + var that = this; + that.manageInstanceDialog.show('configure', that.guid, null, that.instance).result.then(function () { + that.poll(true); + }); + } + }); + + UpgradeController.$inject = [ + '$state', + '$q', + 'app.model.modelManager', + 'app.utils.utilsService', + 'service-manager.utils.version' + ]; + + function UpgradeController($state, $q, modelManager, utils, versionUtils) { + var that = this; + + this.versionUtils = versionUtils; + this.guid = $state.params.guid; + this.id = $state.params.id; + + function processUpgrades(instance) { + var upgrades = {}; + _.each(instance.available_upgrades, function (productUpgrade) { + var pUpgrade = {}; + upgrades[productUpgrade.product_version] = pUpgrade; + _.each(productUpgrade.sdl_versions, function (sdlUpgrade) { + pUpgrade[sdlUpgrade.sdl_version] = sdlUpgrade.is_latest; + }); + }); + return upgrades; + } + + function init() { + that.hsmModel = modelManager.retrieve('service-manager.model'); + + var instances = that.hsmModel.model[that.guid].instances; + var instance = _.find(instances, {instance_id: that.id}); + + if (!instance) { + return $q.reject('Instance with id \'' + that.id + '\' not found: '); + } + + var upgrades = processUpgrades(instance); + var services = that.hsmModel.model[that.guid].services; + var service = _.find(services, {id: instance.service_id}); + + if (!service) { + return $q.reject('Service with id \'' + instance.service_id + '\' not found: '); + } + + that.versions = service.product_versions; + _.each(that.versions, function (product) { + product.versions = []; + product.isUpgrade = _.has(upgrades, product.product_version); + _.each(product.sdl_versions, function (url, sdlVersion) { + var isUpgrade = upgrades[product.product_version] && _.has(upgrades[product.product_version], sdlVersion); + var isLatest = upgrades[product.product_version] && upgrades[product.product_version][sdlVersion]; + product.versions.push({ + sdl_version: sdlVersion, + isUpgrade: isUpgrade, + isLatest: isLatest, + isCurrent: instance.product_version === product.product_version && instance.sdl_version === sdlVersion + }); + }); + versionUtils.sortByProperty(product.versions, 'sdl_version', true); + }); + versionUtils.sortByProperty(that.versions, 'product_version', true); + } + + utils.chainStateResolve('sm.endpoint.instance.versions', $state, init); + + } + +})(); diff --git a/src/plugins/service-manager/view/service/instance-detail/instance-parameters.html b/src/plugins/service-manager/view/service/instance-detail/instance-parameters.html new file mode 100644 index 0000000000..81a3b3b229 --- /dev/null +++ b/src/plugins/service-manager/view/service/instance-detail/instance-parameters.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + +
    NameValue
    +
    +
    + + +
    +
    +
    {{ params.name }}{{ params.value }}
    diff --git a/src/plugins/service-manager/view/service/instance-detail/instance-scaling.html b/src/plugins/service-manager/view/service/instance-detail/instance-scaling.html new file mode 100644 index 0000000000..83c7050671 --- /dev/null +++ b/src/plugins/service-manager/view/service/instance-detail/instance-scaling.html @@ -0,0 +1,36 @@ +
    +
    No scaling metadata available.
    +
    + + + + + + + + + + + + + + + + + + +
    ComponentMin InstancesMax Instances
    +
    +
    + + +
    +
    +
    {{ scale.component }}{{ scale.min_instances }}{{ scale.max_instances }}
    diff --git a/src/plugins/service-manager/view/service/instance-detail/instance-services.html b/src/plugins/service-manager/view/service/instance-detail/instance-services.html new file mode 100644 index 0000000000..2f3526a176 --- /dev/null +++ b/src/plugins/service-manager/view/service/instance-detail/instance-services.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + +
    Service NamePort NameInternal LocationPublicPublic Location
    +
    +
    + + +
    +
    +
    {{ svc.service_name }}{{ svc.port_name }}{{ svc.internal_location }}:{{ svc.internal_port }}{{ svc.public }} + {{ svc.public_location }}:{{ svc.public_port }} + - +
    diff --git a/src/plugins/service-manager/view/service/instance-detail/instance-versions.html b/src/plugins/service-manager/view/service/instance-detail/instance-versions.html new file mode 100644 index 0000000000..4c72bdd01f --- /dev/null +++ b/src/plugins/service-manager/view/service/instance-detail/instance-versions.html @@ -0,0 +1,29 @@ +
    +
    No versions available.
    +
    + + + + + + + + + + + + + + +
    Product VersionSDL Versions
    {{ upgrade.product_version }} +
    + {{ sdl.sdl_version}} + (Latest) + (Current) +
    +
    diff --git a/src/plugins/service-manager/view/service/sdl-detail/sdl-detail.html b/src/plugins/service-manager/view/service/sdl-detail/sdl-detail.html new file mode 100644 index 0000000000..c08a9b19c5 --- /dev/null +++ b/src/plugins/service-manager/view/service/sdl-detail/sdl-detail.html @@ -0,0 +1,28 @@ +
    + +
    +
    +

    Service : {{ sdlCtrl.service.id }} : SDL {{ sdlCtrl.pv }} ({{ sdlCtrl.sv }})

    +
    + +
    +
    +
    +
    + + +
    +
    + +
    + +
    +
    + +
    +
    +
    diff --git a/src/plugins/service-manager/view/service/sdl-detail/sdl-detail.module.js b/src/plugins/service-manager/view/service/sdl-detail/sdl-detail.module.js new file mode 100644 index 0000000000..285d5ac993 --- /dev/null +++ b/src/plugins/service-manager/view/service/sdl-detail/sdl-detail.module.js @@ -0,0 +1,137 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.view.service.sdl-detail', []) + .config(registerRoute); + + registerRoute.$inject = [ + '$stateProvider' + ]; + + function registerRoute($stateProvider) { + $stateProvider.state('sm.endpoint.service.sdl', { + url: '/sdl/:product/:sdl', + templateUrl: 'plugins/service-manager/view/service/sdl-detail/sdl-detail.html', + controller: ServiceManagerSdlDetailController, + controllerAs: 'sdlCtrl', + ncyBreadcrumb: { + label: '{{ sdlCtrl.pv }} ({{ sdlCtrl.sv }})', + parent: 'sm.endpoint.service.detail' + }, + data: { + activeMenuState: 'sm.list' + } + }); + } + + ServiceManagerSdlDetailController.$inject = [ + '$stateParams', + '$state', + '$q', + 'app.utils.utilsService', + 'app.model.modelManager' + ]; + + function ServiceManagerSdlDetailController($stateParams, $state, $q, utils, modelManager) { + var that = this; + that.loading = true; + + this.initialized = false; + this.guid = $stateParams.guid; + this.id = $stateParams.id; + this.pv = $stateParams.product; + this.sv = $stateParams.sdl; + + function init() { + that.hsmModel = modelManager.retrieve('service-manager.model'); + + var services = that.hsmModel.model[that.guid].services; + that.service = _.find(services, {id: that.id}); + + if (!that.service) { + return $q.reject('Service with id \'' + that.id + '\' not found: '); + } + + return that.hsmModel.getServiceSdl(that.guid, that.id, that.pv, that.sv) + .then(function (sdl) { + return that.hsmModel.getTemplate(that.guid, sdl).then(function (template) { + that.template = template; + }); + }) + .finally(function () { + that.initialized = true; + that.loading = false; + that.json = that.template; + }); + } + + utils.chainStateResolve('sm.endpoint.service.sdl', $state, init); + + } + + angular.extend(ServiceManagerSdlDetailController.prototype, { + + filterSDL: function () { + this.json = this.applyFilter(this.template, this.filter); + }, + + escapeRegExp: function (str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + }, + + applyFilter: function (source, filter) { + if (!filter) { + return source; + } + + var json = {}; + var regex = new RegExp(this.escapeRegExp(filter.toLowerCase())); + json = this.filterObject(source, regex); + return json; + }, + + include: function (value) { + if (_.isArray(value)) { + return value.length; + } else if (_.isObject(value)) { + /* eslint-disable angular/no-private-call */ + var min = value.$$hashKey ? 1 : 0; + /* eslint-enable angular/no-private-call */ + return Object.keys(value).length > min; + } else { + return value; + } + }, + + filterObject: function (source, regex) { + var that = this; + if (_.isArray(source)) { + var destArray = []; + _.each(source, function (value) { + var found = that.filterObject(value, regex); + + if (that.include(found)) { + destArray.push(found); + } + }); + return destArray; + } else if (_.isObject(source)) { + var dest = {}; + _.each(source, function (value, key) { + var found = that.filterObject(value, regex); + if (that.include(found)) { + dest[key] = found; + } + }); + return dest; + } else { + var value = source.toString().toLowerCase(); + if (regex.test(value)) { + return source; + } + return undefined; + } + } + }); +})(); diff --git a/src/plugins/service-manager/view/service/service-detail/service-detail.html b/src/plugins/service-manager/view/service/service-detail/service-detail.html new file mode 100644 index 0000000000..452240da8f --- /dev/null +++ b/src/plugins/service-manager/view/service/service-detail/service-detail.html @@ -0,0 +1,113 @@ +
    + +
    +
    +

    Service : {{ sCtrl.service.id }}

    +
    + +
    +
    +
    +
    +
    + Summary + +
    + +
    +
    +
    +
    Name
    +
    + {{ sCtrl.service.name }} +
    +
    Catalog ID
    +
    + {{ sCtrl.service.catalogId }} +
    +
    Type
    +
    + {{ sCtrl.service.type }} +
    +
    Vendor
    +
    + {{ sCtrl.service.vendor }} +
    +
    +
    +
    +
    +
    Labels
    +
    + {{ label }} +
    +
    Categories
    +
    + {{ category }} +
    + + +
    Description
    +
    +
    + {{ sCtrl.service.description }} +
    +
    +
    +
    +
    +
    + +

    Versions

    + + + + + + + + + + + + + + + + + +
    Product VersionSDL Version
    +
    +
    + + +
    +
    +
    {{ svc.product_version }} + + {{ svc.sdl_version }}
    +
    + +
    + diff --git a/src/plugins/service-manager/view/service/service-detail/service-detail.module.js b/src/plugins/service-manager/view/service/service-detail/service-detail.module.js new file mode 100644 index 0000000000..b4d8393789 --- /dev/null +++ b/src/plugins/service-manager/view/service/service-detail/service-detail.module.js @@ -0,0 +1,98 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.view.service.service-detail', []) + .config(registerRoute); + + registerRoute.$inject = [ + '$stateProvider' + ]; + + function registerRoute($stateProvider) { + + $stateProvider.state('sm.endpoint.service', { + url: '/service/:id', + template: '', + controller: ServiceManagerServiceController, + controllerAs: 'svCtrl', + abstract: true, + data: { + activeMenuState: 'sm.list' + } + }); + + $stateProvider.state('sm.endpoint.service.detail', { + url: '/detail', + templateUrl: 'plugins/service-manager/view/service/service-detail/service-detail.html', + controller: ServiceManagerServiceDetailController, + controllerAs: 'sCtrl', + ncyBreadcrumb: { + label: '{{ svCtrl.id || "..." }}', + parent: 'sm.endpoint.detail.services' + }, + data: { + activeMenuState: 'sm.list' + } + }); + } + + ServiceManagerServiceController.$inject = [ + '$state' + ]; + + function ServiceManagerServiceController($state) { + this.id = $state.params.id; + } + + ServiceManagerServiceDetailController.$inject = [ + '$stateParams', + '$state', + '$q', + 'app.utils.utilsService', + 'app.model.modelManager' + ]; + + function ServiceManagerServiceDetailController($stateParams, $state, $q, utils, modelManager) { + var that = this; + + this.initialized = false; + this.guid = $stateParams.guid; + this.userServiceInstanceModel = modelManager.retrieve('app.model.serviceInstance.user'); + + this.hsmModel = modelManager.retrieve('service-manager.model'); + this.stackatoInfo = modelManager.retrieve('app.model.stackatoInfo'); + + this.getEndpoint = function () { + return utils.getClusterEndpoint(that.endpoint); + }; + + var guid = $state.params.guid; + this.id = $state.params.id; + + function init() { + that.hsmModel = modelManager.retrieve('service-manager.model'); + + var services = that.hsmModel.model[guid].services; + that.service = _.find(services, {id: that.id}); + + if (!that.service) { + return $q.reject('Service with id \'' + that.id + '\' not found: '); + } + + that.versions = []; + _.each(that.service.product_versions, function (product) { + _.each(product.sdl_versions, function (sdl, sdlVersion) { + that.versions.push({ + product_version: product.product_version, + sdl_version: sdlVersion, + latest: sdlVersion === product.latest + }); + }); + }); + } + + utils.chainStateResolve('sm.endpoint.service.detail', $state, init); + } + +})(); diff --git a/src/plugins/service-manager/view/service/service-manager.module.js b/src/plugins/service-manager/view/service/service-manager.module.js new file mode 100644 index 0000000000..776392a5d9 --- /dev/null +++ b/src/plugins/service-manager/view/service/service-manager.module.js @@ -0,0 +1,91 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.view.service', [ + 'service-manager.view.service.detail', + 'service-manager.view.service.detail.instances', + 'service-manager.view.service.detail.services', + 'service-manager.view.service.sdl-detail', + 'ncy-angular-breadcrumb' + ]) + .config(registerRoute); + + registerRoute.$inject = [ + '$stateProvider' + ]; + + function registerRoute($stateProvider) { + $stateProvider.state('sm.endpoint', { + url: '/:guid', + abstract: true, + template: '', + controller: ServiceManagerController, + controllerAs: 'smCtrl' + }); + } + + ServiceManagerController.$inject = [ + '$stateParams', + '$state', + 'app.utils.utilsService', + 'app.model.modelManager', + 'service-manager.view.manage-instance.dialog' + ]; + + function ServiceManagerController($stateParams, $state, utils, modelManager, manageInstanceDialog) { + var that = this; + + this.initialized = false; + this.guid = $stateParams.guid; + this.$state = $state; + this.manageInstanceDialog = manageInstanceDialog; + + this.getEndpoint = function () { + return utils.getClusterEndpoint(that.endpoint); + }; + + function init() { + that.userServiceInstanceModel = modelManager.retrieve('app.model.serviceInstance.user'); + that.hsmModel = modelManager.retrieve('service-manager.model'); + that.stackatoInfo = modelManager.retrieve('app.model.stackatoInfo'); + + return that.stackatoInfo.getStackatoInfo().then(function (info) { + that.endpoint = info.endpoints.hsm[that.guid]; + + return that.hsmModel.getModel(that.guid).then(function (model) { + that.model = model; + }); + }); + } + + utils.chainStateResolve('sm.endpoint', $state, init); + } + + angular.extend(ServiceManagerController.prototype, { + + hasUpgrade: function (instanceId, noIgnore) { + return noIgnore ? this.hsmModel.hasUpgradeAvailable(this.guid, instanceId) : this.hsmModel.hasUpgrade(this.guid, instanceId); + }, + + endpointHasUpgrades: function () { + return this.hsmModel.endpointHasUpgrades(this.guid); + }, + + createInstance: function (serviceId, productVersion, sdlVersion) { + var that = this; + + that.manageInstanceDialog.show('create', that.guid, _.keyBy(that.hsmModel.model[that.guid].services, 'id'), + serviceId, productVersion, sdlVersion).result + .then(function (instance) { + // Open the instance once it has been created + that.$state.go('sm.endpoint.instance.components', {guid: that.guid, id: instance.instance_id}); + }); + }, + + acknowledgeUpgrade: function (instanceId) { + this.hsmModel.clearUpgrades(this.guid, instanceId); + } + }); + +})(); diff --git a/src/plugins/service-manager/view/tiles/manager/service-manager-tile.directive.js b/src/plugins/service-manager/view/tiles/manager/service-manager-tile.directive.js new file mode 100644 index 0000000000..dff26d2cd5 --- /dev/null +++ b/src/plugins/service-manager/view/tiles/manager/service-manager-tile.directive.js @@ -0,0 +1,102 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.view.tiles') + .directive('serviceManagerTile', ServiceManagerTile); + + ServiceManagerTile.$inject = []; + + function ServiceManagerTile() { + return { + bindToController: { + service: '=' + }, + controller: ServiceManagerTileController, + controllerAs: 'tileCtrl', + scope: {}, + templateUrl: 'plugins/service-manager/view/tiles/manager/service-manager-tile.html' + }; + } + + ServiceManagerTileController.$inject = [ + '$scope', + '$state', + 'app.model.modelManager', + 'app.utils.utilsService' + ]; + + /** + * @name ServiceManagerTileController + * @constructor + * @param {object} $scope - the angular $scope service + * @param {object} $state - the angular $state service + * @param {app.model.modelManager} modelManager - the Model management service + * @param {app.utils.utilsService} utils - the console utils service + */ + function ServiceManagerTileController($scope, $state, modelManager, utils) { + var that = this; + + this.$state = $state; + this.instancesCount = null; + this.servicesCount = null; + this.userService = {}; + + var cardData = {}; + var expiredStatus = { + classes: 'danger', + icon: 'helion-icon-lg helion-icon helion-icon-Critical_S', + description: gettext('Token has expired') + }; + + var erroredStatus = { + classes: 'danger', + icon: 'helion-icon-lg helion-icon helion-icon-Critical_S', + description: gettext('Cannot contact endpoint') + }; + + cardData.title = this.service.name; + this.getCardData = function () { + if (this.userService.error) { + cardData.status = erroredStatus; + } else if (that.service.hasExpired) { + cardData.status = expiredStatus; + } else { + delete cardData.status; + } + return cardData; + }; + + function init() { + var userServiceInstanceModel = modelManager.retrieve('app.model.serviceInstance.user'); + var serviceManagerModel = modelManager.retrieve('service-manager.model'); + $scope.$watch(function () { return that.service; }, function (newVal) { + if (!newVal) { + return; + } + that.userService = userServiceInstanceModel.serviceInstances[that.service.guid] || {}; + serviceManagerModel.getModel(that.service.guid).then(function (model) { + that.instancesCount = model.instances.length; + that.servicesCount = model.services.length; + }); + }); + } + + utils.chainStateResolve('sm.tiles', $state, init); + } + + angular.extend(ServiceManagerTileController.prototype, { + + /** + * @namespace app.view.endpoints.clusters + * @memberof app.view.endpoints.clusters + * @name summary + * @description Navigate to the cluster summary page for this cluster + */ + summary: function () { + this.$state.go('sm.endpoint.detail.instances', {guid: this.service.guid}); + } + + }); + +})(); diff --git a/src/plugins/service-manager/view/tiles/manager/service-manager-tile.html b/src/plugins/service-manager/view/tiles/manager/service-manager-tile.html new file mode 100644 index 0000000000..bc64ffff61 --- /dev/null +++ b/src/plugins/service-manager/view/tiles/manager/service-manager-tile.html @@ -0,0 +1,23 @@ + + +
    +
    ENDPOINT URL
    +
    {{tileCtrl.service.api_endpoint.Host}}
    +
    ACCOUNT STATUS
    + +
    INSTANCES
    +
    {{tileCtrl.instancesCount}}
    +
    SERVICES
    +
    {{tileCtrl.servicesCount}}
    +
    +
    +
    + Connect + {{ ' to this endpoint.' | translate }}
    + ENDPOINT URL {{tileCtrl.service.api_endpoint.Host}}
    +
    +
    diff --git a/src/plugins/service-manager/view/tiles/service-manager-tiles.html b/src/plugins/service-manager/view/tiles/service-manager-tiles.html new file mode 100644 index 0000000000..3e8c996d42 --- /dev/null +++ b/src/plugins/service-manager/view/tiles/service-manager-tiles.html @@ -0,0 +1,34 @@ + +
    +
    +
    + +
    +
    +
    An error occurred retrieving endpoints.
    +
    +
    +
    +
    No connected {{ OEM_CONFIG.SERVICE_MANAGER }} endpoints.
    +
    + Use the Endpoints Dashboard + to fix your connections or connect to new endpoints. +
    +
    + Please contact your administrator. +
    +
    +
    +
    +
    + {{ OEM_CONFIG.SERVICE_MANAGER }} +
    +
    + +
    +
    +
    diff --git a/src/plugins/service-manager/view/tiles/service-manager-tiles.module.js b/src/plugins/service-manager/view/tiles/service-manager-tiles.module.js new file mode 100644 index 0000000000..fa6067dc02 --- /dev/null +++ b/src/plugins/service-manager/view/tiles/service-manager-tiles.module.js @@ -0,0 +1,143 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.view.tiles', []) + .config(registerRoute); + + registerRoute.$inject = [ + '$stateProvider' + ]; + + function registerRoute($stateProvider) { + $stateProvider.state('sm.tiles', { + url: '/tiles', + templateUrl: 'plugins/service-manager/view/tiles/service-manager-tiles.html', + controller: ServiceManagerTilesController, + controllerAs: 'epCtrl', + params: { + // param set by Router module to prevent relisting + // of servicesInstances and userServiceInstances + instancesListed: false + }, + ncyBreadcrumb: { + label: '{{ OEM_CONFIG.SERVICE_MANAGER }}', + parent: 'endpoint.dashboard' + }, + data: { + activeMenuState: 'sm.list' + } + }); + } + + ServiceManagerTilesController.$inject = [ + '$q', + '$state', + '$stateParams', + 'app.model.modelManager', + 'app.utils.utilsService' + ]; + + /** + * @name ServiceManagerTilesController + * @constructor + * @param {object} $q - the angular $q service + * @param {object} $state - the UI router $state service + * @param {$stateParams} $stateParams - UI Router state params + * @param {app.model.modelManager} modelManager - the Model management service + * @param {app.utils.utilsService} utils - the utils service + * @property {object} $q - the angular $q service + * @property {object} $state - the UI router $state service + * @property {$stateParams} $stateParams - UI Router state params + * @property {app.model.modelManager} modelManager - the Model management service + * @property {app.utils.utilsService} utils - the utils service + */ + function ServiceManagerTilesController($q, $state, $stateParams, modelManager, utils) { + var that = this; + this.modelManager = modelManager; + + this.$q = $q; + this.$state = $state; + this.$stateParams = $stateParams; + this.serviceInstanceModel = modelManager.retrieve('app.model.serviceInstance'); + this.userServiceInstanceModel = modelManager.retrieve('app.model.serviceInstance.user'); + this.stackatoInfo = modelManager.retrieve('app.model.stackatoInfo'); + this.authModel = modelManager.retrieve('cloud-foundry.model.auth'); + this.currentUserAccount = modelManager.retrieve('app.model.account'); + this.serviceInstances = {}; + this.state = ''; + + function init() { + return that.refreshEndpointModel(); + } + + utils.chainStateResolve('sm.tiles', $state, init); + + } + + angular.extend(ServiceManagerTilesController.prototype, { + /** + * @memberof service-manager.view.tiles + * @name createEndpointList + * @description Create the list of Service Manager Endpoints + determine their connected status + */ + createEndpointList: function () { + var that = this; + this.serviceInstances = {}; + var filteredInstances = _.filter(this.serviceInstanceModel.serviceInstances, {cnsi_type: 'hsm'}); + _.forEach(filteredInstances, function (serviceInstance) { + var cloned = angular.fromJson(angular.toJson(serviceInstance)); + cloned.isConnected = _.get(that.userServiceInstanceModel.serviceInstances[cloned.guid], 'valid', false); + + if (cloned.isConnected) { + cloned.hasExpired = false; + that.serviceInstances[cloned.guid] = cloned; + } + }); + this.updateState(false, false); + }, + + /** + * @memberof service-manager.view.tiles + * @name refreshEndpointModel + * @description Update the core model data + create the list of Service Manager Endpoints + * @returns {promise} refresh endpoints promise + */ + refreshEndpointModel: function () { + var that = this; + this.updateState(true, false); + + var promises = [this.stackatoInfo.getStackatoInfo()]; + if (!that.$stateParams.instancesListed) { + promises = promises.concat([this.serviceInstanceModel.list(), this.userServiceInstanceModel.list()]); + } + return this.$q.all(promises) + .then(function () { + that.createEndpointList(); + }) + .catch(function () { + that.updateState(false, true); + }); + }, + + /** + * @memberof service-manager.view.tiles + * @name updateState + * @description Determine the state of the model + * @param {boolean} loading true if loading async data + * @param {boolean} loadError true if the async load of data failed + */ + updateState: function (loading, loadError) { + var hasEndpoints = _.get(_.keys(this.serviceInstances), 'length', 0) > 0; + if (hasEndpoints) { + this.state = ''; + } else if (loading) { + this.state = 'loading'; + } else if (loadError) { + this.state = 'loadError'; + } else { + this.state = 'noClusters'; + } + } + }); +})(); diff --git a/src/plugins/service-manager/view/view.module.js b/src/plugins/service-manager/view/view.module.js new file mode 100644 index 0000000000..67d7e84f5b --- /dev/null +++ b/src/plugins/service-manager/view/view.module.js @@ -0,0 +1,90 @@ +(function () { + 'use strict'; + + angular + .module('service-manager.view', [ + 'service-manager.view.tiles', + 'service-manager.view.service', + 'service-manager.view.service.instance-detail', + 'service-manager.view.service.service-detail', + 'service-manager.view.manage-instance' + ]) + .config(registerRoute); + + registerRoute.$inject = [ + '$stateProvider' + ]; + + function registerRoute($stateProvider) { + + $stateProvider.state('sm', { + url: '/sm', + abstract: true, + template: '', + data: { + activeMenuState: 'sm.list' + } + }); + + $stateProvider.state('sm.list', { + url: '/sm', + template: '', + controller: ServicesManagersRouterController, + controllerAs: 'smRouterCtrl', + ncyBreadcrumb: { + skip: true + } + }); + } + + ServicesManagersRouterController.$inject = [ + '$q', + '$state', + 'app.model.modelManager', + 'app.utils.utilsService' + ]; + + /** + * @name ServicesManagersRouterController + * @description Redirects the user to either the Tiles page ot the detail + * page for a given HSM Detail page or depending on the number of HSM instances connected. + * @param {object} $q - the Angular $q service + * @param {object} $state - the UI router $state service + * @param {app.model.modelManager} modelManager - the Model management service + * @param {app.utils.utilsService} utils - the utils service + * @constructor + */ + function ServicesManagersRouterController($q, $state, modelManager, utils) { + var that = this; + this.modelManager = modelManager; + this.$q = $q; + this.serviceInstanceModel = modelManager.retrieve('app.model.serviceInstance'); + this.userServiceInstanceModel = modelManager.retrieve('app.model.serviceInstance.user'); + + function init() { + return that.$q.all([that.serviceInstanceModel.list(), that.userServiceInstanceModel.list()]) + .then(function () { + var connectedInstances = 0; + var serviceInstanceGuid; + var hcfInstances = _.filter(that.serviceInstanceModel.serviceInstances, {cnsi_type: 'hsm'}); + _.forEach(hcfInstances, function (hcfInstance) { + if (_.get(that.userServiceInstanceModel.serviceInstances[hcfInstance.guid], 'valid', false)) { + serviceInstanceGuid = hcfInstance.guid; + connectedInstances += 1; + } + }); + + if (connectedInstances === 1) { + $state.go('sm.endpoint.detail.instances', {guid: serviceInstanceGuid}); + } else { + $state.go('sm.tiles', {instancesListed: true}); + } + }); + } + + utils.chainStateResolve('sm.list', $state, init); + } + + angular.extend(ServicesManagersRouterController.prototype, {}); + +})();