Compare commits

..

1 Commits
0.6.0 ... 0.4.1

Author SHA1 Message Date
wildloop
115105a2a0 Init with the docker registry hosted on the same domain as UI 2018-11-12 20:00:05 +01:00
47 changed files with 359 additions and 807 deletions

38
.gitignore vendored
View File

@@ -1,5 +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
.idea

View File

@@ -27,8 +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)
- Display image history (see #58)
## Getting Started

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg2" x="0px" y="0px" viewBox="-5.724 -43.601 730 600" fill="#777"><path d="m595.942,422.343 c -7.543,0.119 -13.562,6.331 -13.443,13.875 0.119,7.544 6.332,13.562 13.875,13.443 7.495,-0.118 13.494,-6.254 13.445,-13.75 -0.085,-7.578 -6.297,-13.652 -13.875,-13.568 0,0 -10e-4,0 0,0 m 0,24.398 c -5.975,0.272 -11.039,-4.352 -11.311,-10.326 -0.271,-5.976 4.352,-11.04 10.327,-11.312 5.975,-0.271 11.039,4.352 11.311,10.327 0.01,0.19 0.013,0.382 0.011,0.573 0.204,5.723 -4.27,10.527 -9.992,10.731 -0.115,0.005 -0.23,0.007 -0.346,0.007"/><path d="m599.081,436.342 v -0.185 c 1.512,-0.292 2.65,-1.544 2.8,-3.076 0.057,-1.175 -0.432,-2.311 -1.323,-3.077 -1.445,-0.765 -3.076,-1.106 -4.707,-0.984 -1.743,-0.024 -3.484,0.12 -5.2,0.431 v 13.538 h 3.077 v -5.446 h 1.477 c 1.754,0 2.554,0.646 2.83,2.154 0.184,1.143 0.536,2.252 1.047,3.292 h 3.415 c -0.53,-1.062 -0.873,-2.207 -1.016,-3.385 -0.138,-1.473 -1.088,-2.744 -2.462,-3.292 m -3.723,-0.985 h -1.508 v -3.908 c 0.583,-0.069 1.172,-0.069 1.754,0 1.97,0 2.893,0.831 2.893,2.062 0,1.231 -1.415,2 -3.076,2"/><path d="M707.494,193.557c-1.938-1.539-20.029-15.199-58.181-15.199c-10.074,0.044-20.127,0.908-30.061,2.584 c-7.384-50.612-49.228-75.288-51.104-76.395l-10.245-5.908l-6.738,9.723c-8.438,13.061-14.598,27.459-18.214,42.582 c-6.831,28.891-2.677,56.027,11.999,79.226c-17.722,9.876-46.151,12.307-51.904,12.522H22.367 c-12.294,0.017-22.27,9.952-22.337,22.245c-0.549,41.234,6.437,82.222,20.614,120.946c16.214,42.521,40.336,73.842,71.719,93.01 c35.167,21.537,92.302,33.844,157.067,33.844c29.258,0.092,58.461-2.556,87.226-7.907c39.986-7.342,78.463-21.318,113.839-41.352 c29.149-16.88,55.383-38.354,77.688-63.596c37.29-42.213,59.505-89.226,76.026-131.007c2.215,0,4.431,0,6.584,0 c40.828,0,65.935-16.338,79.78-30.029c9.201-8.732,16.384-19.369,21.045-31.167l2.923-8.553L707.494,193.557z"/><path d="M65.995,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.476-5.538 c-0.01,0-0.021,0-0.031,0H65.995c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C60.488,226.443,62.953,228.909,65.995,228.909L65.995,228.909"/><path d="M152.913,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.059,0-5.538,2.479-5.538,5.538v56.181C147.392,226.448,149.866,228.909,152.913,228.909"/><path d="M241.153,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C235.646,226.443,238.112,228.909,241.153,228.909L241.153,228.909"/><path d="M328.348,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C322.841,226.443,325.307,228.909,328.348,228.909L328.348,228.909"/><path d="M152.913,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.046,0-5.521,2.46-5.538,5.507v56.181C147.392,145.597,149.861,148.066,152.913,148.083"/><path d="M241.153,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C235.646,145.591,238.107,148.066,241.153,148.083"/><path d="M328.348,148.083h63.073c3.052-0.017,5.521-2.486,5.538-5.538V86.364c-0.017-3.047-2.491-5.507-5.538-5.507h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C322.841,145.591,325.302,148.066,328.348,148.083"/><path d="M328.348,67.227h63.073c3.047,0,5.521-2.461,5.538-5.507V5.507C396.942,2.46,394.468,0,391.421,0h-63.073 c-3.042,0-5.507,2.465-5.507,5.507l0,0v56.212C322.841,64.761,325.307,67.227,328.348,67.227"/><path d="M416.312,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.041,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C410.805,226.443,413.271,228.909,416.312,228.909"/></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

4
dist/index.html vendored
View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016 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
@@ -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&amp;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&amp;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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

27
dist/scripts/init.js vendored Normal file
View 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();
}

