diff --git a/zun_ui/api/client.py b/zun_ui/api/client.py index 115ae0a..55a37f8 100644 --- a/zun_ui/api/client.py +++ b/zun_ui/api/client.py @@ -22,7 +22,7 @@ LOG = logging.getLogger(__name__) CONTAINER_CREATE_ATTRS = ['name', 'image', 'command', 'cpu', 'memory', 'environment', 'workdir', 'ports', 'hostname', - 'labels'] + 'labels', 'image_pull_policy'] @memoized @@ -109,3 +109,11 @@ def container_pause(request, id): def container_unpause(request, id): return zunclient(request).containers.unpause(id) + + +def container_execute(request, id, command): + return zunclient(request).containers.execute(id, command) + + +def container_kill(request, id, signal=None): + return zunclient(request).containers.kill(id, signal) diff --git a/zun_ui/api/rest_api.py b/zun_ui/api/rest_api.py index 7d448f6..4c438fe 100644 --- a/zun_ui/api/rest_api.py +++ b/zun_ui/api/rest_api.py @@ -63,6 +63,12 @@ class ContainerActions(generic.View): return client.container_pause(request, id) elif action == 'unpause': return client.container_unpause(request, id) + elif action == 'execute': + command = request.DATA.get("command") + return client.container_execute(request, id, command) + elif action == 'kill': + signal = request.DATA.get("signal") + return client.container_kill(request, id, signal) @urls.register diff --git a/zun_ui/static/dashboard/container/containers/actions.module.js b/zun_ui/static/dashboard/container/containers/actions.module.js index 8ad1098..391f888 100644 --- a/zun_ui/static/dashboard/container/containers/actions.module.js +++ b/zun_ui/static/dashboard/container/containers/actions.module.js @@ -35,6 +35,8 @@ 'horizon.dashboard.container.containers.reboot.service', 'horizon.dashboard.container.containers.pause.service', 'horizon.dashboard.container.containers.unpause.service', + 'horizon.dashboard.container.containers.execute.service', + 'horizon.dashboard.container.containers.kill.service', 'horizon.dashboard.container.containers.resourceType', ]; @@ -48,9 +50,32 @@ rebootContainerService, pauseContainerService, unpauseContainerService, + executeContainerService, + killContainerService, resourceType) { var containersResourceType = registry.getResourceType(resourceType); + + containersResourceType.globalActions + .append({ + id: 'createContainerAction', + service: createContainerService, + template: { + type: 'create', + text: gettext('Create Container') + } + }); + + containersResourceType.batchActions + .append({ + id: 'batchDeleteContainerAction', + service: deleteContainerService, + template: { + type: 'delete-selected', + text: gettext('Delete Containers') + } + }); + containersResourceType.itemActions .append({ id: 'startContainerAction', @@ -87,6 +112,20 @@ text: gettext('Unpause Container') } }) + .append({ + id: 'executeContainerAction', + service: executeContainerService, + template: { + text: gettext('Execute Command') + } + }) + .append({ + id: 'killContainerAction', + service: killContainerService, + template: { + text: gettext('Send Kill Signal') + } + }) .append({ id: 'deleteContainerAction', service: deleteContainerService, @@ -95,24 +134,6 @@ text: gettext('Delete Container') } }); - - containersResourceType.batchActions - .append({ - id: 'createContainerAction', - service: createContainerService, - template: { - type: 'create', - text: gettext('Create Container') - } - }) - .append({ - id: 'batchDeleteContainerAction', - service: deleteContainerService, - template: { - type: 'delete-selected', - text: gettext('Delete Containers') - } - }); } })(); diff --git a/zun_ui/static/dashboard/container/containers/operations/execute.service.js b/zun_ui/static/dashboard/container/containers/operations/execute.service.js new file mode 100644 index 0000000..9901d29 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/operations/execute.service.js @@ -0,0 +1,118 @@ +/** + * 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'; + + angular + .module('horizon.dashboard.container.containers') + .factory( + 'horizon.dashboard.container.containers.execute.service', + executeContainerService); + + executeContainerService.$inject = [ + 'horizon.app.core.openstack-service-api.zun', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service' + ]; + + /** + * @ngdoc factory + * @name horizon.dashboard.container.containers.execute.service + * @description + * Service for the command execution in the container + */ + function executeContainerService( + zun, gettext, $qExtensions, modal, toast + ) { + // schema + var schema = { + type: "object", + properties: { + command: { + title: gettext("Command"), + type: "string" + } + } + }; + + // form + var form = [ + { + type: "section", + htmlClass: "col-sm-12", + items: [ + { + key: "command", + placeholder: gettext("The command to execute."), + required: true + } + ] + } + ]; + + // model + var model; + + var message = { + success: gettext("Command was successfully executed at container %s.") + }; + + var service = { + initAction: initAction, + perform: perform, + allowed: allowed + }; + + return service; + + ////////////// + + function initAction() { + } + + function allowed() { + return $qExtensions.booleanAsPromise(true); + } + + function perform(selected) { + model = { + id: selected.id, + name: selected.name, + command: '' + }; + // modal config + var config = { + title: gettext("Execute Command"), + submitText: gettext("Execute"), + schema: schema, + form: form, + model: model + }; + return modal.open(config).then(submit); + } + + function submit(context) { + var id = context.model.id; + var name = context.model.name; + delete context.model.id; + delete context.model.name; + return zun.executeContainer(id, context.model).then(function(response) { + toast.add('success', interpolate(message.success, [name])); + }); + } + } +})(); diff --git a/zun_ui/static/dashboard/container/containers/operations/kill.help.html b/zun_ui/static/dashboard/container/containers/operations/kill.help.html new file mode 100644 index 0000000..8e9c354 --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/operations/kill.help.html @@ -0,0 +1,6 @@ +

+ Signal to send to the container: integer or string like SIGINT. + When not set, SIGKILL is set as default value and the container will exit. + The supported signals varies between platform. + Besides, you can omit 'SIG' prefix. +

diff --git a/zun_ui/static/dashboard/container/containers/operations/kill.service.js b/zun_ui/static/dashboard/container/containers/operations/kill.service.js new file mode 100644 index 0000000..4d1b9ec --- /dev/null +++ b/zun_ui/static/dashboard/container/containers/operations/kill.service.js @@ -0,0 +1,128 @@ +/** + * 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'; + + angular + .module('horizon.dashboard.container.containers') + .factory( + 'horizon.dashboard.container.containers.kill.service', + killContainerService); + + killContainerService.$inject = [ + 'horizon.app.core.openstack-service-api.zun', + 'horizon.dashboard.container.containers.basePath', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service' + ]; + + /** + * @ngdoc factory + * @name horizon.dashboard.container.containers.kill.service + * @description + * Service to send kill signals to the container + */ + function killContainerService( + zun, basePath, gettext, $qExtensions, modal, toast + ) { + // schema + var schema = { + type: "object", + properties: { + signal: { + title: gettext("Kill Signal"), + type: "string" + } + } + }; + + // form + var form = [ + { + type: 'section', + htmlClass: 'row', + items: [ + { + type: 'section', + htmlClass: 'col-sm-6', + items: [ + { + "key": "signal", + "placeholder": gettext("The kill signal to send.") + } + ] + }, + { + type: 'template', + templateUrl: basePath + 'operations/kill.help.html' + } + ] + } + ]; + + // model + var model; + + var message = { + success: gettext('Kill signal was successfully sent to container %s.') + }; + + var service = { + initAction: initAction, + perform: perform, + allowed: allowed + }; + + return service; + + ////////////// + + function initAction() { + } + + function allowed() { + return $qExtensions.booleanAsPromise(true); + } + + function perform(selected) { + model = { + id: selected.id, + name: selected.name, + signal: '' + }; + // modal config + var config = { + "title": gettext('Send Kill Signal'), + "submitText": gettext('Send'), + "schema": schema, + "form": form, + "model": model + }; + return modal.open(config).then(submit); + } + + function submit(context) { + var id = context.model.id; + var name = context.model.name; + delete context.model.id; + delete context.model.name; + return zun.killContainer(id, context.model).then(function(response) { + toast.add('success', interpolate(message.success, [name])); + }); + } + } +})(); diff --git a/zun_ui/static/dashboard/container/zun.service.js b/zun_ui/static/dashboard/container/zun.service.js index e0d19a4..35c2431 100644 --- a/zun_ui/static/dashboard/container/zun.service.js +++ b/zun_ui/static/dashboard/container/zun.service.js @@ -36,7 +36,9 @@ logsContainer: logsContainer, rebootContainer: rebootContainer, pauseContainer: pauseContainer, - unpauseContainer: unpauseContainer + unpauseContainer: unpauseContainer, + executeContainer: executeContainer, + killContainer: killContainer }; return service; @@ -140,17 +142,31 @@ } function pauseContainer(id) { - return apiService.post('/api/zun/containers/' + id + '/pause') - .error(function() { - toastService.add('error', gettext('Unable to pause Container')); - }); - } + return apiService.post('/api/zun/containers/' + id + '/pause') + .error(function() { + toastService.add('error', gettext('Unable to pause Container')); + }); + } function unpauseContainer(id) { - return apiService.post('/api/zun/containers/' + id + '/unpause') - .error(function() { - toastService.add('error', gettext('Unable to unpause of Container')); - }); - } + return apiService.post('/api/zun/containers/' + id + '/unpause') + .error(function() { + toastService.add('error', gettext('Unable to unpause of Container')); + }); + } + + function executeContainer(id, params) { + return apiService.post('/api/zun/containers/' + id + '/execute', params) + .error(function() { + toastService.add('error', gettext('Unable to execute the command')); + }); + } + + function killContainer(id, params) { + return apiService.post('/api/zun/containers/' + id + '/kill', params) + .error(function() { + toastService.add('error', gettext('Unable to send kill signal')); + }); + } } }());