feat(riot-v5): upgrade catalog component + Http and fix global style

This commit is contained in:
Joxit 2021-03-03 00:05:44 +01:00
parent 11692c136e
commit fb80283dd9
No known key found for this signature in database
GPG Key ID: F526592B8E012263
11 changed files with 269 additions and 156 deletions

View File

@ -32,6 +32,7 @@
"rollup": "^2.34.2",
"rollup-plugin-app-utils": "^1.0.6",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-riot": "^5.0.0",
"rollup-plugin-scss": "^2.6.1",
"rollup-plugin-serve": "^1.1.0",

View File

@ -9,6 +9,7 @@ import serve from 'rollup-plugin-serve';
import html from '@rollup/plugin-html';
import htmlUseref from './rollup/html-useref';
import json from '@rollup/plugin-json';
import copy from 'rollup-plugin-copy'
const useServe = process.env.ROLLUP_SERVE === 'true';
const output = useServe ? '.serve' : 'dist';
@ -21,6 +22,12 @@ const plugins = [
scss({ output: `./${output}/docker-registry-ui.css`, outputStyle: 'compressed' }),
babel({ babelHelpers: 'bundled', presets: ['@babel/env'] }),
html({ template: () => htmlUseref('./src/index.html') }),
copy({
targets: [
{ src: 'src/fonts', dest: `${output}` },
{ src: 'src/images', dest: `${output}` },
]
})
];
if (useServe) {

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

View File

@ -21,6 +21,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</material-navbar>
</header>
<main>
<catalog registry-url="{ state.registryUrl }" name="{ state.name }"
catalog-elements-limit="{ state.catalogElementsLimit }"></catalog>
</main>
<footer>
<material-footer>
@ -41,13 +43,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
} from '../../package.json';
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 Catalog from './catalog/catalog.riot';
import {
stripHttps
} from '../scripts/utils';
export default {
components: {
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
}
</script>

View File

@ -34,7 +34,55 @@
</head>
<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 -->
<script src="../node_modules/riot/riot+compiler.min.js"></script>
<script src="../node_modules/riot-route/dist/route.js"></script>
@ -63,4 +111,4 @@
<!-- endbuild -->
</body>
</html>
</html>

View File

@ -2,12 +2,7 @@ import { component } from 'riot';
import DockerRegistryUI from './components/docker-registry-ui.riot';
import './style.css';
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';
import './style.scss';
const createApp = component(DockerRegistryUI);
const tags = document.getElementsByTagName('docker-registry-ui');

View File

@ -14,45 +14,44 @@
* 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/>.
*/
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) {
if (this.oReq.hasHeader('Docker-Content-Digest')) {
// Same origin or advanced CORS headers set:
// 'Access-Control-Expose-Headers: Docker-Content-Digest'
cb(this.oReq.getResponseHeader('Docker-Content-Digest'))
} else if (window.crypto && window.TextEncoder) {
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
export class Http {
constructor() {
this.oReq = new XMLHttpRequest();
this.oReq.hasHeader = hasHeader;
this.oReq.getErrorMessage = getErrorMessage;
this._events = {};
this._headers = {};
}
};
Http.prototype.addEventListener = function(e, f) {
this._events[e] = f;
const self = this;
switch (e) {
case 'loadend':
{
self.oReq.addEventListener('loadend', function() {
getContentDigest(cb) {
if (this.oReq.hasHeader('Docker-Content-Digest')) {
// Same origin or advanced CORS headers set:
// 'Access-Control-Expose-Headers: Docker-Content-Digest'
cb(this.oReq.getResponseHeader('Docker-Content-Digest'));
} else if (window.crypto && window.TextEncoder) {
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) {
const req = new XMLHttpRequest();
req.open(self._method, self._url);
@ -73,53 +72,68 @@ Http.prototype.addEventListener = function(e, f) {
});
break;
}
case 'load':
{
self.oReq.addEventListener('load', function() {
case 'load': {
self.oReq.addEventListener('load', function () {
if (this.status !== 401) {
f.bind(this)();
}
});
break;
}
default:
{
self.oReq.addEventListener(e, function() {
default: {
self.oReq.addEventListener(e, function () {
f.bind(this)();
});
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) {
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() {
const getErrorMessage = function () {
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()) {
return 'Incorrect server endpoint.';
} 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 +
'`'
);
};

View File

@ -14,7 +14,14 @@
* 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/>.
*/
@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 {
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif !important;
}

View File

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