File diff suppressed because one or more lines are too long

2
dist/style.css vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,22 +0,0 @@
# Traefik example
Host the docker registry ui behind [traefik](http://traefik.io) with Docker Swarm mode.
## How to run
Open a terminal console and type
```bash
bash run-swarm.sh
```
## Authentication
The registry is protected via __Basic authentication__ but feel free to use wathever you like.
In this sample, credentials are: **admin / admin**.
To generate a new password for basic auth, run the command: `htpasswd -nb username password`.
## Contributors
Thank you [@onizet](https://github.com/onizet) for this example.

View File

@@ -1,33 +0,0 @@
version: '3.1'
services:
registry:
image: registry:2.6.2
volumes:
- /opt/docker-registry:/var/lib/registry
environment:
- REGISTRY_HTTP_SECRET=my_registry_secret
- REGISTRY_STORAGE_DELETE_ENABLED=true
deploy:
placement:
constraints: [node.role == manager]
ui:
image: joxit/docker-registry-ui:static
environment:
- DELETE_IMAGES=true
- REGISTRY_TITLE=My Private Docker Registry
- REGISTRY_URL=http://docker-registry_registry:5000
depends_on: ['registry']
networks: ['proxy', 'default']
deploy:
labels:
traefik.backend: 'registry.mydomain.com'
traefik.frontend.rule: 'Host:registry.mydomain.com'
traefik.enable: 'true'
traefik.port: 80
traefik.docker.network: 'traefik-net'
traefik.frontend.auth.basic: 'admin:$apr1$XXrpwZre$ItZSXpoeB6bdPLCGT7eXG0'
traefik.frontend.passHostHeader: 'true'
networks:
proxy: {external: {name: 'traefik-net'}}

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env bash
if ! [[ `docker network ls | grep "traefik-net"` ]] &>/dev/null; then
echo "Setup traefik network"
docker network create --driver=overlay --attachable traefik-net
fi
if ! [[ `docker service ls | grep "traefik2"` ]] &>/dev/null; then
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# ensure acme.json wich will contains the letsencrypt certificates
touch "$dir"/acme.json && chmod 600 "$dir"/acme.json
docker service create --name traefik2 --detach=false \
--constraint node.role==manager \
--update-parallelism 1 --update-delay 10s \
--mode global \
--publish 80:80 \
--publish 443:443 \
--read-only \
--mount type=bind,source="$(pwd)"/acme.json,target=/etc/traefik/acme.json \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--network traefik-net \
traefik:1.7.4-alpine \
--entrypoints='Name:http Address::80 Redirect.EntryPoint:https' \
--entrypoints='Name:https Address::443 TLS' \
--defaultentrypoints=http,https \
--acme \
--acme.storage=/etc/traefik/acme.json \
--acme.entryPoint=https \
--acme.httpChallenge.entryPoint=http \
--acme.email=contact@mydomain.com \
--docker \
--docker.swarmMode \
--docker.domain=mydomain.com \
--docker.exposedByDefault=false \
--docker.watch \
--api
fi
docker stack deploy --compose-file docker-compose-swarm.yml docker-registry

View File

@@ -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');
@@ -16,21 +15,29 @@ const useref = require('gulp-useref');
const injectVersion = require('gulp-inject-version');
const merge = require('stream-series');
const allTags = ['src/tags/*.tag', 'src/tags/dialogs/*.tag'];
const allTags = 'src/tags/*.tag';
const allScripts = [
'src/scripts/http.js',
'src/scripts/script.js'
];
const staticTags = ['src/tags/*.tag'];
const staticTags = [
'src/tags/catalog.tag',
'src/tags/app.tag',
'src/tags/taglist.tag',
'src/tags/copy-to-clipboard.tag',
'src/tags/remove-image.tag',
'src/tags/image-size.tag',
'src/tags/image-tag.tag'
];
const staticScripts = [
'src/scripts/http.js',
'src/scripts/static.js'
];
function html() {
gulp.task('html', function() {
var htmlFilter = filter('**/*.html', {restore: true});
return gulp.src(['src/index.html'])
.pipe(useref())
@@ -45,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())
@@ -63,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())
@@ -77,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({
@@ -98,24 +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'));
};
});
function svgs() {
return gulp.src(['src/images/*.svg'])
.pipe(htmlmin({
removeComments: false,
collapseWhitespace: true,
removeRedundantAttributes: true,
removeEmptyAttributes: true,
minifyJS: uglify
}))
.pipe(gulp.dest('dist/images/'));
};
gulp.task('sources', ['docker-registry-ui', 'vendor', 'docker-registry-ui-static', 'styles'], function() {
gulp.start();
});
exports.build = series(clean, html, parallel(fonts, styles, vendor, app, appStatic, svgs));
gulp.task('build', ['clean'], function() {
gulp.start(['sources', 'fonts']);
});

View File

@@ -1,6 +1,6 @@
{
"name": "docker-registry-ui",
"version": "0.6.0",
"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": "^4.0.0",
"gulp": "^3.9",
"gulp-clean-css": "^3.9.4",
"gulp-concat": "^2.6.0",
"gulp-filter": "^5.1.0",
"gulp-htmlmin": "^5.0.1",
"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.2",
"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"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg2"
x="0px"
y="0px"
viewBox="-5.724 -43.601 730 600" fill="#777">
<path
d="m595.942,422.343 c -7.543,0.119 -13.562,6.331 -13.443,13.875 0.119,7.544 6.332,13.562 13.875,13.443 7.495,-0.118 13.494,-6.254 13.445,-13.75 -0.085,-7.578 -6.297,-13.652 -13.875,-13.568 0,0 -10e-4,0 0,0 m 0,24.398 c -5.975,0.272 -11.039,-4.352 -11.311,-10.326 -0.271,-5.976 4.352,-11.04 10.327,-11.312 5.975,-0.271 11.039,4.352 11.311,10.327 0.01,0.19 0.013,0.382 0.011,0.573 0.204,5.723 -4.27,10.527 -9.992,10.731 -0.115,0.005 -0.23,0.007 -0.346,0.007"/>
<path
d="m599.081,436.342 v -0.185 c 1.512,-0.292 2.65,-1.544 2.8,-3.076 0.057,-1.175 -0.432,-2.311 -1.323,-3.077 -1.445,-0.765 -3.076,-1.106 -4.707,-0.984 -1.743,-0.024 -3.484,0.12 -5.2,0.431 v 13.538 h 3.077 v -5.446 h 1.477 c 1.754,0 2.554,0.646 2.83,2.154 0.184,1.143 0.536,2.252 1.047,3.292 h 3.415 c -0.53,-1.062 -0.873,-2.207 -1.016,-3.385 -0.138,-1.473 -1.088,-2.744 -2.462,-3.292 m -3.723,-0.985 h -1.508 v -3.908 c 0.583,-0.069 1.172,-0.069 1.754,0 1.97,0 2.893,0.831 2.893,2.062 0,1.231 -1.415,2 -3.076,2"/>
<path
d="M707.494,193.557c-1.938-1.539-20.029-15.199-58.181-15.199c-10.074,0.044-20.127,0.908-30.061,2.584 c-7.384-50.612-49.228-75.288-51.104-76.395l-10.245-5.908l-6.738,9.723c-8.438,13.061-14.598,27.459-18.214,42.582 c-6.831,28.891-2.677,56.027,11.999,79.226c-17.722,9.876-46.151,12.307-51.904,12.522H22.367 c-12.294,0.017-22.27,9.952-22.337,22.245c-0.549,41.234,6.437,82.222,20.614,120.946c16.214,42.521,40.336,73.842,71.719,93.01 c35.167,21.537,92.302,33.844,157.067,33.844c29.258,0.092,58.461-2.556,87.226-7.907c39.986-7.342,78.463-21.318,113.839-41.352 c29.149-16.88,55.383-38.354,77.688-63.596c37.29-42.213,59.505-89.226,76.026-131.007c2.215,0,4.431,0,6.584,0 c40.828,0,65.935-16.338,79.78-30.029c9.201-8.732,16.384-19.369,21.045-31.167l2.923-8.553L707.494,193.557z"/>
<path
d="M65.995,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.476-5.538 c-0.01,0-0.021,0-0.031,0H65.995c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C60.488,226.443,62.953,228.909,65.995,228.909L65.995,228.909"/>
<path
d="M152.913,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.059,0-5.538,2.479-5.538,5.538v56.181C147.392,226.448,149.866,228.909,152.913,228.909"/>
<path
d="M241.153,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C235.646,226.443,238.112,228.909,241.153,228.909L241.153,228.909"/>
<path
d="M328.348,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C322.841,226.443,325.307,228.909,328.348,228.909L328.348,228.909"/>
<path
d="M152.913,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.046,0-5.521,2.46-5.538,5.507v56.181C147.392,145.597,149.861,148.066,152.913,148.083"/>
<path
d="M241.153,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C235.646,145.591,238.107,148.066,241.153,148.083"/>
<path
d="M328.348,148.083h63.073c3.052-0.017,5.521-2.486,5.538-5.538V86.364c-0.017-3.047-2.491-5.507-5.538-5.507h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C322.841,145.591,325.302,148.066,328.348,148.083"/>
<path
d="M328.348,67.227h63.073c3.047,0,5.521-2.461,5.538-5.507V5.507C396.942,2.46,394.468,0,391.421,0h-63.073 c-3.042,0-5.507,2.465-5.507,5.507l0,0v56.212C322.841,64.761,325.307,67.227,328.348,67.227"/>
<path
d="M416.312,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.041,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C410.805,226.443,413.271,228.909,416.312,228.909"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016 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
@@ -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>
@@ -39,19 +40,15 @@
<!-- endbuild -->
<!-- build:js scripts/docker-registry-ui.js -->
<script src="tags/catalog.tag" type="riot/tag"></script>
<script src="tags/tag-history-button.tag" type="riot/tag"></script>
<script src="tags/tag-history.tag" type="riot/tag"></script>
<script src="tags/tag-history-element.tag" type="riot/tag"></script>
<script src="tags/taglist.tag" type="riot/tag"></script>
<script src="tags/image-tag.tag" type="riot/tag"></script>
<script src="tags/remove-image.tag" type="riot/tag"></script>
<script src="tags/copy-to-clipboard.tag" type="riot/tag"></script>
<script src="tags/dialogs/add.tag" type="riot/tag"></script>
<script src="tags/dialogs/change.tag" type="riot/tag"></script>
<script src="tags/dialogs/remove.tag" type="riot/tag"></script>
<script src="tags/dialogs/menu.tag" type="riot/tag"></script>
<script src="tags/add.tag" type="riot/tag"></script>
<script src="tags/change.tag" type="riot/tag"></script>
<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>

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016-2018 Jones Magloire @Joxit
* Copyright (C) 2016 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
@@ -24,13 +24,13 @@ function Http() {
Http.prototype.addEventListener = function(e, f) {
this._events[e] = f;
const self = this;
var self = this;
switch (e) {
case 'loadend':
{
self.oReq.addEventListener('loadend', function() {
if (this.status == 401) {
const req = new XMLHttpRequest();
var req = new XMLHttpRequest();
req.open(self._method, self._url);
for (key in self._events) {
req.addEventListener(key, self._events[key]);

27
src/scripts/init.js Normal file
View 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();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016-2018 Jones Magloire @Joxit
* Copyright (C) 2016 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
@@ -20,7 +20,7 @@ registryUI.URL_PARAM_REGEX = /^url=/;
registryUI.name = registryUI.url = function(byPassQueryParam) {
if (!registryUI._url) {
const url = registryUI.getUrlQueryParam();
var url = registryUI.getUrlQueryParam();
if (url) {
try {
registryUI._url = registryUI.decodeURI(url);
@@ -35,7 +35,7 @@ registryUI.name = registryUI.url = function(byPassQueryParam) {
}
registryUI.getRegistryServer = function(i) {
try {
const res = JSON.parse(localStorage.getItem('registryServer'));
var res = JSON.parse(localStorage.getItem('registryServer'));
if (res instanceof Array) {
return (!isNaN(i)) ? res[i] : res.map(function(url) {
return url.trim().replace(/\/*$/, '');
@@ -45,9 +45,9 @@ registryUI.getRegistryServer = function(i) {
return (!isNaN(i)) ? '' : [];
}
registryUI.addServer = function(url) {
const registryServer = registryUI.getRegistryServer();
var registryServer = registryUI.getRegistryServer();
url = url.trim().replace(/\/*$/, '');
const index = registryServer.indexOf(url);
var index = registryServer.indexOf(url);
if (index != -1) {
return;
}
@@ -56,11 +56,11 @@ registryUI.addServer = function(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);
var index = registryServer.indexOf(url);
if (index == -1) {
return;
}
@@ -68,11 +68,11 @@ registryUI.changeServer = function(url) {
registryServer = [url].concat(registryServer);
registryUI.updateHistory(url);
localStorage.setItem('registryServer', JSON.stringify(registryServer));
};
}
registryUI.removeServer = function(url) {
const registryServer = registryUI.getRegistryServer();
var registryServer = registryUI.getRegistryServer();
url = url.trim().replace(/\/*$/, '');
const index = registryServer.indexOf(url);
var index = registryServer.indexOf(url);
if (index == -1) {
return;
}
@@ -90,9 +90,9 @@ registryUI.updateHistory = function(url) {
}
registryUI.getUrlQueryParam = function () {
const search = window.location.search;
var search = window.location.search;
if (registryUI.URL_QUERY_PARAM_REGEX.test(search)) {
const param = search.split(/^\?|&/).find(function(param) {
var param = search.split(/^\?|&/).find(function(param) {
return param && registryUI.URL_PARAM_REGEX.test(param);
});
return param ? param.replace(registryUI.URL_PARAM_REGEX, '') : param;
@@ -110,7 +110,6 @@ registryUI.decodeURI = function(url) {
registryUI.isImageRemoveActivated = true;
registryUI.catalog = {};
registryUI.taglist = {};
registryUI.taghistory = {};
window.addEventListener('DOMContentLoaded', function() {
riot.mount('*');

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016-2018 Jones Magloire @Joxit
* Copyright (C) 2016 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
@@ -24,7 +24,6 @@ registryUI.name = function() {
registryUI.isImageRemoveActivated = true;
registryUI.catalog = {};
registryUI.taglist = {};
registryUI.taghistory = {};
window.addEventListener('DOMContentLoaded', function() {
riot.mount('*');

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
html > body {
html>body {
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif !important;
}
@@ -94,12 +94,12 @@ h2 {
list-style: none;
}
.list.highlight > li:hover {
.list.highlight>li:hover {
background-color: #eee;
cursor: pointer;
}
.list > li {
.list>li {
box-sizing: border-box;
line-height: 1;
height: 48px;
@@ -107,7 +107,7 @@ h2 {
overflow: hidden;
}
.list > li i.material-icons {
.list>li i.material-icons {
margin-right: 32px;
height: 24px;
width: 24px;
@@ -116,7 +116,7 @@ h2 {
color: #757575;
}
.list > li > span {
.list>li>span {
height: 100%;
text-decoration: none;
box-sizing: border-box;
@@ -194,7 +194,6 @@ material-card table td {
text-align: right;
}
tag-history-button button:hover,
material-card table th.material-card-th-sorted-ascending:hover, material-card table th.material-card-th-sorted-descending:hover {
cursor: pointer;
}
@@ -323,58 +322,6 @@ select {
padding: 12px 5px;
}
.show-tag-history {
width: 30px;
text-align: center;
}
.remove-tag {
padding: 12px 5px;
width: 30px;
text-align: center;
}
.copy-to-clipboard a:hover {
cursor: pointer;
}
tag-history material-card {
min-height: auto;
}
tag-history-element i {
font-size: 20px;
padding: 0px;
}
tag-history-element.docker_version .headline .material-icons {
background-size: 24px auto;
background-image: url("images/docker-logo.svg");
background-repeat: no-repeat;
}
tag-history-element {
display: block;
padding: 20px;
min-width: 100px;
width: 420px;
float: left;
overflow-x: auto;
}
tag-history-element .headline p {
font-weight: bold;
line-height: 20px;
position: relative;
display: inline;
top: -4px;
}
tag-history-element.id div.value {
font-size: 12px;
}
tag-history-button button {
background: none;
border: none;
}

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016 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

View File

@@ -1,18 +1,18 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016 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 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.
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/>.
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>
@@ -24,7 +24,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<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>
@@ -32,8 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</main>
<footer>
<material-footer>
<a class="material-footer-logo" href="https://joxit.github.io/docker-registry-ui/">Docker Registry UI
%%GULP_INJECT_VERSION%%</a>
<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>
@@ -46,74 +44,61 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</material-footer>
</footer>
<script>
registryUI.appTag = this;
route.base('#!');
route.base('#!')
route('', function() {
route.routeName = 'home';
if (registryUI.catalog.display) {
registryUI.catalog.loadend = false;
registryUI.catalog.display();
}
registryUI.appTag.update();
});
route('/taglist/*', function(image) {
route.routeName = 'taglist';
registryUI.taglist.name = image;
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.taglist.display();
}
registryUI.appTag.update();
});
registryUI.home = function() {
if (route.routeName == 'home') {
registryUI.catalog.display;
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.snackbar = function (message, isError) {
registryUI.appTag.tags['material-snackbar'].addToast({'message': message, 'isError': isError}, 15000);
};
registryUI.errorSnackbar = function(message) {
registryUI.errorSnackbar = function (message) {
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;
}
return '';
};
}
route.parser(null, function(path, filter) {
const f = filter
.replace(/\?/g, '\\?')
.replace(/\*/g, '([^?#]+?)')
.replace(/\.\./, '.*');
const re = new RegExp('^' + f + '$');
const args = path.match(re);
.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) {
registryUI.DockerImage = function (name, tag) {
this.name = name;
this.tag = tag;
riot.observable(this);
@@ -129,31 +114,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
}
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) {
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, []);
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;
}
@@ -169,19 +148,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
return;
}
this._fillInfoWaiting = true;
const oReq = new Http();
const self = this;
oReq.addEventListener('loadend', function() {
var oReq = new Http();
var self = this;
oReq.addEventListener('loadend', function () {
if (this.status == 200 || this.status == 202) {
const response = JSON.parse(this.responseText);
self.size = response.layers.reduce(function(acc, e) {
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.layers = response.layers;
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 {
@@ -191,46 +168,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/manifests/' + self.tag);
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.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');
oReq.send();
};
registryUI.bytesToSize = function (bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == undefined || isNaN(bytes)) {
return '?';
} else if (bytes == 0) {
return '0 Byte';
}
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.ceil(bytes / Math.pow(1024, i)) + ' ' + sizes[i];
};
}
route.start(true);
</script>
</app>

View File

@@ -1,18 +1,18 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016 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 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.
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/>.
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 -->
@@ -35,10 +35,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<script>
registryUI.catalog.instance = this;
registryUI.catalog.display = function() {
registryUI.catalog.display = function () {
registryUI.catalog.repositories = [];
const oReq = new Http();
oReq.addEventListener('load', function() {
var oReq = new Http();
oReq.addEventListener('load', function () {
registryUI.catalog.repositories = [];
if (this.status == 200) {
registryUI.catalog.repositories = JSON.parse(this.responseText).repositories || [];
@@ -49,18 +49,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
registryUI.snackbar(this.responseText);
}
});
oReq.addEventListener('error', function() {
oReq.addEventListener('error', function () {
registryUI.snackbar(this.getErrorMessage(), true);
registryUI.catalog.repositories = [];
});
oReq.addEventListener('loadend', function() {
oReq.addEventListener('loadend', function () {
registryUI.catalog.loadend = true;
registryUI.catalog.instance.update();
});
oReq.open('GET', registryUI.url() + '/v2/_catalog?n=100000');
oReq.send();
};
registryUI.catalog.go = function(image) {
registryUI.catalog.go = function (image) {
route('taglist/' + image);
};
registryUI.catalog.display();

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016 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

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016-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
@@ -22,7 +22,7 @@
<script type="text/javascript">
this.dockerCmd = 'docker pull ' + registryUI.cleanName() + '/' + opts.image.name + ':' + opts.image.tag;
this.copy = function () {
const copyText = this.refs['input'];
var copyText = this.refs['input'];
copyText.style.display = 'block';
copyText.select();
document.execCommand('copy');

View File

@@ -1,61 +0,0 @@
<!--
Copyright (C) 2016-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/>.
-->
<remove>
<material-popup>
<div class="material-popup-title">Remove your Registry Server ?</div>
<div class="material-popup-content">
<ul class="list">
<li each="{ url in registryUI.getRegistryServer() }">
<span>
<a href="#" onClick="registryUI.removeTag.removeUrl('{url}');">
<i class="material-icons">delete</i>
</a>
<span class="url">{ url }</span>
</span>
</li>
</ul>
</div>
<div class="material-popup-action">
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="registryUI.removeTag.close();">
Close
</material-button>
</div>
</material-popup>
<script type="text/javascript">
registryUI.removeTag = registryUI.removeTag || {}
registryUI.removeTag.update = this.update;
registryUI.removeTag.removeUrl = function(url) {
registryUI.removeServer(url);
registryUI.removeTag.close();
};
registryUI.removeTag.close = function() {
registryUI.removeTag.dialog.close();
registryUI.removeTag.update();
};
registryUI.removeTag.show = function() {
registryUI.removeTag.dialog.open();
};
this.one('mount', function() {
registryUI.removeTag.dialog = this.tags['material-popup'];
});
</script>
</remove>

View File

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

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2018 Jones Magloire @Joxit
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
@@ -15,9 +15,19 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<image-size>
<div title="Compressed size of your image.">{ registryUI.bytesToSize(this.size) }</div>
<div title="Compressed size of your image.">{ this.bytesToSize(this.size) }</div>
<script type="text/javascript">
const self = this;
var self = this;
this.bytesToSize = function (bytes) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == undefined || isNaN(bytes)) {
return '?';
} else if (bytes == 0) {
return '0 Byte';
}
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.ceil(bytes / Math.pow(1024, i)) + ' ' + sizes[i];
};
opts.image.on('size', function(size) {
self.size = size;
self.update();

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016-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
@@ -17,7 +17,7 @@
<image-tag>
<div title="{ this.sha256 }">{ opts.image.tag }</div>
<script type="text/javascript">
const self = this;
var self = this;
opts.image.on('sha256', function(sha256) {
self.sha256 = sha256.substring(0, 19);
self.update();

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016 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
@@ -28,7 +28,7 @@
registryUI.menuTag = registryUI.menuTag || {};
registryUI.menuTag.update = this.update;
this.one('mount', function(args) {
const self = this;
var self = this;
registryUI.menuTag.close = function() {
self.tags['material-dropdown'].close();
self.update();

View File

@@ -1,39 +1,38 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016 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 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.
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/>.
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/>.
-->
<remove-image>
<a href="#" title="This will delete the image."
onclick="registryUI.removeImage.remove('{ opts.image.name }', '{ opts.image.tag }')">
<a href="#" title="This will delete the image." onclick="registryUI.removeImage.remove('{ opts.image.name }', '{ opts.image.tag }')">
<i class="material-icons">delete</i>
</a>
<script type="text/javascript">
registryUI.removeImage = registryUI.removeImage || {};
registryUI.removeImage.remove = function(name, tag) {
const oReq = new Http();
oReq.addEventListener('loadend', function() {
registryUI.removeImage.remove = function (name, tag) {
var oReq = new Http();
oReq.addEventListener('loadend', function () {
registryUI.taglist.refresh();
if (this.status == 200) {
if (!this.hasHeader('Docker-Content-Digest')) {
registryUI.errorSnackbar('You need to add Access-Control-Expose-Headers: [\'Docker-Content-Digest\'] in your server configuration.');
return;
}
const digest = this.getResponseHeader('Docker-Content-Digest');
const oReq = new Http();
oReq.addEventListener('loadend', function() {
var digest = this.getResponseHeader('Docker-Content-Digest');
var oReq = new Http();
oReq.addEventListener('loadend', function () {
if (this.status == 200 || this.status == 202) {
registryUI.taglist.refresh();
registryUI.snackbar('Deleting ' + name + ':' + tag + ' image. Run `registry garbage-collect config.yml` on your registry');
@@ -45,7 +44,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
});
oReq.open('DELETE', registryUI.url() + '/v2/' + name + '/manifests/' + digest);
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
oReq.addEventListener('error', function() {
oReq.addEventListener('error', function () {
registryUI.errorSnackbar('An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].');
});
oReq.send();

59
src/tags/remove.tag Normal file
View File

@@ -0,0 +1,59 @@
<!--
Copyright (C) 2016 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/>.
-->
<remove>
<material-popup>
<div class="material-popup-title">Remove your Registry Server ?</div>
<div class="material-popup-content">
<ul class="list">
<li each="{ url in registryUI.getRegistryServer() }">
<span>
<a href="#" onClick="registryUI.removeTag.removeUrl('{url}');">
<i class="material-icons">delete</i>
</a>
<span class="url">{ url }</span>
</span>
</li>
</ul>
</div>
<div class="material-popup-action">
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="registryUI.removeTag.close();">Close</material-button>
</div>
</material-popup>
<script type="text/javascript">
registryUI.removeTag = registryUI.removeTag || {}
registryUI.removeTag.update = this.update;
registryUI.removeTag.removeUrl = function (url) {
registryUI.removeServer(url);
registryUI.removeTag.close();
};
registryUI.removeTag.close = function () {
registryUI.removeTag.dialog.close();
registryUI.removeTag.update();
};
registryUI.removeTag.show = function () {
registryUI.removeTag.dialog.open();
};
this.one('mount', function () {
registryUI.removeTag.dialog = this.tags['material-popup'];
});
</script>
</remove>

View File

@@ -1,31 +0,0 @@
<!--
Copyright (C) 2016-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/>.
-->
<tag-history-button>
<button ref="button" title="This will show the history of given tag">
<i class="material-icons">history</i>
</button>
<script>
this.on('mount', function() {
const self = this;
this.refs.button.onclick = function() {
registryUI.taghistory._image = self.opts.image;
registryUI.taghistory.go(self.opts.image.name, self.opts.image.tag);
};
});
this.update()
</script>
</tag-history-button>

View File

@@ -1,64 +0,0 @@
<!--
Copyright (C) 2016-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/>.
-->
<tag-history-element class="{entry.key}">
<div class="headline"><i class="material-icons">{ this.getIcon(entry.key) }</i>
<p>{ entry.key.replace('_', ' ') }</p>
</div>
<div class="value" if={!(entry.value instanceof Array)}> { entry.value }</div>
<div class="value" each={ e in entry.value } if={entry.value instanceof Array}> { e }</div>
<script type="text/javascript">
this.getIcon = function(attribute) {
switch (attribute) {
case 'architecture':
return 'memory';
case 'created':
return 'event';
case 'docker_version':
return '';
case 'os':
return 'developer_board';
case 'Cmd':
return 'launch';
case 'Entrypoint':
return 'input';
case 'Env':
return 'notes';
case 'Labels':
return 'label';
case 'User':
return 'face';
case 'Volumes':
return 'storage';
case 'WorkingDir':
return 'home';
case 'author':
return 'account_circle';
case 'id':
case 'digest':
return 'settings_ethernet';
case 'created_by':
return 'build';
case 'size':
return 'get_app';
case 'ExposedPorts':
return 'router';
default:
''
}
}
</script>
</tag-history-element>

View File

@@ -1,131 +0,0 @@
<!--
Copyright (C) 2016-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/>.
-->
<tag-history>
<material-card ref="tag-history-tag" class="tag-history">
<div class="material-card-title-action">
<a href="#!taglist/{registryUI.taghistory.image}">
<i class="material-icons">arrow_back</i>
</a>
<h2>
History of { registryUI.taghistory.image }:{ registryUI.taghistory.tag } <i class="material-icons">history</i>
</h2>
</div>
</material-card>
<div hide="{ registryUI.taghistory.loadend }" class="spinner-wrapper">
<material-spinner/>
</div>
<material-card each="{ guiElement in this.elements }" class="tag-history-element">
<tag-history-element each="{ entry in guiElement }" if="{ entry.value && entry.value.length > 0}"/>
</material-card>
<script type="text/javascript">
const self = this;
const eltIdx = function(e) {
switch (e) {
case 'id': return 1;
case 'created': return 2;
case 'created_by': return 3;
case 'size': return 4;
case 'os': return 5;
case 'architecture': return 6;
case 'linux': return 7;
case 'docker_version': return 8;
default: return 10;
}
};
const eltSort = function(e1, e2) {
return eltIdx(e1.key) - eltIdx(e2.key);
};
const modifySpecificAttributeTypes = function(attribute, value) {
switch (attribute) {
case 'created':
return new Date(value).toLocaleString();
case 'created_by':
const cmd = value.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+)/);
return (cmd && cmd [1]) || 'RUN'
case 'size':
return registryUI.bytesToSize(value);
case 'Entrypoint':
case 'Cmd':
return (value || []).join(' ');
case 'Labels':
return Object.keys(value || {}).map(function(elt) {
return value[elt] ? elt + '=' + value[elt] : '';
});
case 'Volumes':
case 'ExposedPorts':
return Object.keys(value);
}
return value || '';
};
const getConfig = function(blobs) {
const res = ['architecture', 'User', 'created', 'docker_version', 'os', 'Cmd', 'Entrypoint', 'Env', 'Labels', 'User', 'Volumes', 'WorkingDir', 'author', 'id', 'ExposedPorts'].reduce(function(acc, e) {
const value = blobs[e] || blobs.config[e];
if (value) {
acc[e] = value;
}
return acc;
}, {});
if (!res.author && (res.Labels && res.Labels.maintainer)) {
res.author = blobs.config.Labels.maintainer;
delete res.Labels.maintainer;
}
return res;
};
const processBlobs = function(blobs) {
function exec(elt) {
const guiElements = [];
for (const attribute in elt) {
if (elt.hasOwnProperty(attribute) && attribute != 'empty_layer') {
const value = elt[attribute];
const guiElement = {
"key": attribute,
"value": modifySpecificAttributeTypes(attribute, value)
};
guiElements.push(guiElement);
}
}
return guiElements.sort(eltSort);
}
self.elements.push(exec(getConfig(blobs)));
blobs.history.reverse().forEach(function(elt) { self.elements.push(exec(elt)) });
registryUI.taghistory.loadend = true;
self.update();
};
registryUI.taghistory.display = function() {
self.elements = []
const blobs = registryUI.taghistory._image && registryUI.taghistory._image.blobs;
if (blobs) {
return processBlobs(blobs)
}
const image = new registryUI.DockerImage(registryUI.taghistory.image, registryUI.taghistory.tag);
image.fillInfo()
image.on('blobs', processBlobs);
};
registryUI.taghistory.display();
self.update();
</script>
</tag-history>

View File

@@ -1,18 +1,18 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016 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 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.
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/>.
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 -->
@@ -28,53 +28,37 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</div>
<table show="{ registryUI.taglist.loadend }" style="border: none;">
<thead>
<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 class="show-tag-history">History</th>
<th class="remove-tag" show="{ registryUI.isImageRemoveActivated }"></th>
</tr>
<tr>
<th class="material-card-th-left">Repository</th>
<th></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>
</tr>
</thead>
<tbody>
<tr each="{ image in registryUI.taglist.tags }">
<td class="material-card-th-left">{ image.name }</td>
<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 class="show-tag-history">
<tag-history-button image={ image }/>
</td>
<td show="{ registryUI.isImageRemoveActivated }">
<remove-image image={ image }/>
</td>
</tr>
<tr each="{ image in registryUI.taglist.tags }">
<td class="material-card-th-left">{ image.name }</td>
<td class="copy-to-clipboard">
<copy-to-clipboard image={ image }/>
</td>
<td><image-size image="{ image }" /></td>
<td><image-tag image="{ image }" /></td>
<td show="{ registryUI.isImageRemoveActivated }">
<remove-image image={ image }/>
</td>
</tr>
</tbody>
</table>
</material-card>
<script>
registryUI.taglist.instance = this;
registryUI.taglist.display = function() {
registryUI.taglist.display = function () {
registryUI.taglist.tags = [];
if (route.routeName == 'taglist') {
const oReq = new Http();
var oReq = new Http();
registryUI.taglist.instance.update();
oReq.addEventListener('load', function() {
oReq.addEventListener('load', function () {
registryUI.taglist.tags = [];
if (this.status == 200) {
registryUI.taglist.tags = JSON.parse(this.responseText).tags || [];
@@ -87,11 +71,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
registryUI.snackbar(this.responseText, true);
}
});
oReq.addEventListener('error', function() {
oReq.addEventListener('error', function () {
registryUI.snackbar(this.getErrorMessage(), true);
registryUI.taglist.tags = [];
registryUI.taglist.tags = [];
});
oReq.addEventListener('loadend', function() {
oReq.addEventListener('loadend', function () {
registryUI.taglist.loadend = true;
registryUI.taglist.instance.update();
});
@@ -103,7 +87,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
registryUI.taglist.display();
registryUI.taglist.instance.update();
registryUI.taglist.reverse = function() {
registryUI.taglist.reverse = function () {
if (registryUI.taglist.asc) {
registryUI.taglist.tags.reverse();
registryUI.taglist.asc = false;
@@ -113,7 +97,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
}
registryUI.taglist.instance.update();
};
registryUI.taglist.refresh = function() {
registryUI.taglist.refresh = function () {
route(registryUI.taglist.name);
};
</script>