feat(riot-v5): upgrade remove-image and copy-to-clipboard

This commit is contained in:
Joxit 2021-03-16 21:44:42 +01:00
parent 9c303d32c7
commit 669c3399d0
No known key found for this signature in database
GPG Key ID: F526592B8E012263
9 changed files with 186 additions and 147 deletions

View File

@ -20,7 +20,7 @@ const plugins = [
nodeResolve(),
commonjs(),
scss({ output: `./${output}/docker-registry-ui.css`, outputStyle: 'compressed' }),
babel({ babelHelpers: 'bundled', presets: [['@babel/env', { useBuiltIns: 'usage' }]] }),
babel({ babelHelpers: 'bundled', presets: [['@babel/env', { useBuiltIns: 'usage', corejs: { version: "2" } }]] }),
html({ template: () => htmlUseref('./src/index.html') }),
copy({
targets: [

View File

@ -27,7 +27,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
catalog-elements-limit="{ state.catalogElementsLimit }" />
</route>
<route path="{baseRoute}taglist/(.*)">
<tag-list registry-url="{ state.registryUrl }" registry-name="{ state.name }"
<tag-list registry-url="{ state.registryUrl }" registry-name="{ state.name }" pull-url="{ state.pullUrl }"
image="{ router.getTagListImage() }" show-content-digest="{props.showContentDigest}"
is-image-remove-activated="{props.isImageRemoveActivated}"></tag-list>
</route>
@ -73,6 +73,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
''));
this.state.name = props.name || stripHttps(props.registryUrl);
this.state.catalogElementsLimit = props.catalogElementsLimit || 100000;
this.state.pullUrl = this.pullUrl(props);
},
pullUrl(props) {
const url = props.pullUrl ||
(props.registryUrl && props.registryUrl.length > 0 && props.registryUrl) ||
window.location.host;
return stripHttps(url);
},
baseRoute: '/(\\?[^#]*)?(#!)?(/?)',
router,

View File

@ -0,0 +1,55 @@
<!--
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/>.
-->
<copy-to-clipboard>
<div class="copy-to-clipboard">
<input style="display: none; width: 1px; height: 1px;" value="{ state.dockerCmd }">
<material-button waves-center="true" rounded="true" waves-color="#ddd" onClick="{ copy }"
title="Copy pull command.">
<i class="material-icons">content_copy</i>
</material-button>
</div>
<script>
export default {
onBeforeMount(props, state) {
const prefix = 'docker pull ' + props.pullUrl + '/' + props.image.name;
if (props.target === 'tag') {
state.dockerCmd = prefix + ':' + props.image.tag;
} else {
props.image.one('content-digest', (digest) => {
this.update({
dockerCmd: prefix + '@' + digest
})
});
props.image.trigger('get-content-digest');
}
},
copy() {
if (!this.state.dockerCmd) {
// registryUI.showErrorCanNotReadContentDigest();
return;
}
const copyText = this.$('input');
copyText.style.display = 'block';
copyText.select();
document.execCommand('copy');
copyText.style.display = 'none';
// registryUI.snackbar('`' + this.state.dockerCmd + '` has been copied to clipboard.')
}
}
</script>
</copy-to-clipboard>

View File

@ -0,0 +1,78 @@
<!--
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/>.
-->
<remove-image>
<material-button waves-center="true" rounded="true" waves-color="#ddd" title="This will delete the image."
if="{ !props.multiDelete }" disabled="{ !state.digest }" onClick="{ deleteImage }">
<i class="material-icons">delete</i>
</material-button>
<material-checkbox if="{ props.multiDelete }" title="Select this tag to delete it." disabled="{ !state.digest }"
onChange="{ handleCheckboxChange }">
</material-checkbox>
<script>
import {
Http
} from '../../scripts/http';
import router from '../../scripts/router'
export default {
onBeforeMount(props, state) {
props.image.one('content-digest', (digest) => {
this.update({
digest
});
});
props.image.trigger('get-content-digest');
},
deleteImage(ignoreError) {
// registryUI.taglist.go(name);
deleteImage(this.props.image, this.props.registryUrl, ignoreError)
},
handleCheckboxChange(checked) {
this.props.handleCheckboxChange(checked, this.props.image);
}
}
export function deleteImage(image, registryUrl, ignoreError) {
if (!image.digest) {
// registryUI.snackbar('Information for ' + name + ':' + tag + ' are not yet loaded.');
return;
}
const name = image.name;
const tag = image.tag;
const oReq = new Http();
oReq.addEventListener('loadend', function () {
if (this.status == 200 || this.status == 202) {
router.taglist(name);
// registryUI.snackbar('Deleting ' + name + ':' + tag +
// ' image. Run `registry garbage-collect config.yml` on your registry');
} else if (this.status == 404) {
// ignoreError || registryUI.errorSnackbar('Digest not found for this image in your registry.');
} else {
// registryUI.snackbar(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 () {
registryUI.errorSnackbar(
'An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].'
);
});
oReq.send();
}
</script>
</remove-image>

View File

@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2019 Jones Magloire @Joxit
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
@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<tag-list>
<material-card class="header">
<div class="material-card-title-action ">
<material-button waves-center="true" rounded="true" waves-color="#ddd" onclick="registryUI.home();">
<material-button waves-center="true" rounded="true" waves-color="#ddd" onClick="{ router.home }">
<i class="material-icons">arrow_back</i>
</material-button>
<h2>
@ -38,7 +38,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<tag-table if="{ state.loadend }" tags="{state.tags}" asc="{state.asc}" page="{ state.page }"
show-content-digest="{props.showContentDigest}" is-image-remove-activated="{props.isImageRemoveActivated}"
onReverseOrder="{ onReverseOrder }"></tag-table>
onReverseOrder="{ onReverseOrder }" registry-url="{ props.registryUrl }" pull-url="{ props.pullUrl }"></tag-table>
<pagination pages="{ getPageLabels(state.page, getNumPages(state.tags)) }" onPageUpdate="{onPageUpdate}"></pagination>
@ -56,6 +56,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
} from '../../scripts/utils'
import Pagination from './pagination.riot'
import TagTable from './tag-table.riot'
import router from '../../scripts/router'
export default {
components: {
Pagination,
@ -151,7 +152,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
this.update();
},
getPageLabels,
getNumPages
getNumPages,
router
}
</script>
</tag-list>

View File

@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2019 Jones Magloire @Joxit
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
@ -28,11 +28,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
onclick="{() => props.onReverseOrder() }">Tag
</th>
<th class="show-tag-history">History</th>
<th class="remove-tag { state.toDelete > 0 ? 'delete' : '' }" if="{ props.isImageRemoveActivated }">
<material-checkbox ref="remove-tag-checkbox" class="indeterminate" if="{ state.toDelete === 0}"
title="Toggle multi-delete. Alt+Click to select all tags."></material-checkbox>
<th class="remove-tag { state.toDelete.size > 0 ? 'delete' : '' }" if="{ props.isImageRemoveActivated }">
<material-checkbox ref="remove-tag-checkbox" class="indeterminate" if="{ state.toDelete.size === 0}"
title="Toggle multi-delete. Alt+Click to select all tags." onChange="{ onRemoveImageHeaderChange }">
</material-checkbox>
<material-button waves-center="true" rounded="true" waves-color="#ddd"
title="This will delete selected images." onclick="{ bulkDelete }" if="{ state.toDelete > 0 }">
title="This will delete selected images." onClick="{ bulkDelete }" if="{ state.toDelete.size > 0 }">
<i class="material-icons">delete</i>
</material-button>
</th>
@ -48,17 +49,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</td>
<td if="{ props.showContentDigest }">
<image-content-digest image="{ image }" />
<copy-to-clipboard target="digest" image="{ image }" />
<copy-to-clipboard target="digest" image="{ image }" pull-url="{ props.pullUrl }" />
</td>
<td>
<image-tag image="{ image }" />
<copy-to-clipboard target="tag" image="{ image }" />
<copy-to-clipboard target="tag" image="{ image }" pull-url="{ props.pullUrl }" />
</td>
<td class="show-tag-history">
<tag-history-button image="{ image }" />
</td>
<td if="{ props.isImageRemoveActivated }">
<remove-image multi-delete="{ state.multiDelete }" image="{ image }" />
<td if="{ props.isImageRemoveActivated }" class="remove-tag">
<remove-image multi-delete="{ state.multiDelete }" image="{ image }" registry-url="{ props.registryUrl }"
handleCheckboxChange="{ onRemoveImageChange }" />
</td>
</tr>
</tbody>
@ -75,21 +77,44 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import ImageSize from './image-size.riot';
import ImageTag from './image-tag.riot';
import ImageContentDigest from './image-content-digest.riot';
import CopyToClipboard from './copy-to-clipboard.riot';
import RemoveImage, {
deleteImage
} from './remove-image.riot';
export default {
components: {
ImageDate,
ImageSize,
ImageTag,
ImageContentDigest,
CopyToClipboard,
RemoveImage,
},
onBeforeMount(props) {
this.state = {
toDelete: 0,
multiDelete: false
toDelete: new Set(),
multiDelete: false,
}
},
onMounted(props, state) {},
bulkDelete() {},
bulkDelete() {
this.state.toDelete.forEach(image => deleteImage(image, this.props.registryUrl, true))
},
onRemoveImageHeaderChange(checked) {
this.update({
multiDelete: checked
})
},
onRemoveImageChange(checked, image) {
if (checked) {
this.state.toDelete.add(image)
} else {
this.state.toDelete.delete(image)
}
this.update({
toDelete: this.state.toDelete
})
},
getPage
}
</script>

View File

@ -6,7 +6,7 @@ function baseUrl() {
export default {
home() {
router.pus(baseUrl());
router.push(baseUrl());
},
taglist(image) {
router.push(`${baseUrl()}#!/taglist/${image}`);

View File

@ -1,50 +0,0 @@
<!--
Copyright (C) 2016-2019 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/>.
-->
<copy-to-clipboard>
<div class="copy-to-clipboard">
<input ref="input" style="display: none; width: 1px; height: 1px;" value="{ this.dockerCmd }">
<material-button waves-center="true" rounded="true" waves-color="#ddd" onclick="{ this.copy }" title="Copy pull command.">
<i class="material-icons">content_copy</i>
</material-button>
</div>
<script type="text/javascript">
this.prefix = 'docker pull ' + registryUI.cleanName() + '/' + opts.image.name;
const self = this;
if (opts.target === 'tag') {
self.dockerCmd = self.prefix + ':' + opts.image.tag;
} else {
opts.image.one('content-digest', function (digest) {
self.dockerCmd = self.prefix + '@' + digest;
});
opts.image.trigger('get-content-digest');
}
this.copy = function () {
if (!self.dockerCmd) {
registryUI.showErrorCanNotReadContentDigest();
return;
}
const copyText = this.refs['input'];
copyText.style.display = 'block';
copyText.select();
document.execCommand('copy');
copyText.style.display = 'none';
registryUI.snackbar('`' + this.dockerCmd + '` has been copied to clipboard.')
};
</script>
</copy-to-clipboard>

View File

@ -1,78 +0,0 @@
<!--
Copyright (C) 2016-2019 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/>.
-->
<remove-image>
<material-button waves-center="true" rounded="true" waves-color="#ddd" title="This will delete the image." if="{ !opts.multiDelete }" disabled="{ !this.digest }">
<i class="material-icons">delete</i>
</material-button>
<material-checkbox if="{ opts.multiDelete }" title="Select this tag to delete it." disabled="{ !this.digest }"></material-checkbox>
<script type="text/javascript">
const self = this;
this.on('updated', function() {
if (self.multiDelete == self.opts.multiDelete) {
return;
}
if (self.tags['material-button']) {
self.delete = function(ignoreError) {
const name = self.opts.image.name;
const tag = self.opts.image.tag;
registryUI.taglist.go(name);
if (!self.digest) {
registryUI.snackbar('Information for ' + name + ':' + tag + ' are not yet loaded.');
return;
}
const oReq = new Http();
oReq.addEventListener('loadend', function() {
if (this.status == 200 || this.status == 202) {
registryUI.taglist.display()
registryUI.snackbar('Deleting ' + name + ':' + tag + ' image. Run `registry garbage-collect config.yml` on your registry');
} else if (this.status == 404) {
ignoreError || registryUI.errorSnackbar('Digest not found for this image in your registry.');
} else {
registryUI.snackbar(this.responseText);
}
});
oReq.open('DELETE', registryUI.url() + '/v2/' + name + '/manifests/' + self.digest);
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json');
oReq.addEventListener('error', function() {
registryUI.errorSnackbar('An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].');
});
oReq.send();
};
self.tags['material-button'].root.onclick = function() {
self.delete();
}
}
if (self.tags['material-checkbox']) {
if (!self.opts.multiDelete && self.tags['material-checkbox'].checked) {
self.tags['material-checkbox'].toggle();
}
self.tags['material-checkbox'].on('toggle', function() {
registryUI.taglist.instance.trigger('toggle-remove-image', this.checked);
});
}
self.multiDelete = self.opts.multiDelete;
});
opts.image.one('content-digest', function(digest) {
self.digest = digest;
self.update();
});
opts.image.trigger('get-content-digest');
</script>
</remove-image>