feat(riot-v5): handle page query param & refactor router & remove old code

This commit is contained in:
Joxit 2021-03-29 21:49:37 +02:00
parent 603b5861fa
commit 8ef411059c
No known key found for this signature in database
GPG key ID: F526592B8E012263
10 changed files with 74 additions and 733 deletions

View file

@ -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>

View file

@ -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
}

View file

@ -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;

View file

@ -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() {

View file

@ -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'];
},
};

View file

@ -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('*');
});

View file

@ -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('*');
});

View file

@ -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);
}

View file

@ -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 &amp; 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>

View file

@ -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>