Compare commits

...

47 Commits
0.5.0 ... 0.6.0

Author SHA1 Message Date
Joxit
1e185b4034 Release v0.6.0 new history page 2018-12-20 00:01:36 +01:00
Jones Magloire
03e4d6b8c5 Merge pull request #61 from Joxit/fix/image-history
[Fix #59] image history, use v2 manifest instead of v1
2018-12-19 21:32:41 +01:00
Joxit
fe2fcc1104 [fix-image-history] Add some query optimization 2018-12-18 23:25:57 +01:00
Joxit
581975b99e [fix-image-history] Create move history-elements in its own tag tag-history-element
- Add id and ExposedPort
- Better rendering for arrays
- Add more const instead of var
2018-12-17 00:38:25 +01:00
Joxit
ed1e928bf3 [fix-image-history] Add config values for top level history and improve element values rendering
Move icons to js mapping function instead of CSS
In dockerfile, author cand be randered through `MAINTAINER xxx` or `LABEL maintainer=xxx`
2018-12-16 00:06:35 +01:00
Joxit
ffd0a7c628 [fix-image-history] Move byteToSize in app.tag and use it in image history. Better render for created_by 2018-12-14 21:57:42 +01:00
Joxit
cb50dd42d8 [fix-image-history] Sort elements attributes (os, id, created...) 2018-12-13 20:05:28 +01:00
Joxit
5aaedfb0aa [fix-image-history] Move from v1 manifest to manifest v2 2018-12-12 22:41:20 +01:00
Joxit
7cb06d57ee [fix-image-history] Add blobs in DockerImage image, this will superseds current tag-history call 2018-12-11 23:09:22 +01:00
Joxit
a6b6c1531b [docker svg] Change docker logo for a simplified one
Size changed from 12.5Kio to 4.5Kio
2018-12-09 17:34:57 +01:00
Jones Magloire
e89a0112ae Merge pull request #58 from lennartblom/master
New Feature: Overview of tag history content
2018-12-08 15:49:55 +01:00
Joxit
4aa016090c Add more const 2018-12-07 19:46:08 +01:00
Lennart Blom
7163150cf5 Optimize GUI elements and HTTP logic for content preperation 2018-12-06 22:01:35 +01:00
Lennart Blom
c176a082d9 Change wording of headline 2018-12-06 21:13:13 +01:00
Lennart Blom
67ad46a851 Change wording of headline 2018-12-06 21:12:55 +01:00
Lennart Blom
06a11a706b Merge remote-tracking branch 'origin/master' 2018-12-06 21:10:51 +01:00
Lennart Blom
04259ab43d Change wording of headline 2018-12-06 21:10:12 +01:00
Jones Magloire
896031e894 Merge pull request #44 from onizet/master
Improve documentation: traefik sample
2018-12-05 21:46:08 +01:00
Joxit
8643fb16ff Add contributors section 2018-12-03 20:26:41 +01:00
Olivier Nizet
becf8bf887 Move documentation about traefik as a full runnable example 2018-12-03 20:12:33 +01:00
Lennart Blom
472b485455 Remove test logging 2018-12-03 16:48:21 +01:00
Olivier Nizet
637f7635dc Example behind Traefik 2018-12-03 16:45:07 +01:00
Lennart Blom
ae78b2d355 Fix navigation bug
The href="#" was troubling the view that the history appeared just for
a few milliseconds... the button with given onclick event does work now.
2018-12-03 16:36:51 +01:00
Lennart Blom
249d021152 Formate code 2018-12-02 19:15:27 +01:00
Lennart Blom
e3437daefe Use toLocaleString() instead of custom date format 2018-12-02 18:58:04 +01:00
Lennart Blom
246369fdec Move go function to taghistory-namespace within app.tag 2018-12-02 18:56:34 +01:00
Lennart Blom
b94a65d79b Remove space in front of function brackets 2018-12-02 18:52:34 +01:00
Lennart Blom
bf975cd29b Formate code 2018-12-01 21:29:40 +01:00
Lennart Blom
292336c1b6 Format code 2018-12-01 21:23:48 +01:00
Lennart Blom
6d849a9e95 Add information text about the history elements 2018-12-01 21:11:09 +01:00
Lennart Blom
86f78f7604 Remove instance usage of registryUI.taghistory 2018-12-01 21:00:27 +01:00
Lennart Blom
43af3ffdcf Remove logging 2018-12-01 20:52:53 +01:00
Lennart Blom
f364564d0e Remove logging 2018-12-01 20:51:55 +01:00
Lennart Blom
39cf28e562 Remove logging and format code 2018-12-01 20:50:55 +01:00
Lennart Blom
66fe613b6f Optimize format 2018-12-01 20:50:16 +01:00
Lennart Blom
cf883cbfc7 Optimize routing and code format 2018-12-01 20:49:59 +01:00
Lennart Blom
5bb7dfce7d Optimize date format 2018-12-01 20:32:33 +01:00
Lennart Blom
6d798ca75f Styling of history element headlines 2018-12-01 20:19:07 +01:00
Lennart Blom
f6bc4df11f Styling elements and getting structure into data view 2018-12-01 11:44:51 +01:00
Lennart Blom
8ab6ecbf19 Define dynamic parsing of history elements 2018-12-01 11:03:23 +01:00
Lennart Blom
c857bd8db6 Add functionality for new tag history view 2018-12-01 00:24:58 +01:00
Lennart Blom
fe5e962488 Format code 2018-12-01 00:24:28 +01:00
Lennart Blom
96a926652b Include tag-history-button 2018-12-01 00:23:38 +01:00
Lennart Blom
3f860cd0b5 Define taghistory object 2018-12-01 00:23:14 +01:00
Lennart Blom
3b1b6f2e72 Add new "tag-history" column for tag overview 2018-11-30 22:33:34 +01:00
Lennart Blom
20861bbb0d Add .idea directory to .ignore 2018-11-30 22:10:05 +01:00
Joxit
09b77201be [Fixes #52] image-date tag was missing in static version => v0.5.1 2018-11-23 22:22:17 +01:00
45 changed files with 708 additions and 240 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
node_modules
package-lock.json
registry-data
.idea

View File

@@ -28,6 +28,7 @@ This web user interface uses [Riot](https://github.com/Riot/riot) the react-like
- 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 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
dist/images/docker-logo.svg vendored Normal file
View File

@@ -0,0 +1 @@
<?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>

After

Width:  |  Height:  |  Size: 4.2 KiB

2
dist/index.html vendored
View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

@@ -0,0 +1,22 @@
# 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

View File

@@ -0,0 +1,33 @@
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'}}

42
examples/traefik/run-swarm.sh Executable file
View File

@@ -0,0 +1,42 @@
#!/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

@@ -16,22 +16,14 @@ const useref = require('gulp-useref');
const injectVersion = require('gulp-inject-version');
const merge = require('stream-series');
const allTags = 'src/tags/*.tag';
const allTags = ['src/tags/*.tag', 'src/tags/dialogs/*.tag'];
const allScripts = [
'src/scripts/http.js',
'src/scripts/script.js'
];
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 staticTags = ['src/tags/*.tag'];
const staticScripts = [
'src/scripts/http.js',
@@ -114,4 +106,16 @@ function fonts() {
.pipe(gulp.dest('dist/fonts'));
};
exports.build = series(clean, html, parallel(fonts, styles, vendor, app, appStatic));
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/'));
};
exports.build = series(clean, html, parallel(fonts, styles, vendor, app, appStatic, svgs));

View File

@@ -1,6 +1,6 @@
{
"name": "docker-registry-ui",
"version": "0.5.0",
"version": "0.6.0",
"scripts": {
"build": "./node_modules/gulp/bin/gulp.js build"
},
@@ -15,17 +15,17 @@
"devDependencies": {
"del": "^3.0.0",
"gulp": "^4.0",
"gulp-clean-css": "^3.10.0",
"gulp-clean-css": "^4.0.0",
"gulp-concat": "^2.6.0",
"gulp-filter": "^5.1.0",
"gulp-htmlmin": "^3.0.0",
"gulp-htmlmin": "^5.0.1",
"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",
"riot": "^3.13.2",
"riot-mui": "^0.1.1",
"riot-route": "^3.1.4",
"stream-series": "^0.1.1",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,37 @@
<?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>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016 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
@@ -39,14 +39,17 @@
<!-- 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/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/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/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>

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 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
@@ -20,7 +20,7 @@ registryUI.URL_PARAM_REGEX = /^url=/;
registryUI.name = registryUI.url = function(byPassQueryParam) {
if (!registryUI._url) {
var url = registryUI.getUrlQueryParam();
const 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 {
var res = JSON.parse(localStorage.getItem('registryServer'));
const 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) {
var registryServer = registryUI.getRegistryServer();
const registryServer = registryUI.getRegistryServer();
url = url.trim().replace(/\/*$/, '');
var index = registryServer.indexOf(url);
const 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(/\/*$/, '');
var index = registryServer.indexOf(url);
const 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) {
var registryServer = registryUI.getRegistryServer();
const registryServer = registryUI.getRegistryServer();
url = url.trim().replace(/\/*$/, '');
var index = registryServer.indexOf(url);
const index = registryServer.indexOf(url);
if (index == -1) {
return;
}
@@ -90,9 +90,9 @@ registryUI.updateHistory = function(url) {
}
registryUI.getUrlQueryParam = function () {
var search = window.location.search;
const search = window.location.search;
if (registryUI.URL_QUERY_PARAM_REGEX.test(search)) {
var param = search.split(/^\?|&/).find(function(param) {
const param = search.split(/^\?|&/).find(function(param) {
return param && registryUI.URL_PARAM_REGEX.test(param);
});
return param ? param.replace(registryUI.URL_PARAM_REGEX, '') : param;
@@ -110,6 +110,7 @@ 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 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
@@ -24,6 +24,7 @@ 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,6 +194,7 @@ 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;
}
@@ -322,6 +323,58 @@ 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,18 +1,18 @@
<!--
Copyright (C) 2016 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
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,6 +24,7 @@
<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>
@@ -31,7 +32,8 @@
</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>
@@ -44,61 +46,74 @@
</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.taglist.display();
}
registryUI.appTag.update();
});
route('/taghistory/image/*/tag/*', function(image, tag) {
route.routeName = 'taghistory';
registryUI.taghistory.image = image;
registryUI.taghistory.tag = tag;
if (registryUI.taghistory.display) {
registryUI.taghistory.loadend = false;
}
registryUI.appTag.update();
});
registryUI.home = function() {
if(route.routeName == 'home') {
registryUI.catalog.display();
if (route.routeName == 'home') {
registryUI.catalog.display;
} else {
route('');
}
};
registryUI.snackbar = function (message, isError) {
registryUI.taghistory.go = function(image, tag) {
route('/taghistory/image/' + image + '/tag/' + tag);
};
registryUI.snackbar = function(message, isError) {
registryUI.appTag.tags['material-snackbar'].addToast({'message': message, 'isError': isError}, 15000);
};
registryUI.errorSnackbar = function (message) {
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;
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);
@@ -122,14 +137,14 @@
});
};
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, []);
@@ -156,13 +171,14 @@
this._fillInfoWaiting = true;
const oReq = new Http();
const self = this;
oReq.addEventListener('loadend', function () {
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) {
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)
@@ -175,16 +191,25 @@
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 () {
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 {
@@ -195,6 +220,17 @@
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 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
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 @@
<script>
registryUI.catalog.instance = this;
registryUI.catalog.display = function () {
registryUI.catalog.display = function() {
registryUI.catalog.repositories = [];
var oReq = new Http();
oReq.addEventListener('load', function () {
const 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 @@
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-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 () {
var copyText = this.refs['input'];
const copyText = this.refs['input'];
copyText.style.display = 'block';
copyText.select();
document.execCommand('copy');

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,61 @@
<!--
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,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
@@ -17,7 +17,7 @@
<image-date>
<div title="Creation date { this.localDate }">{ this.dateFormat(this.date) } ago</div>
<script type="text/javascript">
var self = this;
const self = this;
this.dateFormat = function(date) {
if (date === undefined) {
return '';
@@ -32,7 +32,7 @@
return Math.floor(diff / maxSeconds[i]) + ' ' + labels[i * 2 + 1];
}
}
}
};
opts.image.on('creation-date', function(date) {
self.date = date;
self.localDate = date.toLocaleString()

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,19 +15,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<image-size>
<div title="Compressed size of your image.">{ this.bytesToSize(this.size) }</div>
<div title="Compressed size of your image.">{ registryUI.bytesToSize(this.size) }</div>
<script type="text/javascript">
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];
};
const self = this;
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">
var self = this;
const self = this;
opts.image.on('sha256', function(sha256) {
self.sha256 = sha256.substring(0, 19);
self.update();

View File

@@ -1,38 +1,39 @@
<!--
Copyright (C) 2016 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
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) {
var oReq = new Http();
oReq.addEventListener('loadend', function () {
registryUI.removeImage.remove = function(name, tag) {
const 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;
}
var digest = this.getResponseHeader('Docker-Content-Digest');
var oReq = new Http();
oReq.addEventListener('loadend', function () {
const digest = this.getResponseHeader('Docker-Content-Digest');
const 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');
@@ -44,7 +45,7 @@
});
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();

View File

@@ -1,59 +0,0 @@
<!--
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

@@ -0,0 +1,31 @@
<!--
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

@@ -0,0 +1,64 @@
<!--
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>

131
src/tags/tag-history.tag Normal file
View File

@@ -0,0 +1,131 @@
<!--
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 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
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,39 +28,53 @@
</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 show="{ registryUI.isImageRemoveActivated }"></th>
</tr>
<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>
</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 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-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>
</tbody>
</table>
</material-card>
<script>
registryUI.taglist.instance = this;
registryUI.taglist.display = function () {
registryUI.taglist.display = function() {
registryUI.taglist.tags = [];
if (route.routeName == 'taglist') {
var oReq = new Http();
const 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 || [];
@@ -73,11 +87,11 @@
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();
});
@@ -89,7 +103,7 @@
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;
@@ -99,7 +113,7 @@
}
registryUI.taglist.instance.update();
};
registryUI.taglist.refresh = function () {
registryUI.taglist.refresh = function() {
route(registryUI.taglist.name);
};
</script>