feat(riot-v5): add notification via material-snackbar

This commit is contained in:
Joxit 2021-03-24 00:02:07 +01:00
parent 926f67e1b5
commit 761a680703
No known key found for this signature in database
GPG Key ID: F526592B8E012263
16 changed files with 80 additions and 47 deletions

View File

@ -51,9 +51,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
onMounted(props) {
this.display(props, this.state)
},
onUpdated(props, state) {
},
display(props, state) {
this.state.repositories = [];
@ -82,13 +79,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
return acc;
}, []);
} else if (this.status == 404) {
// registryUI.snackbar('Server not found', true);
self.props.onNotify('Server not found', true);
} else {
// registryUI.snackbar(this.responseText);
self.props.onNotify(this.responseText);
}
});
oReq.addEventListener('error', function () {
// registryUI.snackbar(this.getErrorMessage(), true);
self.props.onNotify(this.getErrorMessage(), true);
state.repositories = [];
});
oReq.addEventListener('loadend', function () {

View File

@ -24,19 +24,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<router base="#!">
<route path="{baseRoute}">
<catalog registry-url="{ state.registryUrl }" registry-name="{ state.name }"
catalog-elements-limit="{ state.catalogElementsLimit }" />
catalog-elements-limit="{ state.catalogElementsLimit }" on-notify="{ notifySnackbar }" />
</route>
<route path="{baseRoute}taglist/(.*)">
<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>
is-image-remove-activated="{props.isImageRemoveActivated}" on-notify="{ notifySnackbar }"></tag-list>
</route>
<route path="{baseRoute}taghistory/(.*)">
<tag-history registry-url="{ state.registryUrl }" registry-name="{ state.name }" pull-url="{ state.pullUrl }"
image="{ router.getTagHistoryImage() }" tag="{ router.getTagHistoryTag() }"
is-image-remove-activated="{props.isImageRemoveActivated}"></tag-history>
is-image-remove-activated="{props.isImageRemoveActivated}" on-notify="{ notifySnackbar }"></tag-history>
</route>
</router>
<material-snackbar message="{ state.snackbarMessage }" is-error="{ state.snackbarIsError }"></material-snackbar>
</main>
<footer>
<material-footer>
@ -88,6 +89,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
window.location.host;
return stripHttps(url);
},
notifySnackbar(message, isError) {
if (typeof message === 'string') {
this.update({
snackbarMessage: message,
snackbarIsError: isError || false
});
} else if (message && message.message) {
this.update({
snackbarMessage: message.message,
snackbarIsError: message.isError
});
}
},
baseRoute: '/(\\?[^#]*)?(#!)?(/?)',
router,
version

View File

@ -29,7 +29,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<material-spinner />
</div>
<material-tabs if="{ state.archs && state.loadend }" useLine="{ true }" tabs="{ state.archs }" onTabChanged="{ onTabChanged }" />
<material-tabs if="{ state.archs && state.loadend }" useLine="{ true }" tabs="{ state.archs }"
onTabChanged="{ onTabChanged }" />
<material-card each="{ element in state.elements }" class="tag-history-element">
<tag-history-element each="{ entry in element }" if="{ entry.value && entry.value.length > 0}" entry="{ entry }" />
@ -49,7 +50,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
},
onBeforeMount(props, state) {
state.elements = [];
state.image = new DockerImage(props.image, props.tag, true, props.registryUrl);
state.image = new DockerImage(props.image, props.tag, true, props.registryUrl, props.onNotify);
state.image.fillInfo()
state.image.on('blobs', this.processBlobs);
state.image.on('list', this.multiArchList)
@ -58,7 +59,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
const state = this.state;
state.elements = []
state.image.variants[idx] = state.image.variants[idx] ||
new DockerImage(this.props.image, arch.digest, false, this.props.registryUrl);
new DockerImage(this.props.image, arch.digest, false, this.props.registryUrl, this.props.onNotify);
if (state.image.variants[idx].blobs) {
return this.processBlobs(state.image.variants[idx].blobs);
}

View File

@ -23,6 +23,9 @@
</material-button>
</div>
<script>
import {
ERROR_CAN_NOT_READ_CONTENT_DIGEST
} from '../../scripts/utils';
export default {
onBeforeMount(props, state) {
const prefix = 'docker pull ' + props.pullUrl + '/' + props.image.name;
@ -39,7 +42,7 @@
},
copy() {
if (!this.state.dockerCmd) {
// registryUI.showErrorCanNotReadContentDigest();
this.props.onNotify(ERROR_CAN_NOT_READ_CONTENT_DIGEST);
return;
}
const copyText = this.$('input');
@ -48,7 +51,7 @@
document.execCommand('copy');
copyText.style.display = 'none';
// registryUI.snackbar('`' + this.state.dockerCmd + '` has been copied to clipboard.')
this.props.onNotify('`' + this.state.dockerCmd + '` has been copied to clipboard.')
}
}
</script>

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

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

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

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

View File

@ -41,17 +41,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
state.checked = props.checked;
},
deleteImage(ignoreError) {
// registryUI.taglist.go(name);
deleteImage(this.props.image, this.props.registryUrl, ignoreError)
deleteImage(this.props.image, this.props.registryUrl, ignoreError, this.props.onNotify)
},
handleCheckboxChange(checked) {
this.props.handleCheckboxChange(checked, this.props.image);
}
}
export function deleteImage(image, registryUrl, ignoreError) {
export function deleteImage(image, registryUrl, ignoreError, onNotify) {
if (!image.digest) {
// registryUI.snackbar('Information for ' + name + ':' + tag + ' are not yet loaded.');
onNotify(`Information for ${name}:${tag} are not yet loaded.`);
return;
}
const name = image.name;
@ -60,21 +59,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
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');
onNotify(`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.');
ignoreError || onNotify({
message: 'Digest not found for this image in your registry.',
isError: true
});
} else {
// registryUI.snackbar(this.responseText);
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 () {
registryUI.errorSnackbar(
'An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].'
);
onNotify({
message: 'An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].',
isError: true
});
});
oReq.send();
}

View File

@ -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 }" registry-url="{ props.registryUrl }" pull-url="{ props.pullUrl }"></tag-table>
onReverseOrder="{ onReverseOrder }" registry-url="{ props.registryUrl }" pull-url="{ props.pullUrl }" on-notify="{ props.onNotify }"></tag-table>
<pagination pages="{ getPageLabels(state.page, getNumPages(state.tags)) }" onPageUpdate="{onPageUpdate}"></pagination>
@ -87,20 +87,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
if (this.status == 200) {
const tags = JSON.parse(this.responseText).tags || [];
state.tags = tags.map(function (tag) {
return new DockerImage(props.image, tag, null, props.registryUrl);
return new DockerImage(props.image, tag, null, props.registryUrl, props.onNotify);
}).sort(compare);
window.requestAnimationFrame(self.onResize);
self.update({
page: Math.min(state.page, getNumPages(state.tags))
})
} else if (this.status == 404) {
// registryUI.snackbar('Server not found', true);
self.props.onNotify('Server not found', true);
} else {
// registryUI.snackbar(this.responseText, true);
self.props.onNotify(this.responseText, true);
}
});
oReq.addEventListener('error', function () {
// registryUI.snackbar(this.getErrorMessage(), true);
self.props.onNotify(this.getErrorMessage(), true);
state.tags = [];
});
oReq.addEventListener('loadend', function () {

View File

@ -49,18 +49,21 @@ 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 }" pull-url="{ props.pullUrl }" />
<copy-to-clipboard target="digest" image="{ image }" pull-url="{ props.pullUrl }"
on-notify="{ props.onNotify }" />
</td>
<td>
<image-tag image="{ image }" />
<copy-to-clipboard target="tag" image="{ image }" pull-url="{ props.pullUrl }" />
<copy-to-clipboard target="tag" image="{ image }" pull-url="{ props.pullUrl }"
on-notify="{ props.onNotify }" />
</td>
<td class="show-tag-history">
<tag-history-button image="{ image }" />
</td>
<td if="{ props.isImageRemoveActivated }" class="remove-tag">
<remove-image multi-delete="{ state.multiDelete }" image="{ image }" registry-url="{ props.registryUrl }"
handleCheckboxChange="{ onRemoveImageChange }" checked="{ state.toDelete.has(image) }" />
handleCheckboxChange="{ onRemoveImageChange }" checked="{ state.toDelete.has(image) }"
on-notify="{ props.onNotify }" />
</td>
</tr>
</tbody>
@ -106,7 +109,7 @@ 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.registryUrl, true))
this.state.toDelete.forEach(image => deleteImage(image, this.props.registryUrl, true, this.props.onNotify))
},
onRemoveImageHeaderChange(checked, event) {
if (event.altKey === true) {

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

View File

@ -7,6 +7,7 @@ import {
MaterialButton,
MaterialCheckbox,
MaterialTabs,
MaterialSnackbar,
} from 'riot-mui';
import DockerRegistryUI from './components/docker-registry-ui.riot';
@ -19,6 +20,7 @@ register('material-navbar', MaterialNavbar);
register('material-spinner', MaterialSpinner);
register('material-button', MaterialButton);
register('material-checkbox', MaterialCheckbox);
register('material-snackbar', MaterialSnackbar);
register('material-tabs', MaterialTabs);
const createApp = component(DockerRegistryUI);

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Http } from './http';
import { isDigit, eventTransfer } from './utils';
import { isDigit, eventTransfer, ERROR_CAN_NOT_READ_CONTENT_DIGEST } from './utils';
import observable from '@riotjs/observable';
const tagReduce = (acc, e) => {
@ -46,12 +46,13 @@ export function compare(e1, e2) {
}
export class DockerImage {
constructor(name, tag, list, registryUrl) {
constructor(name, tag, list, registryUrl, onNotify) {
this.name = name;
this.tag = tag;
this.list = list;
this.registryUrl = registryUrl;
this.chars = 0;
this.onNotify = onNotify;
observable(this);
this.on('get-size', function () {
if (this.size !== undefined) {
@ -97,7 +98,7 @@ export class DockerImage {
if (response.mediaType === 'application/vnd.docker.distribution.manifest.list.v2+json') {
self.trigger('list', response);
const manifest = response.manifests[0];
const image = new DockerImage(self.name, manifest.digest, false, self.registryUrl);
const image = new DockerImage(self.name, manifest.digest, false, self.registryUrl, self.onNotify);
eventTransfer(image, self);
image.fillInfo();
self.variants = [image];
@ -114,14 +115,14 @@ export class DockerImage {
self.digest = digest;
self.trigger('content-digest', digest);
if (!digest) {
// registryUI.showErrorCanNotReadContentDigest();
self.onNotify(ERROR_CAN_NOT_READ_CONTENT_DIGEST);
}
});
self.getBlobs(response.config.digest);
} else if (this.status == 404) {
// registryUI.errorSnackbar('Manifest for ' + self.name + ':' + self.tag + ' not found');
self.onNotify(`Manifest for ${self.name}:${self.tag} not found`, true);
} else {
// registryUI.snackbar(this.responseText);
self.onNotify(this.responseText);
}
});
oReq.open('GET', this.registryUrl + '/v2/' + self.name + '/manifests/' + self.tag);
@ -152,9 +153,9 @@ export class DockerImage {
self.trigger('creation-date', self.creationDate);
self.trigger('blobs', self.blobs);
} else if (this.status == 404) {
registryUI.errorSnackbar('Blobs for ' + self.name + ':' + self.tag + ' not found');
self.onNotify(`Blobs for ${self.name}:${self.tag} not found`, true);
} else {
registryUI.snackbar(this.responseText);
self.onNotify(this.responseText);
}
});
oReq.open('GET', this.registryUrl + '/v2/' + self.name + '/blobs/' + blob);

View File

@ -149,4 +149,13 @@ export function eventTransfer(from, to) {
export function isDigit(char) {
return char >= '0' && char <= '9';
}
export const ERROR_CAN_NOT_READ_CONTENT_DIGEST = {
message:
'Access on registry response was blocked. Try adding the header ' +
'`Access-Control-Expose-Headers: Docker-Content-Digest`' +
' to your proxy or registry: ' +
'https://docs.docker.com/registry/configuration/#http',
isError: true,
};

View File

@ -21,6 +21,7 @@
@import 'riot-mui/src/material-elements/material-button/material-button.scss';
@import 'riot-mui/src/material-elements/material-checkbox/material-checkbox.scss';
@import 'riot-mui/src/material-elements/material-tabs/material-tabs.scss';
@import 'riot-mui/src/material-elements/material-snackbar/material-snackbar.scss';
@import './roboto.scss';
@import './material-icons.scss';