mirror of
https://github.com/Joxit/docker-registry-ui.git
synced 2025-04-26 15:09:53 +03:00
feat(confirm-delete): add dialog for confirmation of multiple image delete
This commit is contained in:
parent
f02c99f12d
commit
e065298eed
4 changed files with 123 additions and 49 deletions
|
@ -27,7 +27,7 @@
|
|||
"core-js": "^3.9.1",
|
||||
"js-beautify": "^1.13.0",
|
||||
"riot": "^5.3.1",
|
||||
"riot-mui": "joxit/riot-5-mui#03c37c7",
|
||||
"riot-mui": "joxit/riot-5-mui#ba273d7",
|
||||
"rollup": "^2.34.2",
|
||||
"rollup-plugin-app-utils": "^1.0.6",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
|
|
108
src/components/dialogs/confirm-delete-image.riot
Normal file
108
src/components/dialogs/confirm-delete-image.riot
Normal file
|
@ -0,0 +1,108 @@
|
|||
<!--
|
||||
Copyright (C) 2016-2021 Jones Magloire @Joxit
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<confirm-delete-image>
|
||||
<material-popup opened="{ props.opened }" onClick="{ props.onClick }">
|
||||
<div slot="title">These images will be deleted</div>
|
||||
<div slot="content">
|
||||
<ul>
|
||||
<li each="{ image in displayImagesToDelete(props.toDelete, props.tags) }">{ image.name }:{ image.tag }</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div slot="action">
|
||||
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ deleteImages }">
|
||||
Delete
|
||||
</material-button>
|
||||
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ props.onClick }">
|
||||
Cancel
|
||||
</material-button>
|
||||
</div>
|
||||
</material-popup>
|
||||
<script>
|
||||
import {
|
||||
Http
|
||||
} from '../../scripts/http';
|
||||
import router from '../../scripts/router';
|
||||
export default {
|
||||
displayImagesToDelete(toDelete, tags) {
|
||||
const digests = new Set();
|
||||
toDelete.forEach(image => {
|
||||
if (image.digest) {
|
||||
digests.add(image.digest);
|
||||
}
|
||||
})
|
||||
return tags.filter(image => digests.has(image.digest))
|
||||
},
|
||||
deleteImages() {
|
||||
this.props.toDelete.forEach(image => this.deleteImage(image, this.props));
|
||||
},
|
||||
deleteImage(image, opts) {
|
||||
const {
|
||||
registryUrl,
|
||||
ignoreError,
|
||||
onNotify,
|
||||
onAuthentication,
|
||||
onClick
|
||||
} = opts;
|
||||
if (!image.digest) {
|
||||
onNotify(`Information for ${name}:${tag} are not yet loaded.`);
|
||||
return;
|
||||
}
|
||||
const name = image.name;
|
||||
const tag = image.tag;
|
||||
const oReq = new Http({
|
||||
onAuthentication: onAuthentication
|
||||
});
|
||||
oReq.addEventListener('loadend', function () {
|
||||
if (this.status == 200 || this.status == 202) {
|
||||
router.taglist(name);
|
||||
onNotify(`Deleting ${name}:${tag} image. Run \`registry garbage-collect config.yml\` on your registry`);
|
||||
} else if (this.status == 404) {
|
||||
ignoreError || onNotify({
|
||||
message: 'Digest not found for this image in your registry.',
|
||||
isError: true
|
||||
});
|
||||
} else {
|
||||
onNotify(this.responseText);
|
||||
}
|
||||
onClick();
|
||||
});
|
||||
oReq.open('DELETE', `${registryUrl}/v2/${name}/manifests/${image.digest}`);
|
||||
oReq.setRequestHeader('Accept',
|
||||
'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json');
|
||||
oReq.addEventListener('error', function () {
|
||||
onNotify({
|
||||
message: 'An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].',
|
||||
isError: true
|
||||
});
|
||||
});
|
||||
oReq.send();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
:host {
|
||||
color: #000;
|
||||
list-style-type: disc;
|
||||
margin-block-start: 0.7em;
|
||||
}
|
||||
|
||||
:host material-popup .content .material-popup-content {
|
||||
overflow-y: auto;
|
||||
max-height: 250px;
|
||||
}
|
||||
</style>
|
||||
</confirm-delete-image>
|
|
@ -52,46 +52,5 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
this.props.handleCheckboxChange(checked, this.props.image);
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteImage(image, opts) {
|
||||
const {
|
||||
registryUrl,
|
||||
ignoreError,
|
||||
onNotify,
|
||||
onAuthentication
|
||||
} = opts;
|
||||
if (!image.digest) {
|
||||
onNotify(`Information for ${name}:${tag} are not yet loaded.`);
|
||||
return;
|
||||
}
|
||||
const name = image.name;
|
||||
const tag = image.tag;
|
||||
const oReq = new Http({
|
||||
onAuthentication: onAuthentication
|
||||
});
|
||||
oReq.addEventListener('loadend', function () {
|
||||
if (this.status == 200 || this.status == 202) {
|
||||
router.taglist(name);
|
||||
onNotify(`Deleting ${name}:${tag} image. Run \`registry garbage-collect config.yml\` on your registry`);
|
||||
} else if (this.status == 404) {
|
||||
ignoreError || onNotify({
|
||||
message: 'Digest not found for this image in your registry.',
|
||||
isError: true
|
||||
});
|
||||
} else {
|
||||
onNotify(this.responseText);
|
||||
}
|
||||
});
|
||||
oReq.open('DELETE', `${registryUrl}/v2/${name}/manifests/${image.digest}`);
|
||||
oReq.setRequestHeader('Accept',
|
||||
'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json');
|
||||
oReq.addEventListener('error', function () {
|
||||
onNotify({
|
||||
message: 'An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].',
|
||||
isError: true
|
||||
});
|
||||
});
|
||||
oReq.send();
|
||||
}
|
||||
</script>
|
||||
</remove-image>
|
|
@ -15,6 +15,9 @@ You should have received a copy of the GNU Affero General Public License
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<tag-table>
|
||||
<confirm-delete-image opened="{ state.confirmDeleteImage }" on-click="{ onConfirmDeleteImageClick }"
|
||||
registry-url="{ props.registryUrl }" on-notify="{ props.onNotify }" on-authentication="{ props.onAuthentication }"
|
||||
tags="{ props.tags }" to-delete="{ state.toDelete }"></confirm-delete-image>
|
||||
<material-card class="taglist">
|
||||
<table style="border: none;">
|
||||
<thead>
|
||||
|
@ -87,12 +90,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
import ImageContentDigest from './image-content-digest.riot';
|
||||
import CopyToClipboard from './copy-to-clipboard.riot';
|
||||
import TagHistoryButton from './tag-history-button.riot';
|
||||
import RemoveImage, {
|
||||
deleteImage
|
||||
} from './remove-image.riot';
|
||||
import RemoveImage from './remove-image.riot';
|
||||
import {
|
||||
matchSearch
|
||||
} from '../search-bar.riot';
|
||||
import ConfirmDeleteImage from '../dialogs/confirm-delete-image.riot';
|
||||
export default {
|
||||
components: {
|
||||
ImageDate,
|
||||
|
@ -102,6 +104,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
CopyToClipboard,
|
||||
RemoveImage,
|
||||
TagHistoryButton,
|
||||
ConfirmDeleteImage,
|
||||
},
|
||||
onBeforeMount(props) {
|
||||
this.state = {
|
||||
|
@ -117,10 +120,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
state.page = props.page
|
||||
},
|
||||
bulkDelete() {
|
||||
this.state.toDelete.forEach(image => deleteImage(image, {
|
||||
...this.props,
|
||||
ignoreError: true
|
||||
}))
|
||||
this.update({
|
||||
confirmDeleteImage: true
|
||||
})
|
||||
},
|
||||
onConfirmDeleteImageClick() {
|
||||
this.update({
|
||||
confirmDeleteImage: false
|
||||
})
|
||||
},
|
||||
onRemoveImageHeaderChange(checked, event) {
|
||||
if (event.altKey === true) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue