mirror of
https://github.com/Joxit/docker-registry-ui.git
synced 2025-05-01 17:39:54 +03:00
feat(riot-v5): handle page query param & refactor router & remove old code
This commit is contained in:
parent
603b5861fa
commit
8ef411059c
10 changed files with 74 additions and 733 deletions
|
@ -32,8 +32,7 @@
|
||||||
</material-popup>
|
</material-popup>
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
getRegistryServers,
|
getRegistryServers
|
||||||
updateHistory
|
|
||||||
} from '../../scripts/utils';
|
} from '../../scripts/utils';
|
||||||
import router from '../../scripts/router';
|
import router from '../../scripts/router';
|
||||||
|
|
||||||
|
@ -58,7 +57,7 @@
|
||||||
router.home()
|
router.home()
|
||||||
this.props.onServerChange(url);
|
this.props.onServerChange(url);
|
||||||
this.props.onClose()
|
this.props.onClose()
|
||||||
setTimeout(() => updateHistory(url), 100);
|
setTimeout(() => router.updateUrlQueryParam(url), 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -33,8 +33,7 @@
|
||||||
</material-popup>
|
</material-popup>
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
getRegistryServers,
|
getRegistryServers
|
||||||
updateHistory
|
|
||||||
} from '../../scripts/utils';
|
} from '../../scripts/utils';
|
||||||
import router from '../../scripts/router';
|
import router from '../../scripts/router';
|
||||||
export default {
|
export default {
|
||||||
|
@ -52,7 +51,7 @@
|
||||||
router.home()
|
router.home()
|
||||||
this.props.onServerChange(url);
|
this.props.onServerChange(url);
|
||||||
this.props.onClose()
|
this.props.onClose()
|
||||||
setTimeout(() => updateHistory(url), 100);
|
setTimeout(() => router.updateUrlQueryParam(url), 100);
|
||||||
},
|
},
|
||||||
getRegistryServers
|
getRegistryServers
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,10 +38,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
</material-popup>
|
</material-popup>
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
getRegistryServers,
|
getRegistryServers
|
||||||
updateHistory
|
|
||||||
} from '../../scripts/utils';
|
} from '../../scripts/utils';
|
||||||
import router from '../../scripts/router';
|
|
||||||
export default {
|
export default {
|
||||||
remove(event) {
|
remove(event) {
|
||||||
const url = event.currentTarget.attributes.url && event.currentTarget.attributes.url.value;
|
const url = event.currentTarget.attributes.url && event.currentTarget.attributes.url.value;
|
||||||
|
|
|
@ -38,7 +38,8 @@ 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 }"
|
<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}"
|
show-content-digest="{props.showContentDigest}" is-image-remove-activated="{props.isImageRemoveActivated}"
|
||||||
onReverseOrder="{ onReverseOrder }" registry-url="{ props.registryUrl }" pull-url="{ props.pullUrl }" on-notify="{ props.onNotify }"></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>
|
<pagination pages="{ getPageLabels(state.page, getNumPages(state.tags)) }" onPageUpdate="{onPageUpdate}"></pagination>
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
tags: [],
|
tags: [],
|
||||||
loadend: false,
|
loadend: false,
|
||||||
asc: true,
|
asc: true,
|
||||||
page: 1
|
page: router.getPageQueryParam() || 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMounted(props, state) {
|
onMounted(props, state) {
|
||||||
|
@ -117,6 +118,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
this.update({
|
this.update({
|
||||||
page: page
|
page: page
|
||||||
});
|
});
|
||||||
|
router.updatePageQueryParam(page);
|
||||||
},
|
},
|
||||||
|
|
||||||
onResize() {
|
onResize() {
|
||||||
|
|
|
@ -15,23 +15,60 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { router, getCurrentRoute } from '@riotjs/route';
|
import { router, getCurrentRoute } from '@riotjs/route';
|
||||||
|
import { encodeURI } from './utils';
|
||||||
|
|
||||||
function baseUrl() {
|
function getQueryParams() {
|
||||||
return getCurrentRoute().replace(/#!(.*)/, '');
|
const queries = {};
|
||||||
|
window.location.search
|
||||||
|
.slice(1)
|
||||||
|
.split('&')
|
||||||
|
.forEach((qs) => {
|
||||||
|
const splitIndex = qs.indexOf('=');
|
||||||
|
queries[qs.slice(0, splitIndex)] = splitIndex < 0 ? '' : qs.slice(splitIndex + 1);
|
||||||
|
});
|
||||||
|
return queries;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateQueryParams(qs) {
|
||||||
|
const queryParams = getQueryParams();
|
||||||
|
for (let key in qs) {
|
||||||
|
if (qs[key] === null) {
|
||||||
|
delete queryParams[key];
|
||||||
|
} else {
|
||||||
|
queryParams[key] = qs[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return queryParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSearchString(queries) {
|
||||||
|
let search = [];
|
||||||
|
for (let key in queries) {
|
||||||
|
if (queries[key] !== undefined) {
|
||||||
|
search.push(`${key}=${queries[key]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return search.length === 0 ? '' : `?${search.join('&')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseUrl(qs) {
|
||||||
|
const location = window.location;
|
||||||
|
const queryParams = updateQueryParams(qs);
|
||||||
|
return location.origin + location.pathname + toSearchString(queryParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
home() {
|
home() {
|
||||||
router.push(baseUrl());
|
router.push(baseUrl({ page: null }));
|
||||||
},
|
},
|
||||||
taglist(image) {
|
taglist(image) {
|
||||||
router.push(`${baseUrl()}#!/taglist/${image}`);
|
router.push(`${baseUrl({ page: null })}#!/taglist/${image}`);
|
||||||
},
|
},
|
||||||
getTagListImage() {
|
getTagListImage() {
|
||||||
return getCurrentRoute().replace(/^.*(#!)?\/?taglist\//, '');
|
return getCurrentRoute().replace(/^.*(#!)?\/?taglist\//, '');
|
||||||
},
|
},
|
||||||
history(image, tag) {
|
history(image, tag) {
|
||||||
router.push(`${baseUrl()}#!/taghistory/image/${image}/tag/${tag}`);
|
router.push(`${baseUrl({ page: null })}#!/taghistory/image/${image}/tag/${tag}`);
|
||||||
},
|
},
|
||||||
getTagHistoryImage() {
|
getTagHistoryImage() {
|
||||||
return getCurrentRoute().replace(/^.*(#!)?\/?taghistory\/image\/(.*)\/tag\/(.*)\/?$/, '$2');
|
return getCurrentRoute().replace(/^.*(#!)?\/?taghistory\/image\/(.*)\/tag\/(.*)\/?$/, '$2');
|
||||||
|
@ -39,4 +76,18 @@ export default {
|
||||||
getTagHistoryTag() {
|
getTagHistoryTag() {
|
||||||
return getCurrentRoute().replace(/^.*(#!)?\/?taghistory\/image\/(.*)\/tag\/(.*)\/?$/, '$3');
|
return getCurrentRoute().replace(/^.*(#!)?\/?taghistory\/image\/(.*)\/tag\/(.*)\/?$/, '$3');
|
||||||
},
|
},
|
||||||
|
updateQueryString(qs) {
|
||||||
|
const search = toSearchString(updateQueryParams(qs));
|
||||||
|
history.pushState(null, '', search + window.location.hash);
|
||||||
|
},
|
||||||
|
updateUrlQueryParam(url) {
|
||||||
|
this.updateQueryString({ url: encodeURI(url) });
|
||||||
|
},
|
||||||
|
updatePageQueryParam(page) {
|
||||||
|
this.updateQueryString({ page });
|
||||||
|
},
|
||||||
|
getPageQueryParam() {
|
||||||
|
const queries = getQueryParams();
|
||||||
|
return queries['page'];
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,124 +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/>.
|
|
||||||
*/
|
|
||||||
var registryUI = {}
|
|
||||||
registryUI.URL_QUERY_PARAM_REGEX = /[&?]url=/;
|
|
||||||
registryUI.URL_PARAM_REGEX = /^url=/;
|
|
||||||
registryUI.showContentDigest = true;
|
|
||||||
registryUI.catalogElementsLimit = 100000;
|
|
||||||
|
|
||||||
registryUI.url = function(byPassQueryParam) {
|
|
||||||
if (!registryUI._url) {
|
|
||||||
const url = registryUI.getUrlQueryParam();
|
|
||||||
if (url) {
|
|
||||||
try {
|
|
||||||
registryUI._url = registryUI.decodeURI(url);
|
|
||||||
return registryUI._url;
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
registryUI._url = registryUI.getRegistryServer(0);
|
|
||||||
}
|
|
||||||
return registryUI._url;
|
|
||||||
}
|
|
||||||
registryUI.name = function() {
|
|
||||||
return registryUI.stripHttps(registryUI.url());
|
|
||||||
}
|
|
||||||
registryUI.getRegistryServer = function(i) {
|
|
||||||
try {
|
|
||||||
const res = JSON.parse(localStorage.getItem('registryServer'));
|
|
||||||
if (res instanceof Array) {
|
|
||||||
return (!isNaN(i)) ? res[i] : res.map(function(url) {
|
|
||||||
return url.trim().replace(/\/*$/, '');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
return (!isNaN(i)) ? '' : [];
|
|
||||||
}
|
|
||||||
registryUI.addServer = function(url) {
|
|
||||||
const registryServer = registryUI.getRegistryServer();
|
|
||||||
url = url.trim().replace(/\/*$/, '');
|
|
||||||
const index = registryServer.indexOf(url);
|
|
||||||
if (index != -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
registryServer.push(url);
|
|
||||||
if (!registryUI._url) {
|
|
||||||
registryUI.updateHistory(url);
|
|
||||||
}
|
|
||||||
localStorage.setItem('registryServer', JSON.stringify(registryServer));
|
|
||||||
};
|
|
||||||
registryUI.changeServer = function(url) {
|
|
||||||
var registryServer = registryUI.getRegistryServer();
|
|
||||||
url = url.trim().replace(/\/*$/, '');
|
|
||||||
const index = registryServer.indexOf(url);
|
|
||||||
if (index == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
registryServer.splice(index, 1);
|
|
||||||
registryServer = [url].concat(registryServer);
|
|
||||||
registryUI.updateHistory(url);
|
|
||||||
localStorage.setItem('registryServer', JSON.stringify(registryServer));
|
|
||||||
};
|
|
||||||
registryUI.removeServer = function(url) {
|
|
||||||
const registryServer = registryUI.getRegistryServer();
|
|
||||||
url = url.trim().replace(/\/*$/, '');
|
|
||||||
const index = registryServer.indexOf(url);
|
|
||||||
if (index == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
registryServer.splice(index, 1);
|
|
||||||
localStorage.setItem('registryServer', JSON.stringify(registryServer));
|
|
||||||
if (url == registryUI.url()) {
|
|
||||||
registryUI.updateHistory(registryUI.getRegistryServer(0));
|
|
||||||
route('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registryUI.updateHistory = function(url) {
|
|
||||||
registryUI.updateQueryString({ url: registryUI.encodeURI(url) })
|
|
||||||
registryUI._url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
registryUI.getUrlQueryParam = function () {
|
|
||||||
const search = window.location.search;
|
|
||||||
if (registryUI.URL_QUERY_PARAM_REGEX.test(search)) {
|
|
||||||
const param = search.split(/^\?|&/).find(function(param) {
|
|
||||||
return param && registryUI.URL_PARAM_REGEX.test(param);
|
|
||||||
});
|
|
||||||
return param ? param.replace(registryUI.URL_PARAM_REGEX, '') : param;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.encodeURI = function(url) {
|
|
||||||
if (!url) { return; }
|
|
||||||
return url.indexOf('&') < 0 ? window.encodeURIComponent(url) : btoa(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.decodeURI = function(url) {
|
|
||||||
if (!url) { return; }
|
|
||||||
return url.startsWith('http') ? window.decodeURIComponent(url) : atob(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.isImageRemoveActivated = true;
|
|
||||||
registryUI.catalog = {};
|
|
||||||
registryUI.taglist = {};
|
|
||||||
registryUI.taghistory = {};
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', function() {
|
|
||||||
riot.mount('*');
|
|
||||||
});
|
|
|
@ -1,44 +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/>.
|
|
||||||
*/
|
|
||||||
var registryUI = {}
|
|
||||||
registryUI.url = function() {
|
|
||||||
var url = '${URL}';
|
|
||||||
if (!url) {
|
|
||||||
url = window.location.origin + window.location.pathname;
|
|
||||||
return url.endsWith('/') ? url.substr(0, url.length - 1) : url;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
registryUI.name = function() {
|
|
||||||
const name = '${REGISTRY_TITLE}';
|
|
||||||
if (name) {
|
|
||||||
// the user can strip the http prefix if they wish
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
return registryUI.stripHttps(registryUI.url());
|
|
||||||
};
|
|
||||||
registryUI.pullUrl = '${PULL_URL}';
|
|
||||||
registryUI.isImageRemoveActivated = true;
|
|
||||||
registryUI.showContentDigest = true;
|
|
||||||
registryUI.catalogElementsLimit = 100000;
|
|
||||||
registryUI.catalog = {};
|
|
||||||
registryUI.taglist = {};
|
|
||||||
registryUI.taghistory = {};
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', function() {
|
|
||||||
riot.mount('*');
|
|
||||||
});
|
|
|
@ -124,16 +124,6 @@ export function getPageLabels(page, nPages) {
|
||||||
return pageLabels;
|
return pageLabels;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateQueryString(qs) {
|
|
||||||
var search = '';
|
|
||||||
for (var key in qs) {
|
|
||||||
if (qs[key] !== undefined) {
|
|
||||||
search += (search.length > 0 ? '&' : '?') + key + '=' + qs[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
history.pushState(null, '', search + window.location.hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stripHttps(url) {
|
export function stripHttps(url) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return '';
|
return '';
|
||||||
|
@ -171,10 +161,15 @@ export function getRegistryServers(i) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeURI(url) {
|
export function encodeURI(url) {
|
||||||
if (!url) { return; }
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return url.indexOf('&') < 0 ? window.encodeURIComponent(url) : btoa(url);
|
return url.indexOf('&') < 0 ? window.encodeURIComponent(url) : btoa(url);
|
||||||
};
|
}
|
||||||
|
|
||||||
export function updateHistory(url) {
|
export function decodeURI(url) {
|
||||||
updateQueryString({ url: encodeURI(url) })
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return url.startsWith('http') ? window.decodeURIComponent(url) : atob(url);
|
||||||
}
|
}
|
|
@ -1,290 +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/>.
|
|
||||||
-->
|
|
||||||
<app>
|
|
||||||
<header>
|
|
||||||
<material-navbar>
|
|
||||||
<div class="logo">Docker Registry UI</div>
|
|
||||||
<menu></menu>
|
|
||||||
</material-navbar>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<catalog if="{route.routeName == 'home'}"></catalog>
|
|
||||||
<taglist if="{route.routeName == 'taglist'}"></taglist>
|
|
||||||
<tag-history if="{route.routeName == 'taghistory'}"></tag-history>
|
|
||||||
<change></change>
|
|
||||||
<add></add>
|
|
||||||
<remove></remove>
|
|
||||||
<material-snackbar></material-snackbar>
|
|
||||||
</main>
|
|
||||||
<footer>
|
|
||||||
<material-footer>
|
|
||||||
<a class="material-footer-logo" href="https://joxit.github.io/docker-registry-ui/">Docker Registry UI
|
|
||||||
%%GULP_INJECT_VERSION%%</a>
|
|
||||||
<ul class="material-footer-link-list">
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/Joxit/docker-registry-ui">Contribute on GitHub</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/Joxit/docker-registry-ui/blob/main/LICENSE">Privacy & Terms</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</material-footer>
|
|
||||||
</footer>
|
|
||||||
<script type="text/javascript">
|
|
||||||
registryUI.appTag = this;
|
|
||||||
route.base('#!');
|
|
||||||
route('', function() {
|
|
||||||
route.routeName = 'home';
|
|
||||||
if (registryUI.catalog.display) {
|
|
||||||
registryUI.catalog.loadend = false;
|
|
||||||
}
|
|
||||||
registryUI.appTag.update();
|
|
||||||
});
|
|
||||||
route('/taglist/*', function(image) {
|
|
||||||
route.routeName = 'taglist';
|
|
||||||
registryUI.taglist.name = image;
|
|
||||||
if (registryUI.taglist.display) {
|
|
||||||
registryUI.taglist.loadend = false;
|
|
||||||
}
|
|
||||||
registryUI.appTag.update();
|
|
||||||
});
|
|
||||||
route('/taghistory/image/*/tag/*', function(image, tag) {
|
|
||||||
route.routeName = 'taghistory';
|
|
||||||
|
|
||||||
registryUI.taghistory.image = image;
|
|
||||||
registryUI.taghistory.tag = tag;
|
|
||||||
|
|
||||||
if (registryUI.taghistory.display) {
|
|
||||||
registryUI.taghistory.loadend = false;
|
|
||||||
}
|
|
||||||
registryUI.appTag.update();
|
|
||||||
});
|
|
||||||
registryUI.home = function() {
|
|
||||||
if (route.routeName == 'home') {
|
|
||||||
registryUI.catalog.display;
|
|
||||||
} else {
|
|
||||||
route('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.taghistory.go = function(image, tag) {
|
|
||||||
route('/taghistory/image/' + image + '/tag/' + tag);
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.snackbar = function(message, isError) {
|
|
||||||
registryUI.appTag.tags['material-snackbar'].addToast({'message': message, 'isError': isError}, 15000);
|
|
||||||
};
|
|
||||||
registryUI.errorSnackbar = function(message) {
|
|
||||||
return registryUI.snackbar(message, true);
|
|
||||||
};
|
|
||||||
registryUI.showErrorCanNotReadContentDigest = function() {
|
|
||||||
registryUI.errorSnackbar(
|
|
||||||
'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'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
registryUI.cleanName = function() {
|
|
||||||
const url = registryUI.pullUrl || (registryUI.url() && registryUI.url().length > 0 && registryUI.url()) || window.location.host;
|
|
||||||
return registryUI.stripHttps(url);
|
|
||||||
};
|
|
||||||
route.parser(null, function(path, filter) {
|
|
||||||
const f = filter
|
|
||||||
.replace(/\?/g, '\\?')
|
|
||||||
.replace(/\*/g, '([^?#]+?)')
|
|
||||||
.replace(/\.\./, '.*');
|
|
||||||
const re = new RegExp('^' + f + '$');
|
|
||||||
const args = path.match(re);
|
|
||||||
if (args) return args.slice(1)
|
|
||||||
});
|
|
||||||
|
|
||||||
registryUI.isDigit = function(char) {
|
|
||||||
return char >= '0' && char <= '9';
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.DockerImage = function(name, tag, list) {
|
|
||||||
this.name = name;
|
|
||||||
this.tag = tag;
|
|
||||||
this.list = list;
|
|
||||||
this.chars = 0;
|
|
||||||
riot.observable(this);
|
|
||||||
this.on('get-size', function() {
|
|
||||||
if (this.size !== undefined) {
|
|
||||||
return this.trigger('size', this.size);
|
|
||||||
}
|
|
||||||
return this.fillInfo();
|
|
||||||
});
|
|
||||||
this.on('get-sha256', function() {
|
|
||||||
if (this.size !== undefined) {
|
|
||||||
return this.trigger('sha256', this.sha256);
|
|
||||||
}
|
|
||||||
return this.fillInfo();
|
|
||||||
});
|
|
||||||
this.on('get-date', function() {
|
|
||||||
if (this.creationDate !== undefined) {
|
|
||||||
return this.trigger('creation-date', this.creationDate);
|
|
||||||
}
|
|
||||||
return this.fillInfo();
|
|
||||||
});
|
|
||||||
this.on('content-digest-chars', function (chars) {
|
|
||||||
this.chars = chars;
|
|
||||||
});
|
|
||||||
this.on('get-content-digest-chars', function() {
|
|
||||||
return this.trigger('content-digest-chars', this.chars);
|
|
||||||
});
|
|
||||||
this.on('get-content-digest', function() {
|
|
||||||
if (this.digest !== undefined) {
|
|
||||||
return this.trigger('content-digest', this.digest);
|
|
||||||
}
|
|
||||||
return this.fillInfo();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.DockerImage._tagReduce = function(acc, e) {
|
|
||||||
if (acc.length > 0 && registryUI.isDigit(acc[acc.length - 1].charAt(0)) == registryUI.isDigit(e)) {
|
|
||||||
acc[acc.length - 1] += e;
|
|
||||||
} else {
|
|
||||||
acc.push(e);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.DockerImage.compare = function(e1, e2) {
|
|
||||||
const tag1 = e1.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce, []);
|
|
||||||
const tag2 = e2.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce, []);
|
|
||||||
|
|
||||||
for (var i = 0; i < tag1.length && i < tag2.length; i++) {
|
|
||||||
const compare = tag1[i].localeCompare(tag2[i]);
|
|
||||||
if (registryUI.isDigit(tag1[i].charAt(0)) && registryUI.isDigit(tag2[i].charAt(0))) {
|
|
||||||
const diff = tag1[i] - tag2[i];
|
|
||||||
if (diff != 0) {
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
} else if (compare != 0) {
|
|
||||||
return compare;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return e1.tag.length - e2.tag.length;
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.DockerImage.prototype.fillInfo = function() {
|
|
||||||
if (this._fillInfoWaiting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._fillInfoWaiting = true;
|
|
||||||
const oReq = new Http();
|
|
||||||
const self = this;
|
|
||||||
oReq.addEventListener('loadend', function() {
|
|
||||||
if (this.status == 200 || this.status == 202) {
|
|
||||||
const response = JSON.parse(this.responseText);
|
|
||||||
if (response.mediaType === 'application/vnd.docker.distribution.manifest.list.v2+json') {
|
|
||||||
self.trigger('list', response);
|
|
||||||
const manifest = response.manifests[0];
|
|
||||||
const image = new registryUI.DockerImage(self.name, manifest.digest)
|
|
||||||
registryUI.eventTransfer(image, self)
|
|
||||||
image.fillInfo()
|
|
||||||
self.variants = [image];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.size = response.layers.reduce(function(acc, e) {
|
|
||||||
return acc + e.size;
|
|
||||||
}, 0);
|
|
||||||
self.sha256 = response.config.digest;
|
|
||||||
self.layers = response.layers;
|
|
||||||
self.trigger('size', self.size);
|
|
||||||
self.trigger('sha256', self.sha256);
|
|
||||||
oReq.getContentDigest(function (digest) {
|
|
||||||
self.digest = digest;
|
|
||||||
self.trigger('content-digest', digest);
|
|
||||||
if (!digest) {
|
|
||||||
registryUI.showErrorCanNotReadContentDigest();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.getBlobs(response.config.digest)
|
|
||||||
} else if (this.status == 404) {
|
|
||||||
registryUI.errorSnackbar('Manifest for ' + self.name + ':' + self.tag + ' not found');
|
|
||||||
} else {
|
|
||||||
registryUI.snackbar(this.responseText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/manifests/' + self.tag);
|
|
||||||
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json'
|
|
||||||
+ (self.list ? ', application/vnd.docker.distribution.manifest.list.v2+json' : ''));
|
|
||||||
oReq.send();
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.DockerImage.prototype.getBlobs = function(blob) {
|
|
||||||
const oReq = new Http();
|
|
||||||
const self = this;
|
|
||||||
oReq.addEventListener('loadend', function() {
|
|
||||||
if (this.status == 200 || this.status == 202) {
|
|
||||||
const response = JSON.parse(this.responseText);
|
|
||||||
self.creationDate = new Date(response.created);
|
|
||||||
self.blobs = response;
|
|
||||||
self.blobs.history.filter(function(e) {
|
|
||||||
return !e.empty_layer;
|
|
||||||
}).forEach(function(e, i) {
|
|
||||||
e.size = self.layers[i].size;
|
|
||||||
e.id = self.layers[i].digest.replace('sha256:', '');
|
|
||||||
});
|
|
||||||
self.blobs.id = blob.replace('sha256:', '');
|
|
||||||
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');
|
|
||||||
} else {
|
|
||||||
registryUI.snackbar(this.responseText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/blobs/' + blob);
|
|
||||||
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json');
|
|
||||||
oReq.send();
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.taglist.go = function(image) {
|
|
||||||
route('taglist/' + image);
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.getPageQueryParam = function() {
|
|
||||||
var qs = route.query();
|
|
||||||
try {
|
|
||||||
return qs.page !== undefined ? parseInt(qs.page.replace(/#.*/, '')) : 1;
|
|
||||||
} catch(e) { return 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
registryUI.getQueryParams = function(update) {
|
|
||||||
var qs = route.query();
|
|
||||||
update = update || {};
|
|
||||||
for (var key in qs) {
|
|
||||||
if (qs[key] !== undefined) {
|
|
||||||
qs[key] = qs[key].replace(/#!.*/, '');
|
|
||||||
} else {
|
|
||||||
delete qs[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var key in update) {
|
|
||||||
if (update[key] !== undefined) {
|
|
||||||
qs[key] = update[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return qs;
|
|
||||||
}
|
|
||||||
route.start(true);
|
|
||||||
</script>
|
|
||||||
</app>
|
|
|
@ -1,245 +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/>.
|
|
||||||
-->
|
|
||||||
<taglist>
|
|
||||||
<!-- Begin of tag -->
|
|
||||||
<material-card class="header">
|
|
||||||
<div class="material-card-title-action ">
|
|
||||||
<material-button waves-center="true" rounded="true" waves-color="#ddd" onclick="registryUI.home();">
|
|
||||||
<i class="material-icons">arrow_back</i>
|
|
||||||
</material-button>
|
|
||||||
<h2>
|
|
||||||
Tags of { registryUI.taglist.name }
|
|
||||||
<div class="source-hint">
|
|
||||||
Sourced from { registryUI.name() + '/' + registryUI.taglist.name }
|
|
||||||
</div>
|
|
||||||
<div class="item-count">{ registryUI.taglist.tags.length } tags</div>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</material-card>
|
|
||||||
<div hide="{ registryUI.taglist.loadend }" class="spinner-wrapper">
|
|
||||||
<material-spinner></material-spinner>
|
|
||||||
</div>
|
|
||||||
<pagination pages="{ registryUI.getPageLabels(this.page, registryUI.getNumPages(registryUI.taglist.tags)) }"></pagination>
|
|
||||||
<material-card ref="taglist-tag" class="taglist"
|
|
||||||
multi-delete={ this.multiDelete }
|
|
||||||
tags={ registryUI.getPage(registryUI.taglist.tags, this.page) }
|
|
||||||
show="{ registryUI.taglist.loadend }">
|
|
||||||
<table show="{ registryUI.taglist.loadend }" style="border: none;">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="creation-date">Creation date</th>
|
|
||||||
<th class="image-size">Size</th>
|
|
||||||
<th id="image-content-digest-header" if="{ registryUI.showContentDigest }">Content Digest</th>
|
|
||||||
|
|
||||||
<th
|
|
||||||
id="image-tag-header"
|
|
||||||
class="{ registryUI.taglist.asc ? 'material-card-th-sorted-ascending' : 'material-card-th-sorted-descending' }"
|
|
||||||
onclick="registryUI.taglist.reverse();">Tag
|
|
||||||
</th>
|
|
||||||
<th class="show-tag-history">History</th>
|
|
||||||
<th class={ 'remove-tag': true, delete: this.parent.toDelete > 0 } if="{ registryUI.isImageRemoveActivated }">
|
|
||||||
<material-checkbox ref="remove-tag-checkbox" class="indeterminate" show={ this.toDelete === 0} title="Toggle multi-delete. Alt+Click to select all tags."></material-checkbox>
|
|
||||||
<material-button waves-center="true" rounded="true" waves-color="#ddd" title="This will delete selected images." onclick={ registryUI.taglist.bulkDelete } show={ this.toDelete > 0 }>
|
|
||||||
<i class="material-icons">delete</i>
|
|
||||||
</material-button></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr each="{ image in this.opts.tags }">
|
|
||||||
<td class="creation-date">
|
|
||||||
<image-date image="{ image }"/>
|
|
||||||
</td>
|
|
||||||
<td class="image-size">
|
|
||||||
<image-size image="{ image }"/>
|
|
||||||
</td>
|
|
||||||
<td if="{ registryUI.showContentDigest }">
|
|
||||||
<image-content-digest image="{ image }"/>
|
|
||||||
<copy-to-clipboard target="digest" image={ image }/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<image-tag image="{ image }"/>
|
|
||||||
<copy-to-clipboard target="tag" image={ image }/>
|
|
||||||
</td>
|
|
||||||
<td class="show-tag-history">
|
|
||||||
<tag-history-button image={ image }/>
|
|
||||||
</td>
|
|
||||||
<td if="{ registryUI.isImageRemoveActivated }">
|
|
||||||
<remove-image multi-delete={ this.opts.multiDelete } image={ image }/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</material-card>
|
|
||||||
<pagination pages="{ registryUI.getPageLabels(this.page, registryUI.getNumPages(registryUI.taglist.tags)) }"></pagination>
|
|
||||||
<script type="text/javascript">
|
|
||||||
var self = registryUI.taglist.instance = this;
|
|
||||||
self.page = registryUI.getPageQueryParam();
|
|
||||||
registryUI.taglist.tags = [];
|
|
||||||
const onResize = function() {
|
|
||||||
// window.innerWidth is a blocking access, cache its result.
|
|
||||||
const innerWidth = window.innerWidth;
|
|
||||||
var chars = 0;
|
|
||||||
var max = registryUI.taglist.tags.reduce(function(acc, e) {
|
|
||||||
return e.tag.length > acc ? e.tag.length : acc;
|
|
||||||
}, 0);
|
|
||||||
if (innerWidth >= 1440) {
|
|
||||||
chars = 71;
|
|
||||||
} else if (innerWidth < 1024) {
|
|
||||||
chars = 0;
|
|
||||||
} else {
|
|
||||||
// SHA256:12345678 + scaled between 1024 and 1440px
|
|
||||||
chars = 15 + 56 * ((innerWidth - 1024) / 416);
|
|
||||||
}
|
|
||||||
if (max > 20) chars -= (max - 20);
|
|
||||||
registryUI.taglist.tags.map(function (image) {
|
|
||||||
image.trigger('content-digest-chars', chars);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
window.addEventListener('resize', onResize);
|
|
||||||
// this may be run before the final document size is available, so schedule
|
|
||||||
// a correction once everything is set up.
|
|
||||||
window.requestAnimationFrame(onResize);
|
|
||||||
|
|
||||||
this.multiDelete = false;
|
|
||||||
this.toDelete = 0;
|
|
||||||
|
|
||||||
this.on('delete', function() {
|
|
||||||
if (!registryUI.isImageRemoveActivated || !this.multiDelete) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('multi-delete', function() {
|
|
||||||
if (!registryUI.isImageRemoveActivated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.multiDelete = !this.multiDelete;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('toggle-remove-image', function(checked) {
|
|
||||||
if (checked) {
|
|
||||||
this.toDelete++;
|
|
||||||
} else {
|
|
||||||
this.toDelete--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.toDelete <= 1) {
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('page-update', function(page) {
|
|
||||||
self.page = page < 1 ? 1 : page;
|
|
||||||
registryUI.updateQueryString(registryUI.getQueryParams({ page: self.page }) );
|
|
||||||
this.toDelete = 0;
|
|
||||||
this.update();
|
|
||||||
});
|
|
||||||
|
|
||||||
this._getRemoveImageTags = function() {
|
|
||||||
var images = self.refs['taglist-tag'].tags['remove-image'];
|
|
||||||
if (!(images instanceof Array)) {
|
|
||||||
images = [images];
|
|
||||||
}
|
|
||||||
return images;
|
|
||||||
};
|
|
||||||
|
|
||||||
registryUI.taglist.bulkDelete = function(e) {
|
|
||||||
if (self.multiDelete && self.toDelete > 0) {
|
|
||||||
if (e.altKey) {
|
|
||||||
self._getRemoveImageTags()
|
|
||||||
.filter(function(img) { return img.tags['material-checkbox'].checked; })
|
|
||||||
.forEach(function(img) { img.tags['material-checkbox'].toggle() });
|
|
||||||
}
|
|
||||||
self._getRemoveImageTags().filter(function(img) {
|
|
||||||
return img.tags['material-checkbox'].checked;
|
|
||||||
}).forEach(function(img) {
|
|
||||||
img.delete(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.on('update', function() {
|
|
||||||
var checkbox = this.refs['taglist-tag'].refs['remove-tag-checkbox'];
|
|
||||||
if (!checkbox || checkbox._toggle) { return; }
|
|
||||||
|
|
||||||
checkbox._toggle = checkbox.toggle;
|
|
||||||
checkbox.toggle = function(e) {
|
|
||||||
if (e.altKey) {
|
|
||||||
if (!this.checked) { this._toggle(); }
|
|
||||||
self._getRemoveImageTags()
|
|
||||||
.filter(function(img) { return !img.tags['material-checkbox'].checked; })
|
|
||||||
.forEach(function(img) { img.tags['material-checkbox'].toggle() });
|
|
||||||
} else {
|
|
||||||
this._toggle();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
checkbox.on('toggle', function() {
|
|
||||||
registryUI.taglist.instance.multiDelete = this.checked;
|
|
||||||
registryUI.taglist.instance.update();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
registryUI.taglist.display = function() {
|
|
||||||
registryUI.taglist.tags = [];
|
|
||||||
if (route.routeName == 'taglist') {
|
|
||||||
const oReq = new Http();
|
|
||||||
registryUI.taglist.instance.update();
|
|
||||||
oReq.addEventListener('load', function() {
|
|
||||||
registryUI.taglist.tags = [];
|
|
||||||
if (this.status == 200) {
|
|
||||||
const tags = JSON.parse(this.responseText).tags || [];
|
|
||||||
registryUI.taglist.tags = tags.map(function(tag) {
|
|
||||||
return new registryUI.DockerImage(registryUI.taglist.name, tag);
|
|
||||||
}).sort(registryUI.DockerImage.compare);
|
|
||||||
window.requestAnimationFrame(onResize);
|
|
||||||
self.trigger('page-update', Math.min(self.page, registryUI.getNumPages(registryUI.taglist.tags)))
|
|
||||||
} else if (this.status == 404) {
|
|
||||||
registryUI.snackbar('Server not found', true);
|
|
||||||
} else {
|
|
||||||
registryUI.snackbar(this.responseText, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
oReq.addEventListener('error', function() {
|
|
||||||
registryUI.snackbar(this.getErrorMessage(), true);
|
|
||||||
registryUI.taglist.tags = [];
|
|
||||||
});
|
|
||||||
oReq.addEventListener('loadend', function() {
|
|
||||||
registryUI.taglist.loadend = true;
|
|
||||||
registryUI.taglist.instance.update();
|
|
||||||
});
|
|
||||||
oReq.open('GET', registryUI.url() + '/v2/' + registryUI.taglist.name + '/tags/list');
|
|
||||||
oReq.send();
|
|
||||||
registryUI.taglist.asc = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
registryUI.taglist.display();
|
|
||||||
registryUI.taglist.instance.update();
|
|
||||||
|
|
||||||
registryUI.taglist.reverse = function() {
|
|
||||||
if (registryUI.taglist.asc) {
|
|
||||||
registryUI.taglist.tags.reverse();
|
|
||||||
registryUI.taglist.asc = false;
|
|
||||||
} else {
|
|
||||||
registryUI.taglist.tags.sort(registryUI.DockerImage.compare);
|
|
||||||
registryUI.taglist.asc = true;
|
|
||||||
}
|
|
||||||
registryUI.taglist.instance.update();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<!-- End of tag -->
|
|
||||||
</taglist>
|
|
Loading…
Add table
Add a link
Reference in a new issue