diff --git a/releasenotes/notes/pool-panel-020bf94bc34b4cd8.yaml b/releasenotes/notes/pool-panel-020bf94bc34b4cd8.yaml
new file mode 100644
index 0000000..1a56c5b
--- /dev/null
+++ b/releasenotes/notes/pool-panel-020bf94bc34b4cd8.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - >
+ Storage pools management panel is added. This panel is
+ added into Admin dashboard. Also create, update and
+ delete actions are implemeted. Create action is
+ implemented as globalAction, so it is callable from
+ other panels.
diff --git a/zaqar_ui/static/dashboard/admin/pools/actions/actions.module.js b/zaqar_ui/static/dashboard/admin/pools/actions/actions.module.js
new file mode 100644
index 0000000..af12e76
--- /dev/null
+++ b/zaqar_ui/static/dashboard/admin/pools/actions/actions.module.js
@@ -0,0 +1,86 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc overview
+ * @ngname horizon.dashboard.admin.pools.actions
+ *
+ * @description
+ * Provides all of the actions for pools.
+ */
+ angular.module('horizon.dashboard.admin.pools.actions', [
+ 'horizon.framework.conf',
+ 'horizon.dashboard.admin.pools'
+ ])
+ .run(registerPoolActions);
+
+ registerPoolActions.$inject = [
+ 'horizon.framework.conf.resource-type-registry.service',
+ 'horizon.dashboard.admin.pools.actions.create.service',
+ 'horizon.dashboard.admin.pools.actions.delete.service',
+ 'horizon.dashboard.admin.pools.actions.update.service',
+ 'horizon.dashboard.admin.pools.resourceType'
+ ];
+
+ function registerPoolActions(
+ registry,
+ createPoolService,
+ deletePoolService,
+ updatePoolService,
+ poolResourceType
+ ) {
+ var resourceType = registry.getResourceType(poolResourceType);
+
+ resourceType.globalActions
+ .append({
+ id: 'createPoolAction',
+ service: createPoolService,
+ template: {
+ text: gettext('Create Pool'),
+ type: 'create'
+ }
+ });
+
+ resourceType.batchActions
+ .append({
+ id: 'batchDeletePoolAction',
+ service: deletePoolService,
+ template: {
+ type: 'delete-selected',
+ text: gettext('Delete Pools')
+ }
+ });
+
+ resourceType.itemActions
+ .append({
+ id: 'updatePoolAction',
+ service: updatePoolService,
+ template: {
+ text: gettext('Update Pool'),
+ type: 'row'
+ }
+ })
+ .append({
+ id: 'deletePoolAction',
+ service: deletePoolService,
+ template: {
+ text: gettext('Delete Pool'),
+ type: 'delete'
+ }
+ });
+ }
+})();
diff --git a/zaqar_ui/static/dashboard/admin/pools/actions/create.service.js b/zaqar_ui/static/dashboard/admin/pools/actions/create.service.js
new file mode 100644
index 0000000..60c900c
--- /dev/null
+++ b/zaqar_ui/static/dashboard/admin/pools/actions/create.service.js
@@ -0,0 +1,84 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc factory
+ * @name horizon.dashboard.admin.pools.actions.create.service
+ * @description
+ * Service for the storage pool create modal
+ */
+ angular
+ .module('horizon.dashboard.admin.pools.actions')
+ .factory('horizon.dashboard.admin.pools.actions.create.service', createPoolService);
+
+ createPoolService.$inject = [
+ 'horizon.app.core.openstack-service-api.policy',
+ 'horizon.app.core.openstack-service-api.zaqar',
+ 'horizon.dashboard.admin.pools.actions.workflow',
+ 'horizon.dashboard.admin.pools.resourceType',
+ 'horizon.framework.util.actions.action-result.service',
+ 'horizon.framework.util.i18n.gettext',
+ 'horizon.framework.util.q.extensions',
+ 'horizon.framework.widgets.form.ModalFormService',
+ 'horizon.framework.widgets.toast.service'
+ ];
+
+ function createPoolService(
+ policy, zaqar, workflow, resourceType,
+ actionResult, gettext, $qExtensions, modal, toast
+ ) {
+
+ var message = {
+ success: gettext('Pool %s was successfully created.')
+ };
+
+ var service = {
+ initAction: initAction,
+ perform: perform,
+ allowed: allowed
+ };
+
+ return service;
+
+ //////////////
+
+ function initAction() {
+ }
+
+ function perform() {
+ var title, submitText;
+ title = gettext('Create Pool');
+ submitText = gettext('Create');
+ var config = workflow.init('create', title, submitText);
+ return modal.open(config).then(submit);
+ }
+
+ function allowed() {
+ return policy.ifAllowed({ rules: [['pool', 'add_pool']] });
+ }
+
+ function submit(context) {
+ return zaqar.createPool(context.model, true).then(success, true);
+ }
+
+ function success(response) {
+ toast.add('success', interpolate(message.success, [response.data.id]));
+ var result = actionResult.getActionResult().created(resourceType, response.data.name);
+ return result.result;
+ }
+ }
+})();
diff --git a/zaqar_ui/static/dashboard/admin/pools/actions/delete.service.js b/zaqar_ui/static/dashboard/admin/pools/actions/delete.service.js
new file mode 100644
index 0000000..bb25b8d
--- /dev/null
+++ b/zaqar_ui/static/dashboard/admin/pools/actions/delete.service.js
@@ -0,0 +1,138 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use self file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc factory
+ * @name horizon.dashboard.admin.pools.actions.delete.service
+ * @Description
+ * Brings up the delete pools confirmation modal dialog.
+ * On submit, delete given pools.
+ * On cancel, do nothing.
+ */
+ angular
+ .module('horizon.dashboard.admin.pools.actions')
+ .factory('horizon.dashboard.admin.pools.actions.delete.service', deleteService);
+
+ deleteService.$inject = [
+ '$q',
+ 'horizon.app.core.openstack-service-api.policy',
+ 'horizon.app.core.openstack-service-api.zaqar',
+ 'horizon.dashboard.admin.pools.resourceType',
+ 'horizon.framework.util.actions.action-result.service',
+ 'horizon.framework.util.i18n.gettext',
+ 'horizon.framework.util.q.extensions',
+ 'horizon.framework.widgets.modal.deleteModalService',
+ 'horizon.framework.widgets.toast.service'
+ ];
+
+ function deleteService(
+ $q, policy, zaqar, resourceType, actionResult, gettext, $qExtensions,
+ deleteModal, toast
+ ) {
+ var scope, context;
+ var notAllowedMessage = gettext("You are not allowed to delete pools: %s");
+
+ var service = {
+ initAction: initAction,
+ allowed: allowed,
+ perform: perform
+ };
+
+ return service;
+
+ //////////////
+
+ function initAction() {
+ context = { };
+ }
+
+ function perform(items, newScope) {
+ scope = newScope;
+ var pools = angular.isArray(items) ? items : [items];
+ context.labels = labelize(pools.length);
+ context.deleteEntity = deletePool;
+ return $qExtensions.allSettled(pools.map(checkPermission)).then(afterCheck);
+ }
+
+ function allowed() {
+ return policy.ifAllowed({ rules: [['pool', 'delete_pool']] });
+ }
+
+ function checkPermission(pool) {
+ return {promise: allowed(), context: pool};
+ }
+
+ function afterCheck(result) {
+ var outcome = $q.reject(); // Reject the promise by default
+ if (result.fail.length > 0) {
+ toast.add('error', getMessage(notAllowedMessage, result.fail));
+ outcome = $q.reject(result.fail);
+ }
+ if (result.pass.length > 0) {
+ outcome = deleteModal.open(scope, result.pass.map(getEntity), context).then(createResult);
+ }
+ return outcome;
+ }
+
+ function createResult(deleteModalResult) {
+ var result = actionResult.getActionResult();
+ deleteModalResult.pass.forEach(function markDeleted(item) {
+ result.deleted(resourceType, getEntity(item).name);
+ });
+ deleteModalResult.fail.forEach(function markFailed(item) {
+ result.failed(resourceType, getEntity(item).name);
+ });
+ return result.result;
+ }
+
+ function labelize(count) {
+ return {
+ title: ngettext(
+ 'Confirm Delete Pool',
+ 'Confirm Delete Pools', count),
+ message: ngettext(
+ 'You have selected "%s". Deleted Pool is not recoverable.',
+ 'You have selected "%s". Deleted Pools are not recoverable.', count),
+ submit: ngettext(
+ 'Delete Pool',
+ 'Delete Pools', count),
+ success: ngettext(
+ 'Deleted Pool: %s.',
+ 'Deleted Pools: %s.', count),
+ error: ngettext(
+ 'Unable to delete Pool: %s.',
+ 'Unable to delete Pools: %s.', count)
+ };
+ }
+
+ function deletePool(pool) {
+ return zaqar.deletePool(pool, true);
+ }
+
+ function getMessage(message, entities) {
+ return interpolate(message, [entities.map(getName).join(", ")]);
+ }
+
+ function getName(result) {
+ return getEntity(result).name;
+ }
+
+ function getEntity(result) {
+ return result.context;
+ }
+ }
+})();
diff --git a/zaqar_ui/static/dashboard/admin/pools/actions/update.service.js b/zaqar_ui/static/dashboard/admin/pools/actions/update.service.js
new file mode 100644
index 0000000..68bb264
--- /dev/null
+++ b/zaqar_ui/static/dashboard/admin/pools/actions/update.service.js
@@ -0,0 +1,95 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc factory
+ * @name horizon.dashboard.admin.pools.actions.update.service
+ * @description
+ * Service for the storage pool update modal
+ */
+ angular
+ .module('horizon.dashboard.admin.pools.actions')
+ .factory('horizon.dashboard.admin.pools.actions.update.service', updateService);
+
+ updateService.$inject = [
+ 'horizon.app.core.openstack-service-api.policy',
+ 'horizon.app.core.openstack-service-api.zaqar',
+ 'horizon.dashboard.admin.pools.actions.workflow',
+ 'horizon.dashboard.admin.pools.resourceType',
+ 'horizon.framework.util.actions.action-result.service',
+ 'horizon.framework.util.i18n.gettext',
+ 'horizon.framework.util.q.extensions',
+ 'horizon.framework.widgets.form.ModalFormService',
+ 'horizon.framework.widgets.toast.service'
+ ];
+
+ function updateService(
+ policy, zaqar, workflow, resourceType,
+ actionResult, gettext, $qExtensions, modal, toast
+ ) {
+
+ var message = {
+ success: gettext('Pool %s was successfully updated.')
+ };
+
+ var service = {
+ initAction: initAction,
+ perform: perform,
+ allowed: allowed
+ };
+
+ return service;
+
+ //////////////
+
+ function initAction() {
+ }
+
+ function perform(selected) {
+ var title, submitText;
+ title = gettext('Update Pool');
+ submitText = gettext('Update');
+ var config = workflow.init('update', title, submitText);
+
+ // load current data
+ zaqar.getPool(selected.name).then(onLoad);
+ function onLoad(response) {
+ config.model.name = response.data.name;
+ config.model.group = response.data.group;
+ config.model.weight = response.data.weight;
+ config.model.uri = response.data.uri;
+ config.model.options = response.data.options;
+ }
+
+ return modal.open(config).then(submit);
+ }
+
+ function allowed() {
+ return policy.ifAllowed({ rules: [['pool', 'update_pool']] });
+ }
+
+ function submit(context) {
+ return zaqar.updatePool(context.model, true).then(success, true);
+ }
+
+ function success(response) {
+ toast.add('success', interpolate(message.success, [response.data.name]));
+ var result = actionResult.getActionResult().updated(resourceType, response.data.name);
+ return result.result;
+ }
+ }
+})();
diff --git a/zaqar_ui/static/dashboard/admin/pools/actions/workflow.service.js b/zaqar_ui/static/dashboard/admin/pools/actions/workflow.service.js
new file mode 100644
index 0000000..daa8e86
--- /dev/null
+++ b/zaqar_ui/static/dashboard/admin/pools/actions/workflow.service.js
@@ -0,0 +1,138 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc factory
+ * @name horizon.dashboard.admin.pools.actions.workflow
+ * @description
+ * Workflow for creating/updating storage pool
+ */
+ angular
+ .module('horizon.dashboard.admin.pools.actions')
+ .factory('horizon.dashboard.admin.pools.actions.workflow', workflow);
+
+ workflow.$inject = [
+ 'horizon.framework.util.i18n.gettext'
+ ];
+
+ function workflow(gettext) {
+ var workflow = {
+ init: init
+ };
+
+ function init(actionType, title, submitText) {
+ var schema, form, model;
+ var optionsPlaceholder = gettext(
+ 'An optional request component related to storage-specific options in YAML format.');
+
+ // schema
+ schema = {
+ type: 'object',
+ properties: {
+ name: {
+ title: gettext('Name'),
+ type: 'string'
+ },
+ group: {
+ title: gettext('Group'),
+ type: 'string'
+ },
+ weight: {
+ title: gettext('Weight'),
+ type: 'number'
+ },
+ uri: {
+ title: gettext('URI'),
+ type: 'string'
+ },
+ options: {
+ title: gettext('Options'),
+ type: 'string'
+ }
+ }
+ };
+
+ // form
+ form = [
+ {
+ type: 'section',
+ htmlClass: 'row',
+ items: [
+ {
+ type: 'section',
+ htmlClass: 'col-sm-6',
+ items: [
+ {
+ key: 'name',
+ placeholder: gettext('Name of the pool.'),
+ required: true,
+ "readonly": actionType === 'update'
+ },
+ {
+ key: 'weight',
+ placeholder: gettext('Weight of the pool.'),
+ required: true
+ },
+ {
+ key: 'uri',
+ placeholder: gettext('URI for storage engine of this pool.'),
+ description: gettext('e.g. mongodb://127.0.0.1:27017'),
+ required: true
+ }
+ ]
+ },
+ {
+ type: 'section',
+ htmlClass: 'col-sm-6',
+ items: [
+ {
+ key: 'group',
+ placeholder: gettext('Group of the pool.')
+ },
+ {
+ key: 'options',
+ type: 'textarea',
+ placeholder: optionsPlaceholder
+ }
+ ]
+ }
+ ]
+ }
+ ]; // form
+
+ model = {
+ name: '',
+ group: '',
+ weight: 0,
+ uri: '',
+ options: ''
+ };
+
+ var config = {
+ title: title,
+ submitText: submitText,
+ schema: schema,
+ form: form,
+ model: model
+ };
+
+ return config;
+ }
+
+ return workflow;
+ }
+})();
diff --git a/zaqar_ui/static/dashboard/admin/pools/drawer.html b/zaqar_ui/static/dashboard/admin/pools/drawer.html
index fe6617c..55192d1 100644
--- a/zaqar_ui/static/dashboard/admin/pools/drawer.html
+++ b/zaqar_ui/static/dashboard/admin/pools/drawer.html
@@ -1,5 +1,5 @@
+ property-groups="[['uri', 'options']]">
diff --git a/zaqar_ui/static/dashboard/admin/pools/panel.html b/zaqar_ui/static/dashboard/admin/pools/panel.html
index c3c2528..ca0a5d5 100644
--- a/zaqar_ui/static/dashboard/admin/pools/panel.html
+++ b/zaqar_ui/static/dashboard/admin/pools/panel.html
@@ -1,4 +1,4 @@
-
+
diff --git a/zaqar_ui/static/dashboard/admin/pools/pools.module.js b/zaqar_ui/static/dashboard/admin/pools/pools.module.js
index 3045d58..558e6e8 100644
--- a/zaqar_ui/static/dashboard/admin/pools/pools.module.js
+++ b/zaqar_ui/static/dashboard/admin/pools/pools.module.js
@@ -22,7 +22,8 @@
angular
.module('horizon.dashboard.admin.pools', [
- 'ngRoute'
+ 'ngRoute',
+ 'horizon.dashboard.admin.pools.actions'
])
.constant('horizon.dashboard.admin.pools.resourceType', 'OS::Zaqar::Pools')
.run(run)
diff --git a/zaqar_ui/static/dashboard/admin/pools/pools.service.js b/zaqar_ui/static/dashboard/admin/pools/pools.service.js
index 32dfaf7..914dcfa 100644
--- a/zaqar_ui/static/dashboard/admin/pools/pools.service.js
+++ b/zaqar_ui/static/dashboard/admin/pools/pools.service.js
@@ -43,7 +43,19 @@
* pools. This is used in displaying lists of Pools.
*/
function getPoolsPromise(params) {
- return zaqar.getPools(params);
+ return zaqar.getPools(params).then(modifyResponse);
+ }
+
+ function modifyResponse(response) {
+ return {data: {items: response.data.items.map(modifyItem)}};
+
+ function modifyItem(item) {
+ // we should set 'trackBy' as follows ideally.
+ // item.trackBy = item.id + item.updated_at;
+ var timestamp = new Date();
+ item.trackBy = item.name.concat(timestamp.getTime());
+ return item;
+ }
}
}
})();