Remove 'host' parameter from deleting image

'host' parameter for deleting image has removed from client.
Zun UI should remove this parameter.

Now, due to only 'id' is necessary for deleting image,
we can use 'delete-selected' type action and common deletion
confirmation dialog. Using the common deletion action,
we can also implement deletion action as batch action.

Change-Id: I2adae5cb466e620177c3788ac340f6464711e21d
Closes-Bug: #1799125
This commit is contained in:
Shu Muto 2018-10-22 14:44:44 +09:00
parent 0022f4a0ba
commit 3f8cb502b6
4 changed files with 122 additions and 96 deletions

View File

@ -226,6 +226,15 @@ class Images(generic.View):
result = client.image_list(request) result = client.image_list(request)
return {'items': [change_to_id(i.to_dict()) for i in result]} return {'items': [change_to_id(i.to_dict()) for i in result]}
@rest_utils.ajax(data_required=True)
def delete(self, request):
"""Delete one or more Images by id.
Returns HTTP 204 (no content) on successful deletion.
"""
for id in request.DATA:
client.image_delete(request, id)
@rest_utils.ajax(data_required=True) @rest_utils.ajax(data_required=True)
def post(self, request): def post(self, request):
"""Create a new Image. """Create a new Image.
@ -238,17 +247,6 @@ class Images(generic.View):
new_image.to_dict()) new_image.to_dict())
@urls.register
class Image(generic.View):
"""API for operate a single image"""
url_regex = r'zun/images/(?P<id>[^/]+)$'
@rest_utils.ajax(data_required=True)
def delete(self, request, id):
"""Delete a specific image"""
client.image_delete(request, id, **request.DATA)
@urls.register @urls.register
class Hosts(generic.View): class Hosts(generic.View):
"""API for Zun Hosts""" """API for Zun Hosts"""

View File

