mirror of
https://github.com/Joxit/docker-registry-ui.git
synced 2025-04-27 23:50:01 +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>
|
||||
<script>
|
||||
import {
|
||||
getRegistryServers,
|
||||
updateHistory
|
||||
getRegistryServers
|
||||
} from '../../scripts/utils';
|
||||
import router from '../../scripts/router';
|
||||
|
||||
|
@ -58,7 +57,7 @@
|
|||
router.home()
|
||||
this.props.onServerChange(url);
|
||||
this.props.onClose()
|
||||
setTimeout(() => updateHistory(url), 100);
|
||||
setTimeout(() => router.updateUrlQueryParam(url), 100);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -33,8 +33,7 @@
|
|||
</material-popup>
|
||||
<script>
|
||||
import {
|
||||
getRegistryServers,
|
||||
updateHistory
|
||||
getRegistryServers
|
||||
} from '../../scripts/utils';
|
||||
import router from '../../scripts/router';
|
||||
export default {
|
||||
|
@ -52,7 +51,7 @@
|
|||
router.home()
|
||||
this.props.onServerChange(url);
|
||||
this.props.onClose()
|
||||
setTimeout(() => updateHistory(url), 100);
|
||||
setTimeout(() => router.updateUrlQueryParam(url), 100);
|
||||
},
|
||||
getRegistryServers
|
||||
}
|
||||
|
|
|
@ -38,10 +38,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
</material-popup>
|
||||
<script>
|
||||
import {
|
||||
getRegistryServers,
|
||||
updateHistory
|
||||
getRegistryServers
|
||||
} from '../../scripts/utils';
|
||||
import router from '../../scripts/router';
|
||||
export default {
|
||||
remove(event) {
|
||||
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 }"
|
||||
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>
|
||||
|
||||
|
@ -68,7 +69,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
tags: [],
|
||||
loadend: false,
|
||||
asc: true,
|
||||
page: 1
|
||||
page: router.getPageQueryParam() || 1
|
||||
}
|
||||
},
|
||||
onMounted(props, state) {
|
||||
|
@ -117,6 +118,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
this.update({
|
||||
page: page
|
||||
});
|
||||
router.updatePageQueryParam(page);
|
||||
},
|
||||
|
||||
onResize() {
|
||||
|
|
|
@ -15,23 +15,60 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { router, getCurrentRoute } from '@riotjs/route';
|
||||
import { encodeURI } from './utils';
|
||||
|
||||
function baseUrl() {
|
||||
return getCurrentRoute().replace(/#!(.*)/, '');
|
||||
function getQueryParams() {
|
||||
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 {
|
||||
home() {
|
||||
router.push(baseUrl());
|
||||
router.push(baseUrl({ page: null }));
|
||||
},
|
||||
taglist(image) {
|
||||
router.push(`${baseUrl()}#!/taglist/${image}`);
|
||||
router.push(`${baseUrl({ page: null })}#!/taglist/${image}`);
|
||||
},
|
||||
getTagListImage() {
|
||||
return getCurrentRoute().replace(/^.*(#!)?\/?taglist\//, '');
|
||||
},
|
||||
history(image, tag) {
|
||||
router.push(`${baseUrl()}#!/taghistory/image/${image}/tag/${tag}`);
|
||||
router.push(`${baseUrl({ page: null })}#!/taghistory/image/${image}/tag/${tag}`);
|
||||
},
|
||||
getTagHistoryImage() {
|
||||
return getCurrentRoute().replace(/^.*(#!)?\/?taghistory\/image\/(.*)\/tag\/(.*)\/?$/, '$2');
|
||||
|
@ -39,4 +76,18 @@ export default {
|
|||
getTagHistoryTag() {
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!url) {
|
||||
return '';
|
||||
|
@ -171,10 +161,15 @@ export function getRegistryServers(i) {
|
|||
}
|
||||
|
||||
export function encodeURI(url) {
|
||||
if (!url) { return; }
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
return url.indexOf('&') < 0 ? window.encodeURIComponent(url) : btoa(url);
|
||||
};
|
||||
|
||||
export function updateHistory(url) {
|
||||
updateQueryString({ url: encodeURI(url) })
|
||||
}
|
||||
|
||||
export function decodeURI(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