mirror of
https://github.com/Joxit/docker-registry-ui.git
synced 2025-04-30 17:09:55 +03:00
feat(riot-v5): upgrade catalog component + Http and fix global style
This commit is contained in:
parent
11692c136e
commit
fb80283dd9
11 changed files with 269 additions and 156 deletions
|
@ -32,6 +32,7 @@
|
||||||
"rollup": "^2.34.2",
|
"rollup": "^2.34.2",
|
||||||
"rollup-plugin-app-utils": "^1.0.6",
|
"rollup-plugin-app-utils": "^1.0.6",
|
||||||
"rollup-plugin-commonjs": "^10.1.0",
|
"rollup-plugin-commonjs": "^10.1.0",
|
||||||
|
"rollup-plugin-copy": "^3.4.0",
|
||||||
"rollup-plugin-riot": "^5.0.0",
|
"rollup-plugin-riot": "^5.0.0",
|
||||||
"rollup-plugin-scss": "^2.6.1",
|
"rollup-plugin-scss": "^2.6.1",
|
||||||
"rollup-plugin-serve": "^1.1.0",
|
"rollup-plugin-serve": "^1.1.0",
|
||||||
|
|
|
@ -9,6 +9,7 @@ import serve from 'rollup-plugin-serve';
|
||||||
import html from '@rollup/plugin-html';
|
import html from '@rollup/plugin-html';
|
||||||
import htmlUseref from './rollup/html-useref';
|
import htmlUseref from './rollup/html-useref';
|
||||||
import json from '@rollup/plugin-json';
|
import json from '@rollup/plugin-json';
|
||||||
|
import copy from 'rollup-plugin-copy'
|
||||||
|
|
||||||
const useServe = process.env.ROLLUP_SERVE === 'true';
|
const useServe = process.env.ROLLUP_SERVE === 'true';
|
||||||
const output = useServe ? '.serve' : 'dist';
|
const output = useServe ? '.serve' : 'dist';
|
||||||
|
@ -21,6 +22,12 @@ const plugins = [
|
||||||
scss({ output: `./${output}/docker-registry-ui.css`, outputStyle: 'compressed' }),
|
scss({ output: `./${output}/docker-registry-ui.css`, outputStyle: 'compressed' }),
|
||||||
babel({ babelHelpers: 'bundled', presets: ['@babel/env'] }),
|
babel({ babelHelpers: 'bundled', presets: ['@babel/env'] }),
|
||||||
html({ template: () => htmlUseref('./src/index.html') }),
|
html({ template: () => htmlUseref('./src/index.html') }),
|
||||||
|
copy({
|
||||||
|
targets: [
|
||||||
|
{ src: 'src/fonts', dest: `${output}` },
|
||||||
|
{ src: 'src/images', dest: `${output}` },
|
||||||
|
]
|
||||||
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
if (useServe) {
|
if (useServe) {
|
||||||
|
|
107
src/components/catalog/catalog.riot
Normal file
107
src/components/catalog/catalog.riot
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
<!--
|
||||||
|
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/>.
|
||||||
|
-->
|
||||||
|
<catalog>
|
||||||
|
<material-card ref="catalog-tag" class="catalog header">
|
||||||
|
<div class="material-card-title-action">
|
||||||
|
<h2>
|
||||||
|
Repositories of { state.name }
|
||||||
|
<div class="item-count">{ state.length } images</div>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</material-card>
|
||||||
|
<div if="{ !state.loadend }" class="spinner-wrapper">
|
||||||
|
<material-spinner></material-spinner>
|
||||||
|
</div>
|
||||||
|
<catalog-element each="{ item in state.repositories }" item="{ item }" />
|
||||||
|
<script>
|
||||||
|
import MaterialCard from 'riot-mui/src/material-elements/material-card/material-card.riot';
|
||||||
|
import MaterialSpinner from 'riot-mui/src/material-elements/material-spinner/material-spinner.riot';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Http
|
||||||
|
} from '../../scripts/http';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
MaterialCard,
|
||||||
|
MaterialSpinner
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
name: '',
|
||||||
|
length: 0,
|
||||||
|
loadend: false,
|
||||||
|
repositories: []
|
||||||
|
},
|
||||||
|
|
||||||
|
onBeforeMount(props) {
|
||||||
|
this.state.name = props.name;
|
||||||
|
this.state.catalogElementsLimit = props.catalogElementsLimit;
|
||||||
|
},
|
||||||
|
onMounted(props) {
|
||||||
|
this.display(props, this.state)
|
||||||
|
},
|
||||||
|
onUpdated(props, state) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
display(props, state) {
|
||||||
|
this.state.repositories = [];
|
||||||
|
const self = this;
|
||||||
|
const oReq = new Http();
|
||||||
|
oReq.addEventListener('load', function () {
|
||||||
|
state.repositories = [];
|
||||||
|
if (this.status == 200) {
|
||||||
|
state.repositories = JSON.parse(this.responseText).repositories || [];
|
||||||
|
state.repositories.sort();
|
||||||
|
state.length = state.repositories.length;
|
||||||
|
state.repositories = state.repositories.reduce(function (acc, e) {
|
||||||
|
const slash = e.indexOf('/');
|
||||||
|
if (slash > 0) {
|
||||||
|
const repoName = e.substring(0, slash) + '/';
|
||||||
|
if (acc.length == 0 || acc[acc.length - 1].repo != repoName) {
|
||||||
|
acc.push({
|
||||||
|
repo: repoName,
|
||||||
|
images: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
acc[acc.length - 1].images.push(e);
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
acc.push(e);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
} else if (this.status == 404) {
|
||||||
|
// registryUI.snackbar('Server not found', true);
|
||||||
|
} else {
|
||||||
|
// registryUI.snackbar(this.responseText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
oReq.addEventListener('error', function () {
|
||||||
|
// registryUI.snackbar(this.getErrorMessage(), true);
|
||||||
|
state.repositories = [];
|
||||||
|
});
|
||||||
|
oReq.addEventListener('loadend', function () {
|
||||||
|
self.update({
|
||||||
|
loadend: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
oReq.open('GET', props.registryUrl + '/v2/_catalog?n=' + state.catalogElementsLimit);
|
||||||
|
oReq.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</catalog>
|
|
@ -21,6 +21,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
</material-navbar>
|
</material-navbar>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
|
<catalog registry-url="{ state.registryUrl }" name="{ state.name }"
|
||||||
|
catalog-elements-limit="{ state.catalogElementsLimit }"></catalog>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
<material-footer>
|
<material-footer>
|
||||||
|
@ -41,13 +43,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
} from '../../package.json';
|
} from '../../package.json';
|
||||||
import MaterialNavbar from 'riot-mui/src/material-elements/material-navbar/material-navbar.riot';
|
import MaterialNavbar from 'riot-mui/src/material-elements/material-navbar/material-navbar.riot';
|
||||||
import MaterialFooter from 'riot-mui/src/material-elements/material-footer/material-footer.riot';
|
import MaterialFooter from 'riot-mui/src/material-elements/material-footer/material-footer.riot';
|
||||||
|
import Catalog from './catalog/catalog.riot';
|
||||||
|
import {
|
||||||
|
stripHttps
|
||||||
|
} from '../scripts/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
MaterialNavbar,
|
MaterialNavbar,
|
||||||
MaterialFooter
|
MaterialFooter,
|
||||||
|
Catalog
|
||||||
|
},
|
||||||
|
onBeforeMount(props) {
|
||||||
|
this.state.registryUrl = props.registryUrl || (window.location.origin + window.location.pathname.replace(/\/+$/,
|
||||||
|
''));
|
||||||
|
this.state.name = props.name || stripHttps(props.registryUrl);
|
||||||
|
this.state.catalogElementsLimit = props.catalogElementsLimit || 100000;
|
||||||
},
|
},
|
||||||
onMounted() {},
|
|
||||||
version
|
version
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -34,7 +34,55 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<docker-registry-ui></docker-registry-ui>
|
<script>
|
||||||
|
const URL_QUERY_PARAM_REGEX = /[&?]url=/;
|
||||||
|
const URL_PARAM_REGEX = /^url=/;
|
||||||
|
|
||||||
|
function getUrlQueryParam() {
|
||||||
|
const search = window.location.search;
|
||||||
|
if (URL_QUERY_PARAM_REGEX.test(search)) {
|
||||||
|
const param = search.split(/^\?|&/).find(function (param) {
|
||||||
|
return param && URL_PARAM_REGEX.test(param);
|
||||||
|
});
|
||||||
|
return param ? param.replace(URL_PARAM_REGEX, '') : param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRegistryServer(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)) ? '' : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function rDecodeURI(url) {
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return url.startsWith('http') ? window.decodeURIComponent(url) : atob(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRegistryURL() {
|
||||||
|
let url = getUrlQueryParam();
|
||||||
|
if (url) {
|
||||||
|
try {
|
||||||
|
return rDecodeURI(url);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getRegistryServer(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag = document.createElement('docker-registry-ui');
|
||||||
|
tag.setAttribute('registry-url', getRegistryURL());
|
||||||
|
document.getElementsByTagName('body').item(0).appendChild(tag);
|
||||||
|
</script>
|
||||||
<!-- build:js docker-registry-ui.js -->
|
<!-- build:js docker-registry-ui.js -->
|
||||||
<script src="../node_modules/riot/riot+compiler.min.js"></script>
|
<script src="../node_modules/riot/riot+compiler.min.js"></script>
|
||||||
<script src="../node_modules/riot-route/dist/route.js"></script>
|
<script src="../node_modules/riot-route/dist/route.js"></script>
|
||||||
|
|
|
@ -2,12 +2,7 @@ import { component } from 'riot';
|
||||||
|
|
||||||
import DockerRegistryUI from './components/docker-registry-ui.riot';
|
import DockerRegistryUI from './components/docker-registry-ui.riot';
|
||||||
|
|
||||||
import './style.css';
|
import './style.scss';
|
||||||
import './roboto.css';
|
|
||||||
import './material-icons.css';
|
|
||||||
|
|
||||||
import 'riot-mui/src/material-elements/material-navbar/material-navbar.scss';
|
|
||||||
import 'riot-mui/src/material-elements/material-footer/material-footer.scss';
|
|
||||||
|
|
||||||
const createApp = component(DockerRegistryUI);
|
const createApp = component(DockerRegistryUI);
|
||||||
const tags = document.getElementsByTagName('docker-registry-ui');
|
const tags = document.getElementsByTagName('docker-registry-ui');
|
||||||
|
|
|
@ -14,45 +14,44 @@
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
function Http() {
|
|
||||||
this.oReq = new XMLHttpRequest();
|
|
||||||
this.oReq.hasHeader = Http.hasHeader;
|
|
||||||
this.oReq.getErrorMessage = Http.getErrorMessage;
|
|
||||||
this._events = {};
|
|
||||||
this._headers = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Http.prototype.getContentDigest = function(cb) {
|
export class Http {
|
||||||
if (this.oReq.hasHeader('Docker-Content-Digest')) {
|
constructor() {
|
||||||
// Same origin or advanced CORS headers set:
|
this.oReq = new XMLHttpRequest();
|
||||||
// 'Access-Control-Expose-Headers: Docker-Content-Digest'
|
this.oReq.hasHeader = hasHeader;
|
||||||
cb(this.oReq.getResponseHeader('Docker-Content-Digest'))
|
this.oReq.getErrorMessage = getErrorMessage;
|
||||||
} else if (window.crypto && window.TextEncoder) {
|
this._events = {};
|
||||||
crypto.subtle.digest(
|
this._headers = {};
|
||||||
'SHA-256',
|
|
||||||
new TextEncoder().encode(this.oReq.responseText)
|
|
||||||
).then(function (buffer) {
|
|
||||||
cb(
|
|
||||||
'sha256:' + Array.from(
|
|
||||||
new Uint8Array(buffer)
|
|
||||||
).map(function(byte) {
|
|
||||||
return byte.toString(16).padStart(2, '0');
|
|
||||||
}).join('')
|
|
||||||
);
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// IE and old Edge
|
|
||||||
// simply do not call the callback and skip the setup downstream
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Http.prototype.addEventListener = function(e, f) {
|
getContentDigest(cb) {
|
||||||
this._events[e] = f;
|
if (this.oReq.hasHeader('Docker-Content-Digest')) {
|
||||||
const self = this;
|
// Same origin or advanced CORS headers set:
|
||||||
switch (e) {
|
// 'Access-Control-Expose-Headers: Docker-Content-Digest'
|
||||||
case 'loadend':
|
cb(this.oReq.getResponseHeader('Docker-Content-Digest'));
|
||||||
{
|
} else if (window.crypto && window.TextEncoder) {
|
||||||
self.oReq.addEventListener('loadend', function() {
|
crypto.subtle.digest('SHA-256', new TextEncoder().encode(this.oReq.responseText)).then(function (buffer) {
|
||||||
|
cb(
|
||||||
|
'sha256:' +
|
||||||
|
Array.from(new Uint8Array(buffer))
|
||||||
|
.map(function (byte) {
|
||||||
|
return byte.toString(16).padStart(2, '0');
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// IE and old Edge
|
||||||
|
// simply do not call the callback and skip the setup downstream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener(e, f) {
|
||||||
|
this._events[e] = f;
|
||||||
|
const self = this;
|
||||||
|
switch (e) {
|
||||||
|
case 'loadend': {
|
||||||
|
self.oReq.addEventListener('loadend', function () {
|
||||||
if (this.status == 401) {
|
if (this.status == 401) {
|
||||||
const req = new XMLHttpRequest();
|
const req = new XMLHttpRequest();
|
||||||
req.open(self._method, self._url);
|
req.open(self._method, self._url);
|
||||||
|
@ -73,53 +72,68 @@ Http.prototype.addEventListener = function(e, f) {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'load':
|
case 'load': {
|
||||||
{
|
self.oReq.addEventListener('load', function () {
|
||||||
self.oReq.addEventListener('load', function() {
|
|
||||||
if (this.status !== 401) {
|
if (this.status !== 401) {
|
||||||
f.bind(this)();
|
f.bind(this)();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default: {
|
||||||
{
|
self.oReq.addEventListener(e, function () {
|
||||||
self.oReq.addEventListener(e, function() {
|
|
||||||
f.bind(this)();
|
f.bind(this)();
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setRequestHeader(header, value) {
|
||||||
|
this.oReq.setRequestHeader(header, value);
|
||||||
|
this._headers[header] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
open(m, u) {
|
||||||
|
this._method = m;
|
||||||
|
this._url = u;
|
||||||
|
this.oReq.open(m, u);
|
||||||
|
}
|
||||||
|
|
||||||
|
send() {
|
||||||
|
this.oReq.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasHeader = function (header) {
|
||||||
|
return this.getAllResponseHeaders()
|
||||||
|
.split('\n')
|
||||||
|
.some(function (h) {
|
||||||
|
return new RegExp('^' + header + ':', 'i').test(h);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Http.prototype.setRequestHeader = function(header, value) {
|
const getErrorMessage = function () {
|
||||||
this.oReq.setRequestHeader(header, value);
|
|
||||||
this._headers[header] = value;
|
|
||||||
};
|
|
||||||
|
|
||||||
Http.prototype.open = function(m, u) {
|
|
||||||
this._method = m;
|
|
||||||
this._url = u;
|
|
||||||
this.oReq.open(m, u);
|
|
||||||
};
|
|
||||||
|
|
||||||
Http.prototype.send = function() {
|
|
||||||
this.oReq.send();
|
|
||||||
};
|
|
||||||
|
|
||||||
Http.hasHeader = function(header) {
|
|
||||||
return this.getAllResponseHeaders().split('\n').some(function(h) {
|
|
||||||
return new RegExp('^' + header + ':', 'i').test(h);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Http.getErrorMessage = function() {
|
|
||||||
if (registryUI.url() && registryUI.url().match('^http://') && window.location.protocol === 'https:') {
|
if (registryUI.url() && registryUI.url().match('^http://') && window.location.protocol === 'https:') {
|
||||||
return 'Mixed Content: The page at `' + window.location.origin + '` was loaded over HTTPS, but requested an insecure server endpoint `' + registryUI.url() + '`. This request has been blocked; the content must be served over HTTPS.';
|
return (
|
||||||
|
'Mixed Content: The page at `' +
|
||||||
|
window.location.origin +
|
||||||
|
'` was loaded over HTTPS, but requested an insecure server endpoint `' +
|
||||||
|
registryUI.url() +
|
||||||
|
'`. This request has been blocked; the content must be served over HTTPS.'
|
||||||
|
);
|
||||||
} else if (!registryUI.url()) {
|
} else if (!registryUI.url()) {
|
||||||
return 'Incorrect server endpoint.';
|
return 'Incorrect server endpoint.';
|
||||||
} else if (this.withCredentials && !this.hasHeader('Access-Control-Allow-Credentials')) {
|
} else if (this.withCredentials && !this.hasHeader('Access-Control-Allow-Credentials')) {
|
||||||
return 'The `Access-Control-Allow-Credentials` header in the response is missing and must be set to `true` when the request\'s credentials mode is on. Origin `'+ registryUI.url() +'` is therefore not allowed access.';
|
return (
|
||||||
|
"The `Access-Control-Allow-Credentials` header in the response is missing and must be set to `true` when the request's credentials mode is on. Origin `" +
|
||||||
|
registryUI.url() +
|
||||||
|
'` is therefore not allowed access.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return 'An error occured: Check your connection and your registry must have `Access-Control-Allow-Origin` header set to `' + window.location.origin + '`';
|
return (
|
||||||
|
'An error occured: Check your connection and your registry must have `Access-Control-Allow-Origin` header set to `' +
|
||||||
|
window.location.origin +
|
||||||
|
'`'
|
||||||
|
);
|
||||||
};
|
};
|
|
@ -14,6 +14,13 @@
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
@import 'riot-mui/src/material-elements/material-navbar/material-navbar.scss';
|
||||||
|
@import 'riot-mui/src/material-elements/material-footer/material-footer.scss';
|
||||||
|
@import 'riot-mui/src/material-elements/material-card/material-card.scss';
|
||||||
|
@import 'riot-mui/src/material-elements/material-spinner/material-spinner.scss';
|
||||||
|
|
||||||
|
@import './roboto.scss';
|
||||||
|
@import './material-icons.scss';
|
||||||
|
|
||||||
html > body {
|
html > body {
|
||||||
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif !important;
|
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif !important;
|
|
@ -1,78 +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/>.
|
|
||||||
-->
|
|
||||||
<catalog>
|
|
||||||
<!-- Begin of tag -->
|
|
||||||
<material-card ref="catalog-tag" class="catalog header">
|
|
||||||
<div class="material-card-title-action">
|
|
||||||
<h2>
|
|
||||||
Repositories of { registryUI.name() }
|
|
||||||
<div class="item-count">{ registryUI.catalog.length } images</div>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</material-card>
|
|
||||||
<div hide="{ registryUI.catalog.loadend }" class="spinner-wrapper">
|
|
||||||
<material-spinner></material-spinner>
|
|
||||||
</div>
|
|
||||||
<catalog-element each="{ item in registryUI.catalog.repositories }" />
|
|
||||||
<script type="text/javascript">
|
|
||||||
registryUI.catalog.instance = this;
|
|
||||||
registryUI.catalog.display = function() {
|
|
||||||
registryUI.catalog.repositories = [];
|
|
||||||
const oReq = new Http();
|
|
||||||
oReq.addEventListener('load', function() {
|
|
||||||
registryUI.catalog.repositories = [];
|
|
||||||
if (this.status == 200) {
|
|
||||||
if (!registryUI.url()) {
|
|
||||||
registryUI._url = window.location.origin + window.location.pathname.replace(/\/+$/, '')
|
|
||||||
}
|
|
||||||
registryUI.catalog.repositories = JSON.parse(this.responseText).repositories || [];
|
|
||||||
registryUI.catalog.repositories.sort();
|
|
||||||
registryUI.catalog.length = registryUI.catalog.repositories.length;
|
|
||||||
registryUI.catalog.repositories = registryUI.catalog.repositories.reduce(function(acc, e) {
|
|
||||||
const slash = e.indexOf('/');
|
|
||||||
if (slash > 0) {
|
|
||||||
const repoName = e.substring(0, slash) + '/';
|
|
||||||
if (acc.length == 0 || acc[acc.length - 1].repo != repoName) {
|
|
||||||
acc.push({repo: repoName, images: []});
|
|
||||||
}
|
|
||||||
acc[acc.length - 1].images.push(e);
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
acc.push(e);
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
} else if (this.status == 404) {
|
|
||||||
registryUI.snackbar('Server not found', true);
|
|
||||||
} else {
|
|
||||||
registryUI.snackbar(this.responseText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
oReq.addEventListener('error', function() {
|
|
||||||
registryUI.snackbar(this.getErrorMessage(), true);
|
|
||||||
registryUI.catalog.repositories = [];
|
|
||||||
});
|
|
||||||
oReq.addEventListener('loadend', function() {
|
|
||||||
registryUI.catalog.loadend = true;
|
|
||||||
registryUI.catalog.instance.update();
|
|
||||||
});
|
|
||||||
oReq.open('GET', registryUI.url() + '/v2/_catalog?n=' + registryUI.catalogElementsLimit);
|
|
||||||
oReq.send();
|
|
||||||
};
|
|
||||||
registryUI.catalog.display();
|
|
||||||
</script>
|
|
||||||
<!-- End of tag -->
|
|
||||||
</catalog>
|
|
Loading…
Add table
Add a link
Reference in a new issue