@ -56,6 +56,16 @@
} }
}); });
imagesResourceType.batchActions
.append({
id: 'deleteImageAction',
service: deleteImageService,
template: {
type: 'delete-selected',
text: gettext('Delete Images')
}
});
imagesResourceType.itemActions imagesResourceType.itemActions
.append({ .append({
id: 'deleteImageAction', id: 'deleteImageAction',

View File

@ -19,123 +19,138 @@
* @ngDoc factory * @ngDoc factory
* @name horizon.dashboard.container.images.actions.delete.service * @name horizon.dashboard.container.images.actions.delete.service
* @Description * @Description
* restart container. * Brings up the delete images confirmation modal dialog.
* On submit, delete selected resources.
* On cancel, do nothing.
*/ */
angular angular
.module('horizon.dashboard.container.images.actions') .module('horizon.dashboard.container.images')
.factory('horizon.dashboard.container.images.actions.delete.service', deleteService); .factory('horizon.dashboard.container.images.actions.delete.service', deleteService);
deleteService.$inject = [ deleteService.$inject = [
'$location',
'$q',
'$rootScope',
'horizon.app.core.openstack-service-api.zun', 'horizon.app.core.openstack-service-api.zun',
'horizon.dashboard.container.images.basePath', 'horizon.app.core.openstack-service-api.policy',
'horizon.dashboard.container.images.resourceType',
'horizon.framework.util.actions.action-result.service', 'horizon.framework.util.actions.action-result.service',
'horizon.framework.util.i18n.gettext', 'horizon.framework.util.i18n.gettext',
'horizon.framework.util.q.extensions', 'horizon.framework.util.q.extensions',
'horizon.framework.widgets.form.ModalFormService', 'horizon.framework.widgets.modal.deleteModalService',
'horizon.framework.widgets.toast.service' 'horizon.framework.widgets.table.events',
'horizon.framework.widgets.toast.service',
'horizon.dashboard.container.images.resourceType',
'horizon.dashboard.container.images.events'
]; ];
function deleteService( function deleteService(
zun, basePath, resourceType, actionResult, gettext, $qExtensions, modal, toast $location, $q, $rootScope, zun, policy, actionResult, gettext, $qExtensions, deleteModal,
tableEvents, toast, resourceType, events
) { ) {
var push = Array.prototype.push; var scope;
var hosts = [{value: "", name: gettext("Select host to remove the image from.")}]; var context = {
// schema labels: null,
var schema = { deleteEntity: deleteEntity,
type: "object", successEvent: events.DELETE_SUCCESS
properties: {
host: {
title: gettext("Host"),
type: "string"
}
}
}; };
// form
var form = [
{
type: 'section',
htmlClass: 'row',
items: [
{
type: 'section',
htmlClass: 'col-sm-12',
items: [
{
key: 'host',
type: "select",
titleMap: hosts,
required: true
}
]
}
]
}
];
// model
var model;
var message = {
success: gettext('Container %s was successfully restarted.')
};
var service = { var service = {
initAction: initAction, initAction: initAction,
allowed: allowed, allowed: allowed,
perform: perform perform: perform
}; };
var notAllowedMessage = gettext("You are not allowed to delete images: %s");
return service; return service;
////////////// //////////////
// include this function in your service
// if you plan to emit events to the parent controller
function initAction() { function initAction() {
// get hosts for zun
zun.getHosts().then(onGetZunHosts);
function onGetZunHosts(response) {
var hs = [];
response.data.items.forEach(function (host) {
hs.push({value: host.id, name: host.hostname});
});
push.apply(hosts, hs);
}
} }
function allowed() { function allowed() {
return $qExtensions.booleanAsPromise(true); return $qExtensions.booleanAsPromise(true);
} }
function perform(selected) { // delete selected resource objects
model = { function perform(selected, newScope) {
id: selected.id, scope = newScope;
repo: selected.repo, selected = angular.isArray(selected) ? selected : [selected];
host: "" context.labels = labelize(selected.length);
}; return $qExtensions.allSettled(selected.map(checkPermission)).then(afterCheck);
// modal config
var config = {
"title": interpolate(gettext('Delete Image %s'), [model.repo]),
"submitText": gettext('Delete'),
"schema": schema,
"form": form,
"model": model
};
return modal.open(config).then(submit);
function submit(context) {
var id = context.model.id;
var repo = context.model.repo;
var host = context.model.host;
return zun.deleteImage(id, host).then(function() {
toast.add('success', interpolate(message.success, [repo]));
var result = actionResult.getActionResult().updated(resourceType, id);
return result.result;
});
} }
function labelize(count) {
return {
title: ngettext('Confirm Delete Image',
'Confirm Delete Images', count),
/* eslint-disable max-len */
message: ngettext('You have selected "%s". Please confirm your selection. Deleted image is not recoverable.',
'You have selected "%s". Please confirm your selection. Deleted images are not recoverable.', count),
/* eslint-enable max-len */
submit: ngettext('Delete Image',
'Delete Images', count),
success: ngettext('Deleted Image: %s.',
'Deleted Images: %s.', count),
error: ngettext('Unable to delete Image: %s.',
'Unable to delete Images: %s.', count)
};
}
// for batch delete
function checkPermission(selected) {
return {promise: allowed(selected), context: selected};
}
// for batch delete
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) {
// To make the result of this action generically useful, reformat the return
// from the deleteModal into a standard form
var result = actionResult.getActionResult();
deleteModalResult.pass.forEach(function markDeleted(item) {
result.updated(resourceType, getEntity(item).id);
});
deleteModalResult.fail.forEach(function markFailed(item) {
result.failed(resourceType, getEntity(item).id);
});
var indexPath = '/admin/container/images';
var currentPath = $location.path();
if (result.result.failed.length === 0 && result.result.updated.length > 0 &&
currentPath !== indexPath) {
$location.path(indexPath);
} else {
$rootScope.$broadcast(tableEvents.CLEAR_SELECTIONS);
return result.result;
}
}
function getMessage(message, entities) {
return interpolate(message, [entities.map(getName).join(", ")]);
}
function getName(result) {
return getEntity(result).name;
}
// for batch delete
function getEntity(result) {
return result.context;
}
// call delete REST API
function deleteEntity(id) {
return zun.deleteImage(id);
} }
} }
})(); })();

View File

@ -243,9 +243,12 @@
return apiService.get(imagesPath).error(error(msg)); return apiService.get(imagesPath).error(error(msg));
} }
function deleteImage(id, host) { function deleteImage(id, suppressError) {
var msg = gettext('Unable to delete the Image.'); var promise = apiService.delete(imagesPath, [id]);
return apiService.delete(imagesPath + id, {host: host}).error(error(msg)); return suppressError ? promise : promise.error(function() {
var msg = gettext('Unable to delete the Image with id: %(id)s');
toastService.add('error', interpolate(msg, { id: id }, true));
});
} }
/////////// ///////////