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 @@
-
-
-
- {{ clusterAction.name }}
-
-
-
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 @@
+
+
+
+
+ {{ clusterAction.name }}
+
+
+
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 @@
+
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);
+ });
+
}
+
});
})();