From 247af4b88b862975c9c79e52565957f723a1e86b Mon Sep 17 00:00:00 2001 From: Shu Muto Date: Wed, 13 Jun 2018 16:19:20 +0900 Subject: [PATCH] Add capsule panel This patch adds capsule panel that includes capsule list view. Other views and actions will be implemented in subsequent patches. To enable this panel, copy enabled file[1] to horizon enabled directory[2] and restart horizon. [1] zun_ui/enabled/_1332_project_container_capsules_panel.py [2] openstack_dashboard/local/enabled/ Change-Id: I6d89704a8fe4fa32cee52bbf2c38e0b10cd01f1a Partial-Implements: blueprint capsule --- zun_ui/api/client.py | 6 + zun_ui/api/rest_api.py | 16 ++ zun_ui/content/container/capsules/__init__.py | 0 zun_ui/content/container/capsules/panel.py | 19 +++ zun_ui/content/container/capsules/urls.py | 20 +++ .../_1332_project_container_capsules_panel.py | 21 +++ .../container/capsules/actions.module.js | 58 +++++++ .../capsules/actions/refresh.service.js | 68 ++++++++ .../container/capsules/capsules.module.js | 150 ++++++++++++++++++ .../container/capsules/capsules.service.js | 61 +++++++ .../dashboard/container/capsules/drawer.html | 7 + .../dashboard/container/capsules/panel.html | 4 + .../dashboard/container/container.module.js | 1 + .../static/dashboard/container/zun.service.js | 11 ++ 14 files changed, 442 insertions(+) create mode 100644 zun_ui/content/container/capsules/__init__.py create mode 100644 zun_ui/content/container/capsules/panel.py create mode 100644 zun_ui/content/container/capsules/urls.py create mode 100644 zun_ui/enabled/_1332_project_container_capsules_panel.py create mode 100644 zun_ui/static/dashboard/container/capsules/actions.module.js create mode 100644 zun_ui/static/dashboard/container/capsules/actions/refresh.service.js create mode 100644 zun_ui/static/dashboard/container/capsules/capsules.module.js create mode 100644 zun_ui/static/dashboard/container/capsules/capsules.service.js create mode 100644 zun_ui/static/dashboard/container/capsules/drawer.html create mode 100644 zun_ui/static/dashboard/container/capsules/panel.html diff --git a/zun_ui/api/client.py b/zun_ui/api/client.py index b5c272e..baa0e1e 100644 --- a/zun_ui/api/client.py +++ b/zun_ui/api/client.py @@ -266,6 +266,12 @@ def availability_zone_list(request): return list +def capsule_list(request, limit=None, marker=None, sort_key=None, + sort_dir=None): + return zunclient(request).capsules.list(limit, marker, sort_key, + sort_dir) + + def image_list(request, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False): # FIXME(shu-mutou): change "detail" param to True, if it enabled. diff --git a/zun_ui/api/rest_api.py b/zun_ui/api/rest_api.py index 3d4cd76..e2d05f4 100644 --- a/zun_ui/api/rest_api.py +++ b/zun_ui/api/rest_api.py @@ -163,6 +163,22 @@ class AvailabilityZones(generic.View): return {'items': [i.to_dict() for i in result]} +@urls.register +class Capsules(generic.View): + """API for Capsules""" + url_regex = r'zun/capsules/$' + + @rest_utils.ajax() + def get(self, request): + """Get a list of the Capsules. + + The returned result is an object with property 'items' and each + item under this is a Capsules. + """ + result = client.capsule_list(request) + return {'items': [i.to_dict() for i in result]} + + @urls.register class Images(generic.View): """API for Zun Images""" diff --git a/zun_ui/content/container/capsules/__init__.py b/zun_ui/content/container/capsules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zun_ui/content/container/capsules/panel.py b/zun_ui/content/container/capsules/panel.py new file mode 100644 index 0000000..b8ae3ca --- /dev/null +++ b/zun_ui/content/container/capsules/panel.py @@ -0,0 +1,19 @@ +# 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. + +from django.utils.translation import ugettext_lazy as _ +import horizon + + +class Capsules(horizon.Panel): + name = _("Capsules") + slug = "container.capsules" diff --git a/zun_ui/content/container/capsules/urls.py b/zun_ui/content/container/capsules/urls.py new file mode 100644 index 0000000..bb215ee --- /dev/null +++ b/zun_ui/content/container/capsules/urls.py @@ -0,0 +1,20 @@ +# 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. + +from django.conf.urls import url +from django.utils.translation import ugettext_lazy as _ +from horizon.browsers import views + +title = _("Capsules") +urlpatterns = [ + url('', views.AngularIndexView.as_view(title=title), name='index'), +] diff --git a/zun_ui/enabled/_1332_project_container_capsules_panel.py b/zun_ui/enabled/_1332_project_container_capsules_panel.py new file mode 100644 index 0000000..893ef56 --- /dev/null +++ b/zun_ui/enabled/_1332_project_container_capsules_panel.py @@ -0,0 +1,21 @@ +# 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. + +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'container.capsules' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'container' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'zun_ui.content.container.capsules.panel.Capsules' diff --git a/zun_ui/static/dashboard/container/capsules/actions.module.js b/zun_ui/static/dashboard/container/capsules/actions.module.js new file mode 100644 index 0000000..db5d5c9 --- /dev/null +++ b/zun_ui/static/dashboard/container/capsules/actions.module.js @@ -0,0 +1,58 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @ngname horizon.dashboard.container.capsules.actions + * + * @description + * Provides all of the actions for capsules. + */ + angular.module('horizon.dashboard.container.capsules.actions', + [ + 'horizon.framework', + 'horizon.dashboard.container' + ]) + .run(registerCapsuleActions); + + registerCapsuleActions.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.dashboard.container.capsules.actions.refresh.service', + 'horizon.dashboard.container.capsules.resourceType' + ]; + + function registerCapsuleActions( + registry, + gettext, + refreshCapsuleService, + resourceType + ) { + var capsulesResourceType = registry.getResourceType(resourceType); + + // FIXME(shu-mutow): refresh action is dummy. remove it when add other action. + capsulesResourceType.itemActions + .append({ + id: 'refreshCapsuleAction', + service: refreshCapsuleService, + template: { + text: gettext('Refresh') + } + }); + } + +})(); diff --git a/zun_ui/static/dashboard/container/capsules/actions/refresh.service.js b/zun_ui/static/dashboard/container/capsules/actions/refresh.service.js new file mode 100644 index 0000000..14c026a --- /dev/null +++ b/zun_ui/static/dashboard/container/capsules/actions/refresh.service.js @@ -0,0 +1,68 @@ +/** + * 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.container.capsules.refresh.service + * @Description + * refresh container. + */ + angular + .module('horizon.dashboard.container.capsules.actions') + .factory('horizon.dashboard.container.capsules.actions.refresh.service', refreshService); + + refreshService.$inject = [ + 'horizon.app.core.openstack-service-api.zun', + 'horizon.dashboard.container.capsules.resourceType', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.util.q.extensions' + ]; + + function refreshService( + zun, resourceType, actionResult, $qExtensions + ) { + + var service = { + initAction: initAction, + allowed: allowed, + perform: perform + }; + + return service; + + ////////////// + + // include this function in your service + // if you plan to emit events to the parent controller + function initAction() { + } + + function allowed() { + return $qExtensions.booleanAsPromise(true); + } + + function perform(selected) { + // refresh selected capsule + return $qExtensions.booleanAsPromise(true).then(success); + + function success() { + var result = actionResult.getActionResult().updated(resourceType, selected.id); + return result.result; + } + } + } +})(); diff --git a/zun_ui/static/dashboard/container/capsules/capsules.module.js b/zun_ui/static/dashboard/container/capsules/capsules.module.js new file mode 100644 index 0000000..e2bb326 --- /dev/null +++ b/zun_ui/static/dashboard/container/capsules/capsules.module.js @@ -0,0 +1,150 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @name horizon.dashboard.container.capsules + * @ngModule + * @description + * Provides all the services and widgets require to display the capsules + * panel + */ + angular + .module('horizon.dashboard.container.capsules', [ + 'ngRoute', + 'horizon.dashboard.container.capsules.actions' + ]) + .constant('horizon.dashboard.container.capsules.events', events()) + .constant('horizon.dashboard.container.capsules.resourceType', 'OS::Zun::Capsule') + .run(run) + .config(config); + + /** + * @ngdoc constant + * @name horizon.dashboard.container.capsules.events + * @description A list of events used by Capsules + * @returns {Object} Event constants + */ + function events() { + return { + CREATE_SUCCESS: 'horizon.dashboard.container.capsules.CREATE_SUCCESS', + DELETE_SUCCESS: 'horizon.dashboard.container.capsules.DELETE_SUCCESS' + }; + } + + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'horizon.app.core.openstack-service-api.zun', + 'horizon.dashboard.container.capsules.basePath', + 'horizon.dashboard.container.capsules.resourceType', + 'horizon.dashboard.container.capsules.service' + ]; + + function run(registry, zun, basePath, resourceType, capsuleService) { + registry.getResourceType(resourceType) + .setNames(gettext('Capsule'), gettext('Capsules')) + // for detail summary view on table row. + .setSummaryTemplateUrl(basePath + 'drawer.html') + // for table row items and detail summary view. + .setProperties(capsuleProperties()) + .setListFunction(capsuleService.getCapsulesPromise) + .tableColumns + .append({ + id: 'meta_name', + priority: 1, + sortDefault: true + }) + .append({ + id: 'id', + priority: 2 + }) + .append({ + id: 'status', + priority: 1 + }) + .append({ + id: 'cpu', + priority: 3 + }) + .append({ + id: 'memory', + priority: 3 + }); + // for magic-search + registry.getResourceType(resourceType).filterFacets + .append({ + 'label': gettext('ID'), + 'name': 'id', + 'singleton': true + }) + .append({ + 'label': gettext('Name'), + 'name': 'meta_name', + 'singleton': true + }) + .append({ + 'label': gettext('Status'), + 'name': 'status', + 'singleton': true + }); + } + + function capsuleProperties() { + return { + 'addresses': {label: gettext('Addresses'), filters: ['noValue', 'json'] }, + 'capsule_versionid': {label: gettext('Capsule Version ID'), filters: ['noValue'] }, + 'containers': {label: gettext('Containers'), filters: ['noValue', 'json'] }, + 'container_uuids': {label: gettext('Container UUIDs'), filters: ['noValue', 'json'] }, + 'cpu': {label: gettext('CPU'), filters: ['noValue'] }, + 'created_at': { label: gettext('Created'), filters: ['simpleDate'] }, + 'id': {label: gettext('ID'), filters: ['noValue'] }, + 'links': {label: gettext('Links'), filters: ['noValue', 'json'] }, + 'memory': { label: gettext('Memory'), filters: ['noValue'] }, + 'meta_labels': {label: gettext('Labels'), filters: ['noValue', 'json'] }, + 'meta_name': { label: gettext('Name'), filters: ['noName'] }, + 'project_id': { label: gettext('Project ID'), filters: ['noValue'] }, + 'restart_policy': { label: gettext('Restart Policy'), filters: ['noValue'] }, + 'status': { label: gettext('Status'), filters: ['noValue'] }, + 'status_reason': { label: gettext('Status Reason'), filters: ['noValue'] }, + 'updated_at': { label: gettext('Updated'), filters: ['simpleDate'] }, + 'user_id': { label: gettext('User ID'), filters: ['noValue'] }, + 'volumes_info': {label: gettext('Volumes Info'), filters: ['noValue', 'json'] } + }; + } + + config.$inject = [ + '$provide', + '$windowProvider', + '$routeProvider' + ]; + + /** + * @name config + * @param {Object} $provide + * @param {Object} $windowProvider + * @param {Object} $routeProvider + * @description Routes used by this module. + * @returns {undefined} Returns nothing + */ + function config($provide, $windowProvider, $routeProvider) { + var path = $windowProvider.$get().STATIC_URL + 'dashboard/container/capsules/'; + $provide.constant('horizon.dashboard.container.capsules.basePath', path); + $routeProvider.when('/project/container/capsules', { + templateUrl: path + 'panel.html' + }); + } +})(); diff --git a/zun_ui/static/dashboard/container/capsules/capsules.service.js b/zun_ui/static/dashboard/container/capsules/capsules.service.js new file mode 100644 index 0000000..9c18278 --- /dev/null +++ b/zun_ui/static/dashboard/container/capsules/capsules.service.js @@ -0,0 +1,61 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function() { + "use strict"; + + angular + .module('horizon.dashboard.container.capsules') + .factory('horizon.dashboard.container.capsules.service', capsulesService); + + capsulesService.$inject = [ + 'horizon.app.core.detailRoute', + 'horizon.app.core.openstack-service-api.zun' + ]; + + /* + * @ngdoc factory + * @name horizon.dashboard.container.capsules.service + * + * @description + * This service provides functions that are used through + * the capsules of container features. + */ + function capsulesService(detailRoute, zun) { + return { + getCapsulesPromise: getCapsulesPromise + }; + + /* + * @ngdoc function + * @name getCapsulesPromise + * @description + * Given filter/query parameters, returns a promise for the matching + * capsules. This is used in displaying lists of capsules. + */ + function getCapsulesPromise(params) { + return zun.getCapsules(params).then(modifyResponse); + } + + function modifyResponse(response) { + return {data: {items: response.data.items.map(modifyItem)}}; + } + + function modifyItem(item) { + item.id = item.uuid; + item.trackBy = item.uuid; + item.trackBy = item.trackBy.concat(item.updated_at); + return item; + } + } +})(); diff --git a/zun_ui/static/dashboard/container/capsules/drawer.html b/zun_ui/static/dashboard/container/capsules/drawer.html new file mode 100644 index 0000000..4e0e9e9 --- /dev/null +++ b/zun_ui/static/dashboard/container/capsules/drawer.html @@ -0,0 +1,7 @@ + + diff --git a/zun_ui/static/dashboard/container/capsules/panel.html b/zun_ui/static/dashboard/container/capsules/panel.html new file mode 100644 index 0000000..c2c8950 --- /dev/null +++ b/zun_ui/static/dashboard/container/capsules/panel.html @@ -0,0 +1,4 @@ + + + diff --git a/zun_ui/static/dashboard/container/container.module.js b/zun_ui/static/dashboard/container/container.module.js index d6b1e30..cd341c2 100644 --- a/zun_ui/static/dashboard/container/container.module.js +++ b/zun_ui/static/dashboard/container/container.module.js @@ -24,6 +24,7 @@ angular .module('horizon.dashboard.container', [ 'horizon.dashboard.container.containers', + 'horizon.dashboard.container.capsules', 'horizon.dashboard.container.images', 'ngRoute' ]) diff --git a/zun_ui/static/dashboard/container/zun.service.js b/zun_ui/static/dashboard/container/zun.service.js index a8084f0..6bbd8e5 100644 --- a/zun_ui/static/dashboard/container/zun.service.js +++ b/zun_ui/static/dashboard/container/zun.service.js @@ -27,6 +27,7 @@ function ZunAPI(apiService, toastService, gettext) { var containersPath = '/api/zun/containers/'; var zunAvailabilityZonesPath = '/api/zun/availability_zones/'; + var capsulesPath = '/api/zun/capsules/'; var imagesPath = '/api/zun/images/'; var hostsPath = '/api/zun/hosts/'; var service = { @@ -52,6 +53,7 @@ detachNetwork: detachNetwork, updatePortSecurityGroup: updatePortSecurityGroup, getZunAvailabilityZones: getZunAvailabilityZones, + getCapsules: getCapsules, pullImage: pullImage, getImages: getImages, deleteImage: deleteImage, @@ -197,6 +199,15 @@ return apiService.get(zunAvailabilityZonesPath).error(error(msg)); } + ////////////// + // Capsules // + ////////////// + + function getCapsules() { + var msg = gettext('Unable to retrieve the Capsules.'); + return apiService.get(capsulesPath).error(error(msg)); + } + //////////// // Images // ////////////