mirror of
https://github.com/Joxit/docker-registry-ui.git
synced 2025-04-27 15:39:54 +03:00
feat(riot-v5): add notification via material-snackbar
This commit is contained in:
parent
926f67e1b5
commit
761a680703
16 changed files with 80 additions and 47 deletions
|
@ -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 () {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue