mirror of
https://github.com/Joxit/docker-registry-ui.git
synced 2026-02-19 21:29:51 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
115105a2a0 |
37
.gitignore
vendored
37
.gitignore
vendored
@@ -1,4 +1,37 @@
|
||||
.project
|
||||
node_modules
|
||||
# dependencies
|
||||
/node_modules
|
||||
package-lock.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
*.iml
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.vscode/
|
||||
|
||||
registry-data
|
||||
|
||||
@@ -27,7 +27,6 @@ This web user interface uses [Riot](https://github.com/Riot/riot) the react-like
|
||||
- Alpine and Debian based images with supports for arm32v7 and arm64v8
|
||||
- Copy `docker pull` command to clipbloard
|
||||
- Show sha256 for specific tag (hover image tag)
|
||||
- Display image creation date (see #49)
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
2
dist/index.html
vendored
2
dist/index.html
vendored
@@ -13,4 +13,4 @@
|
||||
|
||||
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/>.
|
||||
--><!DOCTYPE html><html><head><meta charset="UTF-8"><link rel="stylesheet" href="vendor.css"><link rel="stylesheet" href="style.css"><link href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&lang=en" rel="stylesheet" type="text/css"><title>Docker Registry UI</title></head><body><app></app><script src="scripts/vendor.js"></script><script src="scripts/docker-registry-ui.js"></script></body></html>
|
||||
--><!DOCTYPE html><html><head><meta charset="UTF-8"><link rel="stylesheet" href="vendor.css"><link rel="stylesheet" href="style.css"><link href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&lang=en" rel="stylesheet" type="text/css"><title>Docker Registry UI</title></head><body><app></app><script src="scripts/init.js"></script><script src="scripts/vendor.js"></script><script src="scripts/docker-registry-ui.js"></script></body></html>
|
||||
2
dist/scripts/docker-registry-ui-static.js
vendored
2
dist/scripts/docker-registry-ui-static.js
vendored
File diff suppressed because one or more lines are too long
2
dist/scripts/docker-registry-ui.js
vendored
2
dist/scripts/docker-registry-ui.js
vendored
File diff suppressed because one or more lines are too long
27
dist/scripts/init.js
vendored
Normal file
27
dist/scripts/init.js
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
const REGISTRY_SERVER_KEY = 'registryServer';
|
||||
let registryServersStr = localStorage.getItem(REGISTRY_SERVER_KEY);
|
||||
let registryServers = [];
|
||||
if (registryServersStr !== null){
|
||||
let rs = JSON.parse(registryServersStr);
|
||||
if (rs instanceof Array) {
|
||||
registryServers = rs;
|
||||
}
|
||||
}
|
||||
|
||||
const apiHost = location.protocol+'//'+location.host;
|
||||
if (!registryServers.includes(apiHost)) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', apiHost+"/v2/", true);
|
||||
xhr.onload = function (e) {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
const version = xhr.getResponseHeader('Docker-Distribution-Api-Version');
|
||||
if (version.startsWith('registry/2.')) {
|
||||
registryServers.unshift(apiHost);
|
||||
localStorage.setItem(REGISTRY_SERVER_KEY, JSON.stringify(registryServers));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
6
dist/scripts/vendor.js
vendored
6
dist/scripts/vendor.js
vendored
File diff suppressed because one or more lines are too long
42
gulpfile.js
42
gulpfile.js
@@ -3,10 +3,9 @@ const cleanCSS = require('gulp-clean-css');
|
||||
const concat = require('gulp-concat');
|
||||
const del = require('del');
|
||||
const filter = require('gulp-filter');
|
||||
const fs = require('fs');
|
||||
const gIf = require('gulp-if');
|
||||
const gulp = require('gulp');
|
||||
const parallel = gulp.parallel;
|
||||
const series = gulp.series;
|
||||
const htmlmin = require('gulp-htmlmin');
|
||||
const license = require('gulp-license');
|
||||
const riot = require('gulp-riot');
|
||||
@@ -30,8 +29,7 @@ const staticTags = [
|
||||
'src/tags/copy-to-clipboard.tag',
|
||||
'src/tags/remove-image.tag',
|
||||
'src/tags/image-size.tag',
|
||||
'src/tags/image-tag.tag',
|
||||
'src/tags/image-date.tag'
|
||||
'src/tags/image-tag.tag'
|
||||
];
|
||||
|
||||
const staticScripts = [
|
||||
@@ -39,7 +37,7 @@ const staticScripts = [
|
||||
'src/scripts/static.js'
|
||||
];
|
||||
|
||||
function html() {
|
||||
gulp.task('html', function() {
|
||||
var htmlFilter = filter('**/*.html', {restore: true});
|
||||
return gulp.src(['src/index.html'])
|
||||
.pipe(useref())
|
||||
@@ -54,13 +52,13 @@ function html() {
|
||||
}))
|
||||
.pipe(htmlFilter.restore)
|
||||
.pipe(gulp.dest('dist'));
|
||||
};
|
||||
});
|
||||
|
||||
function clean() {
|
||||
gulp.task('clean', function(done) {
|
||||
return del(['dist']);
|
||||
};
|
||||
});
|
||||
|
||||
function appStatic() {
|
||||
gulp.task('docker-registry-ui-static', ['html'], function() {
|
||||
return merge(gulp.src(staticScripts), gulp.src(staticTags).pipe(riot()))
|
||||
.pipe(concat('docker-registry-ui-static.js'))
|
||||
.pipe(minifier())
|
||||
@@ -72,9 +70,9 @@ function appStatic() {
|
||||
}))
|
||||
.pipe(injectVersion())
|
||||
.pipe(gulp.dest('dist/scripts'));
|
||||
};
|
||||
});
|
||||
|
||||
function app() {
|
||||
gulp.task('docker-registry-ui', ['html'], function() {
|
||||
return merge(gulp.src(allScripts), gulp.src(allTags).pipe(riot()))
|
||||
.pipe(concat('docker-registry-ui.js'))
|
||||
.pipe(minifier())
|
||||
@@ -86,15 +84,15 @@ function app() {
|
||||
}))
|
||||
.pipe(injectVersion())
|
||||
.pipe(gulp.dest('dist/scripts'));
|
||||
};
|
||||
});
|
||||
|
||||
function vendor() {
|
||||
gulp.task('vendor', ['html'], function() {
|
||||
return gulp.src(['node_modules/riot/riot.min.js', 'node_modules/riot-route/dist/route.min.js', 'node_modules/riot-mui/build/js/riot-mui-min.js'])
|
||||
.pipe(concat('vendor.js'))
|
||||
.pipe(gulp.dest('dist/scripts'));
|
||||
};
|
||||
});
|
||||
|
||||
function styles() {
|
||||
gulp.task('styles', ['html'], function() {
|
||||
return gulp.src(['src/*.css'])
|
||||
.pipe(concat('style.css'))
|
||||
.pipe(cleanCSS({
|
||||
@@ -107,12 +105,18 @@ function styles() {
|
||||
organization: 'Jones Magloire @Joxit'
|
||||
}))
|
||||
.pipe(gulp.dest('dist/'));
|
||||
};
|
||||
});
|
||||
|
||||
function fonts() {
|
||||
gulp.task('fonts', function() {
|
||||
return gulp.src('src/fonts/*')
|
||||
.pipe(filter('**/*.{otf,eot,svg,ttf,woff,woff2}'))
|
||||
.pipe(gulp.dest('dist/fonts'));
|
||||
};
|
||||
});
|
||||
|
||||
exports.build = series(clean, html, parallel(fonts, styles, vendor, app, appStatic));
|
||||
gulp.task('sources', ['docker-registry-ui', 'vendor', 'docker-registry-ui-static', 'styles'], function() {
|
||||
gulp.start();
|
||||
});
|
||||
|
||||
gulp.task('build', ['clean'], function() {
|
||||
gulp.start(['sources', 'fonts']);
|
||||
});
|
||||
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "docker-registry-ui",
|
||||
"version": "0.5.1",
|
||||
"version": "0.4.0",
|
||||
"scripts": {
|
||||
"build": "./node_modules/gulp/bin/gulp.js build"
|
||||
},
|
||||
@@ -14,20 +14,20 @@
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"del": "^3.0.0",
|
||||
"gulp": "^4.0",
|
||||
"gulp-clean-css": "^3.10.0",
|
||||
"gulp": "^3.9",
|
||||
"gulp-clean-css": "^3.9.4",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-filter": "^5.1.0",
|
||||
"gulp-htmlmin": "^3.0.0",
|
||||
"gulp-if": "^2.0.0",
|
||||
"gulp-inject-version": "^1.0.1",
|
||||
"gulp-license": "^1.1.0",
|
||||
"gulp-riot": "^1.1.5",
|
||||
"gulp-uglify": "^3.0.1",
|
||||
"gulp-useref": "^3.1.6",
|
||||
"riot": "^3.13.1",
|
||||
"gulp-riot": "^1.1.4",
|
||||
"gulp-uglify": "^3.0.0",
|
||||
"gulp-useref": "^3.1.5",
|
||||
"riot": "^3.11.1",
|
||||
"riot-mui": "^0.1.1",
|
||||
"riot-route": "^3.1.4",
|
||||
"riot-route": "^3.1.3",
|
||||
"stream-series": "^0.1.1",
|
||||
"uglify-es": "^3.3.10"
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
<body>
|
||||
<app></app>
|
||||
<script src="scripts/init.js"></script>
|
||||
<!-- build:js scripts/vendor.js -->
|
||||
<script src="../node_modules/riot/riot+compiler.min.js"></script>
|
||||
<script src="../node_modules/riot-route/dist/route.js"></script>
|
||||
@@ -48,7 +49,6 @@
|
||||
<script src="tags/remove.tag" type="riot/tag"></script>
|
||||
<script src="tags/menu.tag" type="riot/tag"></script>
|
||||
<script src="tags/image-size.tag" type="riot/tag"></script>
|
||||
<script src="tags/image-date.tag" type="riot/tag"></script>
|
||||
<script src="tags/app.tag" type="riot/tag"></script>
|
||||
<script src="scripts/http.js"></script>
|
||||
<script src="scripts/script.js"></script>
|
||||
|
||||
27
src/scripts/init.js
Normal file
27
src/scripts/init.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const REGISTRY_SERVER_KEY = 'registryServer';
|
||||
let registryServersStr = localStorage.getItem(REGISTRY_SERVER_KEY);
|
||||
let registryServers = [];
|
||||
if (registryServersStr !== null){
|
||||
let rs = JSON.parse(registryServersStr);
|
||||
if (rs instanceof Array) {
|
||||
registryServers = rs;
|
||||
}
|
||||
}
|
||||
|
||||
const apiHost = location.protocol+'//'+location.host;
|
||||
if (!registryServers.includes(apiHost)) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', apiHost+"/v2/", true);
|
||||
xhr.onload = function (e) {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
const version = xhr.getResponseHeader('Docker-Distribution-Api-Version');
|
||||
if (version.startsWith('registry/2.')) {
|
||||
registryServers.unshift(apiHost);
|
||||
localStorage.setItem(REGISTRY_SERVER_KEY, JSON.stringify(registryServers));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
@@ -78,7 +78,7 @@
|
||||
return registryUI.snackbar(message, true);
|
||||
}
|
||||
registryUI.cleanName = function() {
|
||||
const url = (registryUI.url() && registryUI.url().length > 0 && registryUI.url()) || window.location.host;
|
||||
var url = (registryUI.url() && registryUI.url().length > 0 && registryUI.url()) || window.location.host;
|
||||
if (url) {
|
||||
return url.startsWith('http') ? url.replace(/https?:\/\//, '') : url;
|
||||
}
|
||||
@@ -114,12 +114,6 @@
|
||||
}
|
||||
return this.fillInfo();
|
||||
});
|
||||
this.on('get-date', function() {
|
||||
if (this.date !== undefined) {
|
||||
return this.trigger('date', this.date);
|
||||
}
|
||||
return this.fillInfo();
|
||||
});
|
||||
};
|
||||
|
||||
registryUI.DockerImage._tagReduce = function (acc, e) {
|
||||
@@ -132,13 +126,13 @@
|
||||
}
|
||||
|
||||
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, []);
|
||||
var tag1 = e1.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce, []);
|
||||
var 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]);
|
||||
var 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];
|
||||
var diff = tag1[i] - tag2[i];
|
||||
if (diff != 0) {
|
||||
return diff;
|
||||
}
|
||||
@@ -154,18 +148,17 @@
|
||||
return;
|
||||
}
|
||||
this._fillInfoWaiting = true;
|
||||
const oReq = new Http();
|
||||
const self = this;
|
||||
var oReq = new Http();
|
||||
var self = this;
|
||||
oReq.addEventListener('loadend', function () {
|
||||
if (this.status == 200 || this.status == 202) {
|
||||
const response = JSON.parse(this.responseText);
|
||||
var response = JSON.parse(this.responseText);
|
||||
self.size = response.layers.reduce(function (acc, e) {
|
||||
return acc + e.size;
|
||||
}, 0);
|
||||
self.sha256 = response.config.digest;
|
||||
self.trigger('size', self.size);
|
||||
self.trigger('sha256', self.sha256);
|
||||
self.getBlobs(response.config.digest)
|
||||
} else if (this.status == 404) {
|
||||
registryUI.errorSnackbar('Manifest for ' + self.name + ':' + self.tag + ' not found');
|
||||
} else {
|
||||
@@ -176,25 +169,6 @@
|
||||
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.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.trigger('creation-date', self.creationDate);
|
||||
} 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');
|
||||
oReq.send();
|
||||
};
|
||||
route.start(true);
|
||||
</script>
|
||||
</app>
|
||||
@@ -1,43 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2018 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/>.
|
||||
-->
|
||||
<image-date>
|
||||
<div title="Creation date { this.localDate }">{ this.dateFormat(this.date) } ago</div>
|
||||
<script type="text/javascript">
|
||||
var self = this;
|
||||
this.dateFormat = function(date) {
|
||||
if (date === undefined) {
|
||||
return '';
|
||||
}
|
||||
const labels = ['a second', 'seconds', 'a minute', 'minutes', 'an hour', 'hours', 'a day', 'days', 'a month', 'months', 'a year', 'years'];
|
||||
const maxSeconds = [1, 60, 3600, 86400, 2592000, 31104000, Infinity];
|
||||
const diff = (new Date() - date) / 1000;
|
||||
for (var i = 0; i < maxSeconds.length - 1; i++) {
|
||||
if (maxSeconds[i] * 2 >= diff) {
|
||||
return labels[i * 2];
|
||||
} else if (maxSeconds[i + 1] > diff) {
|
||||
return Math.floor(diff / maxSeconds[i]) + ' ' + labels[i * 2 + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
opts.image.on('creation-date', function(date) {
|
||||
self.date = date;
|
||||
self.localDate = date.toLocaleString()
|
||||
self.update();
|
||||
});
|
||||
opts.image.trigger('get-date');
|
||||
</script>
|
||||
</image-date>
|
||||
@@ -31,7 +31,6 @@
|
||||
<tr>
|
||||
<th class="material-card-th-left">Repository</th>
|
||||
<th></th>
|
||||
<th>Creation date</th>
|
||||
<th>Size</th>
|
||||
<th class="{ registryUI.taglist.asc ? 'material-card-th-sorted-ascending' : 'material-card-th-sorted-descending' }" onclick="registryUI.taglist.reverse();">Tag</th>
|
||||
<th show="{ registryUI.isImageRemoveActivated }"></th>
|
||||
@@ -43,7 +42,6 @@
|
||||
<td class="copy-to-clipboard">
|
||||
<copy-to-clipboard image={ image }/>
|
||||
</td>
|
||||
<td><image-date image="{ image }" /></td>
|
||||
<td><image-size image="{ image }" /></td>
|
||||
<td><image-tag image="{ image }" /></td>
|
||||
<td show="{ registryUI.isImageRemoveActivated }">
|
||||
|
||||
Reference in New Issue
Block a user