diff --git a/.gitignore b/.gitignore index 58ce7a7503..ea05b88e55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +*.iml npm-debug.log phantomjsdriver.log node_modules diff --git a/src/app/view/endpoints/clusters/cluster/cluster.html b/src/app/view/endpoints/clusters/cluster/cluster.html index 3b1d2ae619..e707747882 100644 --- a/src/app/view/endpoints/clusters/cluster/cluster.html +++ b/src/app/view/endpoints/clusters/cluster/cluster.html @@ -1,15 +1,5 @@
-
diff --git a/src/app/view/endpoints/clusters/cluster/cluster.module.js b/src/app/view/endpoints/clusters/cluster/cluster.module.js index 73e41c6aba..8285f38b2a 100644 --- a/src/app/view/endpoints/clusters/cluster/cluster.module.js +++ b/src/app/view/endpoints/clusters/cluster/cluster.module.js @@ -29,19 +29,16 @@ 'app.utils.utilsService', '$state', '$q', - 'app.model.modelManager', - 'helion.framework.widgets.asyncTaskDialog', - 'app.view.endpoints.clusters.cluster.assignUsers' + 'app.model.modelManager' ]; - function ClusterController($stateParams, $log, utils, $state, $q, modelManager, asyncTaskDialog, assignUsers) { + function ClusterController($stateParams, $log, utils, $state, $q, modelManager) { var that = this; var organizationModel = modelManager.retrieve('cloud-foundry.model.organization'); var serviceBindingModel = modelManager.retrieve('cloud-foundry.model.service-binding'); var privateDomains = modelManager.retrieve('cloud-foundry.model.private-domain'); var sharedDomains = modelManager.retrieve('cloud-foundry.model.shared-domain'); var appModel = modelManager.retrieve('cloud-foundry.model.application'); - var stackatoInfo = modelManager.retrieve('app.model.stackatoInfo'); this.initialized = false; this.guid = $stateParams.guid; @@ -51,72 +48,6 @@ return utils.getClusterEndpoint(that.userServiceInstanceModel.serviceInstances[that.guid]); }; - this.clusterActions = [ - { - name: gettext('Create Organization'), - disabled: true, - execute: function () { - return asyncTaskDialog( - { - title: gettext('Create Organization'), - templateUrl: 'app/view/endpoints/clusters/cluster/detail/actions/create-organization.html', - buttonTitles: { - submit: gettext('Create') - } - }, - { - data: { - // Make the form invalid if the name is already taken - organizationNames: organizationModel.organizationNames[that.guid] - } - }, - function (orgData) { - if (orgData.name && orgData.name.length > 0) { - return organizationModel.createOrganization(that.guid, orgData.name); - } else { - return $q.reject('Invalid Name!'); - } - - } - ); - }, - icon: 'helion-icon-lg helion-icon helion-icon-Tree' - }, - { - name: gettext('Create Space'), - disabled: true, - execute: function () { - }, - icon: 'helion-icon-lg helion-icon helion-icon-Tree' - }, - { - name: gettext('Assign User(s)'), - disabled: true, - execute: function () { - assignUsers.assign({ - selectedUsers: {} - }); - }, - icon: 'helion-icon-lg helion-icon helion-icon-Add_user' - } - ]; - - /** - * Enable actions based on admin status - * N.B. when finer grain ACLs are wired in this should be updated - * */ - function enableActions() { - if (stackatoInfo.info.endpoints.hcf[that.guid].user.admin) { - _.forEach(that.clusterActions, function (action) { - action.disabled = false; - }); - // Disable these until implemented! - that.clusterActions[1].disabled = true; - } - that.clusterActions[2].disabled = - that.clusterActions[2].disabled || _.keys(organizationModel.organizations).length < 1; - } - function init() { // Cache all organizations associated with this cluster @@ -130,7 +61,6 @@ allDetailsP.push(orgDetailsP); }); return $q.all(allDetailsP).then(function (val) { - enableActions(); that.organizationNames = organizationModel.organizationNames[that.guid]; return val; }); diff --git a/src/app/view/endpoints/clusters/cluster/cluster.scss b/src/app/view/endpoints/clusters/cluster/cluster.scss index c33a94ec1e..6842e24c97 100644 --- a/src/app/view/endpoints/clusters/cluster/cluster.scss +++ b/src/app/view/endpoints/clusters/cluster/cluster.scss @@ -40,6 +40,11 @@ text-align: center; } + .cluster-actions-menu { + position: absolute; + right: 24px; // ok with scroll bar on, no time find a way to satisfy both with and without the scrollbar + } + .cluster-detail { padding-left: $hpe-unit-space; diff --git a/src/app/view/endpoints/clusters/cluster/detail/actions/cluster-actions.directive.js b/src/app/view/endpoints/clusters/cluster/detail/actions/cluster-actions.directive.js new file mode 100644 index 0000000000..6fdc72ada7 --- /dev/null +++ b/src/app/view/endpoints/clusters/cluster/detail/actions/cluster-actions.directive.js @@ -0,0 +1,257 @@ +(function () { + 'use strict'; + + angular + .module('app.view.endpoints') + .directive('clusterActions', ClusterActions) + .directive('uniqueSpaceName', UniqueSpaceName); + + // Define contextData here so it's available to both directives + var contextData; + + ClusterActions.$inject = []; + + function ClusterActions() { + return { + restrict: 'E', + bindToController: true, + controller: ClusterActionsController, + controllerAs: 'clusterActionsCtrl', + scope: { + // stateName: '@' + }, + templateUrl: 'app/view/endpoints/clusters/cluster/detail/actions/cluster-actions.html' + }; + } + + ClusterActionsController.$inject = [ + 'app.model.modelManager', + '$state', + '$q', + '$stateParams', + 'app.utils.utilsService', + 'helion.framework.widgets.asyncTaskDialog', + 'app.view.endpoints.clusters.cluster.assignUsers' + ]; + + /** + * @name OrganizationTileController + * @constructor + * @param {app.model.modelManager} modelManager - the model management service + * @param {object} $state - the angular $state service + * @param {object} $q - the angular $q service + * @param {object} $stateParams - the ui-router $stateParams service + * @param {object} utils - our utils service + * @param {object} asyncTaskDialog - our async dialog service + * @param {object} assignUsersService - service that allows assigning roles to users + * @property {Array} actions - collection of relevant actions that can be executed against cluster + */ + function ClusterActionsController(modelManager, $state, $q, $stateParams, utils, asyncTaskDialog, assignUsersService) { + var that = this; + var stackatoInfo = modelManager.retrieve('app.model.stackatoInfo'); + var organizationModel = modelManager.retrieve('cloud-foundry.model.organization'); + var spaceModel = modelManager.retrieve('cloud-foundry.model.space'); + + this.stateName = $state.current.name; + this.clusterGuid = $stateParams.guid; + + function getOrgName(org) { + return _.get(org, 'details.org.entity.name'); + } + + function getExistingSpaceNames(orgGuid) { + var orgSpaces = organizationModel.organizations[that.clusterGuid][orgGuid].spaces; + return _.map(orgSpaces, function (space) { + return space.entity.name; + }); + } + + var createOrg = { + name: gettext('Create Organization'), + disabled: true, + execute: function () { + return asyncTaskDialog( + { + title: gettext('Create Organization'), + templateUrl: 'app/view/endpoints/clusters/cluster/detail/actions/create-organization.html', + buttonTitles: { + submit: gettext('Create') + } + }, + { + data: { + // Make the form invalid if the name is already taken + organizationNames: organizationModel.organizationNames[that.clusterGuid] + } + }, + function (orgData) { + if (orgData.name && orgData.name.length > 0) { + return organizationModel.createOrganization(that.clusterGuid, orgData.name); + } else { + return $q.reject('Invalid Name!'); + } + + } + ); + }, + icon: 'helion-icon-lg helion-icon helion-icon-Tree' + }; + + var createSpace = { + name: gettext('Create Space'), + disabled: true, + execute: function () { + + var existingSpaceNames, selectedOrg; + + // Context-sensitively pre-select the correct organization + if ($stateParams.organization) { + selectedOrg = organizationModel.organizations[that.clusterGuid][$stateParams.organization]; + } else { + // Pre-select the most recently created organization + var sortedOrgs = _.sortBy(organizationModel.organizations[that.clusterGuid], function (org) { + return -org.details.created_at; + }); + selectedOrg = sortedOrgs[0]; + } + + existingSpaceNames = getExistingSpaceNames(selectedOrg.details.guid); + + function setOrganization() { + contextData.existingSpaceNames = getExistingSpaceNames(contextData.organization.details.guid); + } + + function createSpaceDisabled() { + if (contextData.spaces.length >= 10) { + return true; + } + // Make sure all spaces have a valid name before allowing creating another + for (var i = 0; i < contextData.spaces.length; i++) { + var spaceName = contextData.spaces[i]; + if (angular.isUndefined(spaceName) || spaceName.length < 1) { + return true; + } + } + return false; + } + + function addSpace() { + if (createSpaceDisabled()) { + return; + } + contextData.spaces.push(''); + } + + function removeSpace() { + contextData.spaces.length--; + } + + contextData = { + organization: selectedOrg, + organizations: _.map(organizationModel.organizations[that.clusterGuid], function (org) { + return { + label: getOrgName(org), + value: org + }; + }), + existingSpaceNames: existingSpaceNames, + spaces: [''], + setOrganization: setOrganization, + createSpaceDisabled: createSpaceDisabled, + addSpace: addSpace, + removeSpace: removeSpace + }; + + return asyncTaskDialog( + { + title: gettext('Create Space'), + templateUrl: 'app/view/endpoints/clusters/cluster/detail/actions/create-space.html', + buttonTitles: { + submit: gettext('Create') + } + }, + { + data: contextData + }, + function () { + var toCreate = []; + for (var i = 0; i < contextData.spaces.length; i++) { + var name = contextData.spaces[i]; + if (angular.isDefined(name) && name.length > 0) { + toCreate.push(name); + } + } + if (toCreate.length < 1) { + return $q.reject('Nothing to create!'); + } + return spaceModel.createSpaces(that.clusterGuid, contextData.organization.details.guid, toCreate); + } + ); + }, + icon: 'helion-icon-lg helion-icon helion-icon-Tree' + }; + + var assignUsers = { + name: gettext('Assign User(s)'), + disabled: true, + execute: function () { + assignUsersService.assign({ + selectedUsers: {} + }); + }, + icon: 'helion-icon-lg helion-icon helion-icon-Add_user' + }; + + this.clusterActions = [ + createOrg, + createSpace, + assignUsers + ]; + + /** + * Enable actions based on admin status + * N.B. when finer grain ACLs are wired in this should be updated + * */ + function enableActions() { + if (stackatoInfo.info.endpoints.hcf[that.clusterGuid].user.admin) { + _.forEach(that.clusterActions, function (action) { + action.disabled = false; + }); + } + } + + function init() { + enableActions(); + return $q.resolve(); + } + + utils.chainStateResolve(this.stateName, $state, init); + } + + UniqueSpaceName.$inject = []; + + // private validator to ensure there are no duplicates within the list of new names + function UniqueSpaceName() { + return { + require: 'ngModel', + link: function (scope, elm, attrs, ctrl) { + var index = parseInt(attrs.uniqueSpaceName, 10); + ctrl.$validators.dupeName = function (modelValue, viewValue) { + if (ctrl.$isEmpty(modelValue)) { + return true; + } + for (var i = 0; i < contextData.spaces.length; i++) { + if (index === i) { + continue; + } + var name = contextData.spaces[i]; + if (modelValue === name) { + return false; + } + } + return true; + }; + } + }; + } +})(); diff --git a/src/app/view/endpoints/clusters/cluster/detail/actions/cluster-actions.html b/src/app/view/endpoints/clusters/cluster/detail/actions/cluster-actions.html new file mode 100644 index 0000000000..10c12697d3 --- /dev/null +++ b/src/app/view/endpoints/clusters/cluster/detail/actions/cluster-actions.html @@ -0,0 +1,14 @@ + + + diff --git a/src/app/view/endpoints/clusters/cluster/detail/actions/create-organization.html b/src/app/view/endpoints/clusters/cluster/detail/actions/create-organization.html index d028c5c133..d87d1e60f1 100644 --- a/src/app/view/endpoints/clusters/cluster/detail/actions/create-organization.html +++ b/src/app/view/endpoints/clusters/cluster/detail/actions/create-organization.html @@ -5,6 +5,5 @@ - {{ asyncTaskDialogCtrl.context.data.organizations }} diff --git a/src/app/view/endpoints/clusters/cluster/detail/actions/create-space.html b/src/app/view/endpoints/clusters/cluster/detail/actions/create-space.html new file mode 100644 index 0000000000..dee12b3a9e --- /dev/null +++ b/src/app/view/endpoints/clusters/cluster/detail/actions/create-space.html @@ -0,0 +1,38 @@ +
+
+ + + +
+ +
+ + +
+ +
+ + + Create Another Space + + + + Remove Last Space + +
+ +
diff --git a/src/app/view/endpoints/clusters/cluster/detail/actions/create-space.scss b/src/app/view/endpoints/clusters/cluster/detail/actions/create-space.scss new file mode 100644 index 0000000000..6334119c9f --- /dev/null +++ b/src/app/view/endpoints/clusters/cluster/detail/actions/create-space.scss @@ -0,0 +1,14 @@ +.create-space-form { + + div.organizations-drop-down { + margin-bottom: 30px; + } + + .create-space-button { + padding-left: 0; + padding-right: 10px; + font-size: 16px; + text-transform: none; + } + +} diff --git a/src/app/view/endpoints/clusters/cluster/detail/cluster-detail.html b/src/app/view/endpoints/clusters/cluster/detail/cluster-detail.html index 596669a264..4c2ff33fd9 100644 --- a/src/app/view/endpoints/clusters/cluster/detail/cluster-detail.html +++ b/src/app/view/endpoints/clusters/cluster/detail/cluster-detail.html @@ -53,5 +53,5 @@

{{ clusterController.userServiceInstanceModel.serviceInstances[clusterContro - + diff --git a/src/app/view/endpoints/clusters/cluster/detail/cluster-detail.scss b/src/app/view/endpoints/clusters/cluster/detail/cluster-detail.scss index 3677dffd58..d03541580a 100644 --- a/src/app/view/endpoints/clusters/cluster/detail/cluster-detail.scss +++ b/src/app/view/endpoints/clusters/cluster/detail/cluster-detail.scss @@ -1,4 +1,5 @@ -@import 'organization-tile/organization-tile'; +@import 'organization-tile/organization-tile', + 'actions/create-space.scss'; .cluster-detail { diff --git a/src/app/view/endpoints/clusters/cluster/organization/detail/cluster-organization-detail.html b/src/app/view/endpoints/clusters/cluster/organization/detail/cluster-organization-detail.html index 0dda0ff941..3cbcb0fafd 100644 --- a/src/app/view/endpoints/clusters/cluster/organization/detail/cluster-organization-detail.html +++ b/src/app/view/endpoints/clusters/cluster/organization/detail/cluster-organization-detail.html @@ -23,5 +23,5 @@

Organization : {{ clusterOrgDetailController.organization().details.org.enti - + diff --git a/src/app/view/endpoints/clusters/cluster/organization/space/cluster-space.module.js b/src/app/view/endpoints/clusters/cluster/organization/space/cluster-space.module.js index 90ef4bcc1f..eb245c2d67 100644 --- a/src/app/view/endpoints/clusters/cluster/organization/space/cluster-space.module.js +++ b/src/app/view/endpoints/clusters/cluster/organization/space/cluster-space.module.js @@ -21,11 +21,9 @@ }); } - ClusterSpaceController.$inject = [ ]; + ClusterSpaceController.$inject = []; - function ClusterSpaceController() { - - } + function ClusterSpaceController() {} angular.extend(ClusterSpaceController.prototype, {}); })(); diff --git a/src/app/view/endpoints/clusters/cluster/organization/space/detail/cluster-space-detail.html b/src/app/view/endpoints/clusters/cluster/organization/space/detail/cluster-space-detail.html index 3022a4c79c..0b7d37ebb4 100644 --- a/src/app/view/endpoints/clusters/cluster/organization/space/detail/cluster-space-detail.html +++ b/src/app/view/endpoints/clusters/cluster/organization/space/detail/cluster-space-detail.html @@ -28,3 +28,4 @@

Space : {{ clusterSpaceController.space().details.space.entity.name }}

+ diff --git a/src/plugins/cloud-foundry/model/organization/organization.model.js b/src/plugins/cloud-foundry/model/organization/organization.model.js index b4698a4928..36bf69d17f 100644 --- a/src/plugins/cloud-foundry/model/organization/organization.model.js +++ b/src/plugins/cloud-foundry/model/organization/organization.model.js @@ -153,7 +153,7 @@ if (!roles || roles.length === 0) { // Shouldn't happen as we should at least be a user of the org - return gettext('none'); + return gettext('none assigned'); } else { // If there are more than one role, don't show the user role if (roles.length > 1) { @@ -276,7 +276,7 @@ } } if (!myRoles) { - throw new Error('Failed to find my roles in this organization'); + return []; } return myRoles; }); @@ -386,7 +386,9 @@ var newOrgGuid = org.metadata.guid; return that.stackatoInfoModel.getStackatoInfo().then(function (stackatoInfo) { var userGuid = stackatoInfo.endpoints.hcf[cnsiGuid].user.guid; - return orgsApi.AssociateManagerWithOrganization(newOrgGuid, userGuid, {}, httpConfig).then(function () { + var makeUserP = orgsApi.AssociateUserWithOrganization(newOrgGuid, userGuid, {}, httpConfig); + var makeManagerP = orgsApi.AssociateManagerWithOrganization(newOrgGuid, userGuid, {}, httpConfig); + return that.$q.all([makeUserP, makeManagerP]).then(function () { that.getOrganizationDetails(cnsiGuid, org); }); }); diff --git a/src/plugins/cloud-foundry/model/space/space.model.js b/src/plugins/cloud-foundry/model/space/space.model.js index 0ae54bd19e..f648ee83f3 100644 --- a/src/plugins/cloud-foundry/model/space/space.model.js +++ b/src/plugins/cloud-foundry/model/space/space.model.js @@ -33,6 +33,7 @@ function Space(apiManager, modelManager, $q) { this.apiManager = apiManager; this.stackatoInfoModel = modelManager.retrieve('app.model.stackatoInfo'); + this.organizationModel = modelManager.retrieve('cloud-foundry.model.organization'); this.$q = $q; this.data = { }; @@ -279,7 +280,7 @@ if (!roles || roles.length === 0) { // Shouldn't happen as we should at least be a user of the org - return gettext('none'); + return gettext('none assigned'); } else { // If there are more than one role, don't show the user role if (roles.length > 1) { @@ -335,7 +336,7 @@ // Find my user's roles myRoles = that.spaces[cnsiGuid][spaceGuid].roles[userGuid]; if (!myRoles) { - throw new Error('Failed to find my roles in this space'); + return []; } return myRoles; }); @@ -382,7 +383,43 @@ return details; }); + }, + + createSpaces: function (cnsiGuid, orgGuid, spaceNames, params) { + var that = this; + + var userGuid = this.stackatoInfoModel.info.endpoints.hcf[cnsiGuid].user.guid; + var spaceModel = this.apiManager.retrieve('cloud-foundry.api.Spaces'); + + var createPromises = []; + + function getSpaceDetails(response) { + return that.getSpaceDetails(cnsiGuid, response.data); + } + + for (var i = 0; i < spaceNames.length; i++) { + var spaceName = spaceNames[i]; + var newSpace = { + organization_guid: orgGuid, + name: spaceName, + manager_guids: [userGuid], + developer_guids: [userGuid] + }; + + var createP = spaceModel.CreateSpace(newSpace, params, this.makeHttpConfig(cnsiGuid)) + .then(getSpaceDetails); // Cache the space details + + createPromises.push(createP); + } + + return that.$q.all(createPromises).then(function () { + // Refresh the org! + var org = that.organizationModel.organizations[cnsiGuid][orgGuid].details.org; + return that.organizationModel.getOrganizationDetails(cnsiGuid, org); + }); + } + }); })();