mirror of
https://github.com/Joxit/docker-registry-ui.git
synced 2025-04-29 08:29:54 +03:00
feat(riot-v5): upgrade add-registry-url dialog
This commit is contained in:
parent
bb3182d56e
commit
e6af9321a8
9 changed files with 154 additions and 79 deletions
|
@ -53,16 +53,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
},
|
},
|
||||||
|
|
||||||
display(props, state) {
|
display(props, state) {
|
||||||
this.state.repositories = [];
|
let repositories = [];
|
||||||
const self = this;
|
const self = this;
|
||||||
const oReq = new Http();
|
const oReq = new Http();
|
||||||
oReq.addEventListener('load', function () {
|
oReq.addEventListener('load', function () {
|
||||||
state.repositories = [];
|
|
||||||
if (this.status == 200) {
|
if (this.status == 200) {
|
||||||
state.repositories = JSON.parse(this.responseText).repositories || [];
|
repositories = JSON.parse(this.responseText).repositories || [];
|
||||||
state.repositories.sort();
|
repositories.sort();
|
||||||
state.length = state.repositories.length;
|
repositories = repositories.reduce(function (acc, e) {
|
||||||
state.repositories = state.repositories.reduce(function (acc, e) {
|
|
||||||
const slash = e.indexOf('/');
|
const slash = e.indexOf('/');
|
||||||
if (slash > 0) {
|
if (slash > 0) {
|
||||||
const repoName = e.substring(0, slash) + '/';
|
const repoName = e.substring(0, slash) + '/';
|
||||||
|
@ -86,14 +84,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
});
|
});
|
||||||
oReq.addEventListener('error', function () {
|
oReq.addEventListener('error', function () {
|
||||||
self.props.onNotify(this.getErrorMessage(), true);
|
self.props.onNotify(this.getErrorMessage(), true);
|
||||||
state.repositories = [];
|
|
||||||
});
|
});
|
||||||
oReq.addEventListener('loadend', function () {
|
oReq.addEventListener('loadend', function () {
|
||||||
self.update({
|
self.update({
|
||||||
|
repositories,
|
||||||
|
length: repositories.length,
|
||||||
loadend: true
|
loadend: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
oReq.open('GET', props.registryUrl + '/v2/_catalog?n=' + state.catalogElementsLimit);
|
oReq.open('GET', `${props.registryUrl}/v2/_catalog?n=${state.catalogElementsLimit}`);
|
||||||
oReq.send();
|
oReq.send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
65
src/components/dialogs/add-registry-url.riot
Normal file
65
src/components/dialogs/add-registry-url.riot
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<!--
|
||||||
|
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/>.
|
||||||
|
-->
|
||||||
|
<add-registry-url>
|
||||||
|
<material-popup opened="{ props.opened }" onClick="{ props.onClose }">
|
||||||
|
<div slot="title">Add your Server ?</div>
|
||||||
|
<div slot="content">
|
||||||
|
<material-input onkeyup="{ onKeyUp }" placeholder="Server URL"></material-input>
|
||||||
|
<span>Write your URL without /v2</span>
|
||||||
|
</div>
|
||||||
|
<div slot="action">
|
||||||
|
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ add }">
|
||||||
|
Add
|
||||||
|
</material-button>
|
||||||
|
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ props.onClose }">
|
||||||
|
Cancel
|
||||||
|
</material-button>
|
||||||
|
</div>
|
||||||
|
</material-popup>
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
getRegistryServers,
|
||||||
|
updateHistory
|
||||||
|
} from '../../scripts/utils';
|
||||||
|
import router from '../../scripts/router';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
onKeyUp(event) {
|
||||||
|
// if keyCode is Enter
|
||||||
|
if (event.keyCode === 13) {
|
||||||
|
this.add();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
add() {
|
||||||
|
const input = this.$('input');
|
||||||
|
if (!input || !input.value || input.value.length === 0) {
|
||||||
|
return this.props.onNotify('The input field is empty. Please enter an url.', true);
|
||||||
|
}
|
||||||
|
if (!input.value.startsWith('http')) {
|
||||||
|
return this.props.onNotify('The input field should start with http:// or https://.', true);
|
||||||
|
}
|
||||||
|
const url = input.value.trim().replace(/\/*$/, '');
|
||||||
|
const registryServer = getRegistryServers().filter(e => e !== url);
|
||||||
|
localStorage.setItem('registryServer', JSON.stringify([url].concat(registryServer)));
|
||||||
|
router.home()
|
||||||
|
this.props.onServerChange(url);
|
||||||
|
this.props.onClose()
|
||||||
|
setTimeout(() => updateHistory(url), 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</add-registry-url>
|
|
@ -21,8 +21,15 @@
|
||||||
<material-dropdown-list items="{ dropdownItems }" onSelect="{ onDropdownSelect }"
|
<material-dropdown-list items="{ dropdownItems }" onSelect="{ onDropdownSelect }"
|
||||||
opened="{ state.isDropdownOpened }" />
|
opened="{ state.isDropdownOpened }" />
|
||||||
<div class="overlay" onclick="{ onClick }" if="{ state.isDropdownOpened }"></div>
|
<div class="overlay" onclick="{ onClick }" if="{ state.isDropdownOpened }"></div>
|
||||||
|
<add-registry-url opened="{ state['add-registry-url'] }" on-close="{ onClose('add-registry-url') }"
|
||||||
|
on-notify="{ props.onNotify }" on-server-change="{ props.onServerChange }"></add-registry-url>
|
||||||
<script>
|
<script>
|
||||||
|
import AddRegistryUrl from './add-registry-url.riot';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
AddRegistryUrl
|
||||||
|
},
|
||||||
dropdownItems: [{
|
dropdownItems: [{
|
||||||
title: 'Add URL',
|
title: 'Add URL',
|
||||||
name: 'add-registry-url'
|
name: 'add-registry-url'
|
||||||
|
@ -33,7 +40,20 @@
|
||||||
title: 'Remove URL',
|
title: 'Remove URL',
|
||||||
name: 'remove-registry-url'
|
name: 'remove-registry-url'
|
||||||
}],
|
}],
|
||||||
onDropdownSelect(key, item) {},
|
onDropdownSelect(key, item) {
|
||||||
|
this.update({
|
||||||
|
[item.name]: true,
|
||||||
|
isDropdownOpened: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onClose(name) {
|
||||||
|
return () => {
|
||||||
|
this.update({
|
||||||
|
[name]: false,
|
||||||
|
isDropdownOpened: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
onClick() {
|
onClick() {
|
||||||
this.update({
|
this.update({
|
||||||
isDropdownOpened: !this.state.isDropdownOpened
|
isDropdownOpened: !this.state.isDropdownOpened
|
||||||
|
@ -57,7 +77,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 1;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host material-button {
|
:host material-button {
|
||||||
|
@ -86,5 +106,13 @@
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.2em;
|
line-height: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host material-popup * {
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host material-popup material-button .content {
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</dialogs-menu>
|
</dialogs-menu>
|
|
@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
<header>
|
<header>
|
||||||
<material-navbar>
|
<material-navbar>
|
||||||
<div class="logo">Docker Registry UI</div>
|
<div class="logo">Docker Registry UI</div>
|
||||||
<dialogs-menu></dialogs-menu>
|
<dialogs-menu on-notify="{ notifySnackbar }" on-server-change="{ onServerChange }"></dialogs-menu>
|
||||||
</material-navbar>
|
</material-navbar>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
|
@ -79,16 +79,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
Router,
|
Router,
|
||||||
Route
|
Route
|
||||||
},
|
},
|
||||||
|
onUpdated(props, state) {
|
||||||
|
state.snackbarIsError = false;
|
||||||
|
state.snackbarMessage = undefined;
|
||||||
|
},
|
||||||
onBeforeMount(props) {
|
onBeforeMount(props) {
|
||||||
this.state.registryUrl = props.registryUrl || (window.location.origin + window.location.pathname.replace(/\/+$/,
|
this.state.registryUrl = props.registryUrl || (window.location.origin + window.location.pathname.replace(/\/+$/,
|
||||||
''));
|
''));
|
||||||
this.state.name = props.name || stripHttps(props.registryUrl);
|
this.state.name = props.name || stripHttps(props.registryUrl);
|
||||||
this.state.catalogElementsLimit = props.catalogElementsLimit || 100000;
|
this.state.catalogElementsLimit = props.catalogElementsLimit || 100000;
|
||||||
this.state.pullUrl = this.pullUrl(props);
|
this.state.pullUrl = this.pullUrl(this.state.registryUrl, props.pullUrl);
|
||||||
},
|
},
|
||||||
pullUrl(props) {
|
onServerChange(registryUrl) {
|
||||||
const url = props.pullUrl ||
|
this.update({
|
||||||
(props.registryUrl && props.registryUrl.length > 0 && props.registryUrl) ||
|
registryUrl,
|
||||||
|
name: stripHttps(registryUrl),
|
||||||
|
pullUrl: this.pullUrl(registryUrl),
|
||||||
|
snackbarMessage: 'Registry server changed to `' + registryUrl + '`.'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
pullUrl(registryUrl, pullUrl) {
|
||||||
|
const url = pullUrl ||
|
||||||
|
(registryUrl && registryUrl.length > 0 && registryUrl) ||
|
||||||
window.location.host;
|
window.location.host;
|
||||||
return stripHttps(url);
|
return stripHttps(url);
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,10 +5,13 @@ import {
|
||||||
MaterialNavbar,
|
MaterialNavbar,
|
||||||
MaterialFooter,
|
MaterialFooter,
|
||||||
MaterialButton,
|
MaterialButton,
|
||||||
|
MaterialWaves,
|
||||||
MaterialCheckbox,
|
MaterialCheckbox,
|
||||||
MaterialTabs,
|
MaterialTabs,
|
||||||
MaterialSnackbar,
|
MaterialSnackbar,
|
||||||
MaterialDropdownList
|
MaterialDropdownList,
|
||||||
|
MaterialPopup,
|
||||||
|
MaterialInput
|
||||||
} from 'riot-mui';
|
} from 'riot-mui';
|
||||||
|
|
||||||
import DockerRegistryUI from './components/docker-registry-ui.riot';
|
import DockerRegistryUI from './components/docker-registry-ui.riot';
|
||||||
|
@ -20,10 +23,13 @@ register('material-footer', MaterialFooter);
|
||||||
register('material-navbar', MaterialNavbar);
|
register('material-navbar', MaterialNavbar);
|
||||||
register('material-spinner', MaterialSpinner);
|
register('material-spinner', MaterialSpinner);
|
||||||
register('material-button', MaterialButton);
|
register('material-button', MaterialButton);
|
||||||
|
register('material-waves', MaterialWaves);
|
||||||
register('material-checkbox', MaterialCheckbox);
|
register('material-checkbox', MaterialCheckbox);
|
||||||
register('material-snackbar', MaterialSnackbar);
|
register('material-snackbar', MaterialSnackbar);
|
||||||
register('material-tabs', MaterialTabs);
|
register('material-tabs', MaterialTabs);
|
||||||
register('material-dropdown-list', MaterialDropdownList);
|
register('material-dropdown-list', MaterialDropdownList);
|
||||||
|
register('material-popup', MaterialPopup);
|
||||||
|
register('material-input', MaterialInput);
|
||||||
|
|
||||||
const createApp = component(DockerRegistryUI);
|
const createApp = component(DockerRegistryUI);
|
||||||
const tags = document.getElementsByTagName('docker-registry-ui');
|
const tags = document.getElementsByTagName('docker-registry-ui');
|
||||||
|
|
|
@ -54,6 +54,7 @@ export class Http {
|
||||||
self.oReq.addEventListener('loadend', function () {
|
self.oReq.addEventListener('loadend', function () {
|
||||||
if (this.status == 401) {
|
if (this.status == 401) {
|
||||||
const req = new XMLHttpRequest();
|
const req = new XMLHttpRequest();
|
||||||
|
req._url = self._url;
|
||||||
req.open(self._method, self._url);
|
req.open(self._method, self._url);
|
||||||
for (key in self._events) {
|
for (key in self._events) {
|
||||||
req.addEventListener(key, self._events[key]);
|
req.addEventListener(key, self._events[key]);
|
||||||
|
@ -97,6 +98,7 @@ export class Http {
|
||||||
open(m, u) {
|
open(m, u) {
|
||||||
this._method = m;
|
this._method = m;
|
||||||
this._url = u;
|
this._url = u;
|
||||||
|
this.oReq._url = u;
|
||||||
this.oReq.open(m, u);
|
this.oReq.open(m, u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,20 +116,20 @@ const hasHeader = function (header) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getErrorMessage = function () {
|
const getErrorMessage = function () {
|
||||||
if (registryUI.url() && registryUI.url().match('^http://') && window.location.protocol === 'https:') {
|
if (this._url.match('^http://') && window.location.protocol === 'https:') {
|
||||||
return (
|
return (
|
||||||
'Mixed Content: The page at `' +
|
'Mixed Content: The page at `' +
|
||||||
window.location.origin +
|
window.location.origin +
|
||||||
'` was loaded over HTTPS, but requested an insecure server endpoint `' +
|
'` was loaded over HTTPS, but requested an insecure server endpoint `' +
|
||||||
registryUI.url() +
|
new URL(this._url).origin +
|
||||||
'`. This request has been blocked; the content must be served over HTTPS.'
|
'`. This request has been blocked; the content must be served over HTTPS.'
|
||||||
);
|
);
|
||||||
} else if (!registryUI.url()) {
|
} else if (!this._url || !this._url.match('^http')) {
|
||||||
return 'Incorrect server endpoint.';
|
return 'Incorrect server endpoint.';
|
||||||
} else if (this.withCredentials && !this.hasHeader('Access-Control-Allow-Credentials')) {
|
} else if (this.withCredentials && !this.hasHeader('Access-Control-Allow-Credentials')) {
|
||||||
return (
|
return (
|
||||||
"The `Access-Control-Allow-Credentials` header in the response is missing and must be set to `true` when the request's credentials mode is on. Origin `" +
|
"The `Access-Control-Allow-Credentials` header in the response is missing and must be set to `true` when the request's credentials mode is on. Origin `" +
|
||||||
registryUI.url() +
|
new URL(this._url).origin +
|
||||||
'` is therefore not allowed access.'
|
'` is therefore not allowed access.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,3 +159,22 @@ export const ERROR_CAN_NOT_READ_CONTENT_DIGEST = {
|
||||||
'https://docs.docker.com/registry/configuration/#http',
|
'https://docs.docker.com/registry/configuration/#http',
|
||||||
isError: true,
|
isError: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getRegistryServers(i) {
|
||||||
|
try {
|
||||||
|
const res = JSON.parse(localStorage.getItem('registryServer'));
|
||||||
|
if (res instanceof Array) {
|
||||||
|
return !isNaN(i) ? res[i] : res.map((url) => url.trim().replace(/\/*$/, ''));
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
return !isNaN(i) ? '' : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeURI(url) {
|
||||||
|
if (!url) { return; }
|
||||||
|
return url.indexOf('&') < 0 ? window.encodeURIComponent(url) : btoa(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function updateHistory(url) {
|
||||||
|
updateQueryString({ url: encodeURI(url) })
|
||||||
|
}
|
|
@ -19,10 +19,13 @@
|
||||||
@import 'riot-mui/src/material-elements/material-card/material-card.scss';
|
@import 'riot-mui/src/material-elements/material-card/material-card.scss';
|
||||||
@import 'riot-mui/src/material-elements/material-spinner/material-spinner.scss';
|
@import 'riot-mui/src/material-elements/material-spinner/material-spinner.scss';
|
||||||
@import 'riot-mui/src/material-elements/material-button/material-button.scss';
|
@import 'riot-mui/src/material-elements/material-button/material-button.scss';
|
||||||
|
@import 'riot-mui/src/material-elements/material-waves/material-waves.scss';
|
||||||
@import 'riot-mui/src/material-elements/material-checkbox/material-checkbox.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-tabs/material-tabs.scss';
|
||||||
@import 'riot-mui/src/material-elements/material-snackbar/material-snackbar.scss';
|
@import 'riot-mui/src/material-elements/material-snackbar/material-snackbar.scss';
|
||||||
@import 'riot-mui/src/material-elements/material-dropdown-list/material-dropdown-list.scss';
|
@import 'riot-mui/src/material-elements/material-dropdown-list/material-dropdown-list.scss';
|
||||||
|
@import 'riot-mui/src/material-elements/material-popup/material-popup.scss';
|
||||||
|
@import 'riot-mui/src/material-elements/material-input/material-input.scss';
|
||||||
|
|
||||||
@import './roboto.scss';
|
@import './roboto.scss';
|
||||||
@import './material-icons.scss';
|
@import './material-icons.scss';
|
||||||
|
|
|
@ -1,59 +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/>.
|
|
||||||
-->
|
|
||||||
<add>
|
|
||||||
<material-popup>
|
|
||||||
<div class="material-popup-title">Add your Server ?</div>
|
|
||||||
<div class="material-popup-content">
|
|
||||||
<material-input onkeyup="{ registryUI.addTag.onkeyup }" placeholder="Server URL"></material-input>
|
|
||||||
<span>Write your URL without /v2</span>
|
|
||||||
</div>
|
|
||||||
<div class="material-popup-action">
|
|
||||||
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="registryUI.addTag.add();">Add</material-button>
|
|
||||||
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="registryUI.addTag.close();">Cancel</material-button>
|
|
||||||
</div>
|
|
||||||
</material-popup>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
registryUI.addTag = registryUI.addTag || {};
|
|
||||||
this.one('mount', function () {
|
|
||||||
registryUI.addTag.dialog = this.tags['material-popup'];
|
|
||||||
registryUI.addTag.dialog.getAddServer = function() {
|
|
||||||
return this.tags['material-input'] ? this.tags['material-input'].value : '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
registryUI.addTag.onkeyup = function (e) {
|
|
||||||
// if keyCode is Enter
|
|
||||||
if (e.keyCode == 13) {
|
|
||||||
registryUI.addTag.add();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
registryUI.addTag.show = function () {
|
|
||||||
registryUI.addTag.dialog.open();
|
|
||||||
};
|
|
||||||
registryUI.addTag.add = function () {
|
|
||||||
if (registryUI.addTag.dialog.getAddServer().length > 0) {
|
|
||||||
registryUI.addServer(registryUI.addTag.dialog.getAddServer());
|
|
||||||
}
|
|
||||||
registryUI.home();
|
|
||||||
registryUI.addTag.close();
|
|
||||||
};
|
|
||||||
registryUI.addTag.close = function () {
|
|
||||||
registryUI.addTag.dialog.tags['material-input'].value = '';
|
|
||||||
registryUI.addTag.dialog.close();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</add>
|
|
Loading…
Add table
Add a link
Reference in a new issue