From 7150fe3245a471a87456b83f593b079d74fe1e55 Mon Sep 17 00:00:00 2001 From: Joxit Date: Thu, 14 Jul 2016 23:50:43 +0200 Subject: [PATCH 1/6] [delete image] Add request for deleting a manifest/image. Add setRequestHeader in Http class. --- src/scripts/http.js | 9 +++++++++ src/tags/taglist.tag | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/scripts/http.js b/src/scripts/http.js index 0f485ac..3f0ecb1 100644 --- a/src/scripts/http.js +++ b/src/scripts/http.js @@ -17,6 +17,7 @@ function Http() { this.oReq = new XMLHttpRequest(); this._events = {}; + this._headers = {}; } Http.prototype.addEventListener = function(e, f) { @@ -31,6 +32,9 @@ Http.prototype.addEventListener = function(e, f) { for (key in this.http._events) { req.addEventListener(key, this.http._events[key]); } + for (key in this.http._headers) { + req.setRequestHeader(key, this.http._headers[key]); + } req.withCredentials = true; req.open(this.http._method, this.http._url); req.send(); @@ -50,6 +54,11 @@ Http.prototype.addEventListener = function(e, f) { } }; +Http.prototype.setRequestHeader = function(header, value) { + this.oReq.setRequestHeader(header, value); + this._headers[header] = value; +}; + Http.prototype.open = function(m, u) { this._method = m; this._url = u; diff --git a/src/tags/taglist.tag b/src/tags/taglist.tag index 59801cb..6c93af4 100644 --- a/src/tags/taglist.tag +++ b/src/tags/taglist.tag @@ -36,6 +36,11 @@ { registryUI.taglist.name } { item } + + + delete + + @@ -110,6 +115,35 @@ registryUI.taglist.back = function () { rg.router.go('home'); }; + registryUI.taglist.remove = function (name, tag) { + var oReq = new Http(); + oReq.addEventListener('load', function () { + if (this.status == 200) { + var digest = JSON.parse(this.responseText).config.digest; + var oReq = new Http(); + oReq.addEventListener('load', function () { + if (this.status == 200) { + registryUI.taglist.instance.update(); + } else if (this.status == 404) { + registryUI.taglist.createSnackbar('Server not found'); + } else { + registryUI.taglist.createSnackbar(this.responseText); + } + }); + oReq.open('DELETE', registryUI.url() + '/v2/' + name + '/manifests/' + digest); + oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json'); + oReq.send(); + } else if (this.status == 404) { + registryUI.taglist.createSnackbar('Server not found'); + } else { + registryUI.taglist.createSnackbar(this.responseText); + } + }); + console.log(name, tag); + oReq.open('GET', registryUI.url() + '/v2/' + name + '/manifests/' + tag); + oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json'); + oReq.send(); + }; \ No newline at end of file From bc215978accd417f583ca0bc7bed927634d75e5a Mon Sep 17 00:00:00 2001 From: Joxit Date: Sat, 1 Oct 2016 23:27:10 +0200 Subject: [PATCH 2/6] [delete image] Fix docker remove image, need HEAD request and not a GET for digest. --- src/tags/taglist.tag | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/tags/taglist.tag b/src/tags/taglist.tag index 6c93af4..110a063 100644 --- a/src/tags/taglist.tag +++ b/src/tags/taglist.tag @@ -119,13 +119,14 @@ var oReq = new Http(); oReq.addEventListener('load', function () { if (this.status == 200) { - var digest = JSON.parse(this.responseText).config.digest; + var digest = this.getResponseHeader('Docker-Content-Digest'); var oReq = new Http(); oReq.addEventListener('load', function () { - if (this.status == 200) { - registryUI.taglist.instance.update(); + if (this.status == 200 || this.status == 202) { + registryUI.taglist.createSnackbar('Deleting '+ name+ ':' + tag + ' image. Run `registry garbage-collect config.yml` on your registry'); + rg.router.go(rg.router.current.name, rg.router.current.params); } else if (this.status == 404) { - registryUI.taglist.createSnackbar('Server not found'); + registryUI.taglist.createSnackbar('Digest not found'); } else { registryUI.taglist.createSnackbar(this.responseText); } @@ -134,13 +135,12 @@ oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json'); oReq.send(); } else if (this.status == 404) { - registryUI.taglist.createSnackbar('Server not found'); + registryUI.taglist.createSnackbar('Manifest for' + name + ':' + tag + 'not found'); } else { registryUI.taglist.createSnackbar(this.responseText); } }); - console.log(name, tag); - oReq.open('GET', registryUI.url() + '/v2/' + name + '/manifests/' + tag); + oReq.open('HEAD', registryUI.url() + '/v2/' + name + '/manifests/' + tag); oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json'); oReq.send(); }; From 6180c206b488a6b6e1c9612689829c1b2b9980a9 Mon Sep 17 00:00:00 2001 From: Joxit Date: Sun, 2 Oct 2016 00:36:04 +0200 Subject: [PATCH 3/6] [delete image] Update readme for delete feature --- README.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 09c39af..e58ce35 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Docker Registry UI ## Overview + This project aims to provide a user interface for your private docker registry v2. There is no default registry on this UI, you should add your own with the UI. You can manage more than one registry server. @@ -12,13 +13,14 @@ This web user interface use [Riot](https://github.com/Riot/riot) the react-like ## Features - * List all your repositories/images. - * List all tags for a repository/image - * Sort the tag list - * One interface for many registry - * Use a secured docker registry +- List all your repositories/images. +- List all tags for a repository/image +- Sort the tag list +- One interface for many registry +- Use a secured docker registry ## Getting Started + ### Basic First you need node and npm in order to download dependencies. @@ -30,34 +32,43 @@ npm install ``` Now you can open index.html with your browser or use a http-server + ```sh npm install -g http-server http-server ``` + ### Docker + The docker contains the source code and a node webserver in order to serve the docker-registry-ui. #### Get the docker image + You can get the image in three ways From sources with this command : + ```sh git clone https://github.com/Joxit/docker-registry-ui.git docker build -t joxit/docker-registry-ui docker-registry-ui ``` Or build with the url : - ```sh + +```sh docker build -t joxit/docker-registry-ui github.com/Joxit/docker-registry-ui ``` Or pull the image from [docker hub](https://hub.docker.com/r/joxit/docker-registry-ui/) : + ```sh docker pull joxit/docker-registry-ui ``` #### Run the docker + To run the docker and see the website on your 8080 port, try this : + ```sh docker run -d -p 8080:8080 joxit/docker-registry-ui ``` @@ -67,13 +78,66 @@ docker run -d -p 8080:8080 joxit/docker-registry-ui Your server should be configured to accept CORS. If your docker registry does not need credentials, you will need to send this HEADER : -``` -Access-Control-Allow-Origin: '*' -``` + + Access-Control-Allow-Origin: '*' If your docker registry need credentials, you will need to send these HEADERS : + +```yml +http: + headers: + Access-Control-Allow-Origin: '' + Access-Control-Allow-Credentials: true + Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS'] # Optional ``` -Access-Control-Allow-Origin: '' -Access-Control-Allow-Credentials: true -Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS'] # Optional + +## Using delete + +For deleting images, you need to activate the delete feature in your registry : + +```yml +storage: + delete: + enabled: true +``` + +And you need to add these HEADERS : + +```yml +http: + headers: + Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE'] + Access-Control-Expose-Headers: ['Docker-Content-Digest'] +``` + +## Registry example + +Example of docker registry configuration file: + +```yml +version: 0.1 +log: + fields: + service: registry +storage: + delete: + enabled: true + cache: + blobdescriptor: inmemory + filesystem: + rootdirectory: /var/lib/registry +http: + addr: :5000 + headers: + X-Content-Type-Options: [nosniff] + Access-Control-Allow-Origin: ['http://127.0.0.1:8001'] + Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE'] + Access-Control-Allow-Headers: ['Authorization'] + Access-Control-Max-Age: [1728000] + Access-Control-Allow-Credentials: [true] + Access-Control-Expose-Headers: ['Docker-Content-Digest'] +auth: + htpasswd: + realm: basic-realm + path: /etc/docker/registry/htpasswd ``` From dbcd0031d39123a11e263cdb67073ab5425b19d9 Mon Sep 17 00:00:00 2001 From: Joxit Date: Mon, 3 Oct 2016 22:06:04 +0200 Subject: [PATCH 4/6] [delete image] Add some error messages. --- src/tags/taglist.tag | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/tags/taglist.tag b/src/tags/taglist.tag index 110a063..5473a96 100644 --- a/src/tags/taglist.tag +++ b/src/tags/taglist.tag @@ -115,16 +115,23 @@ registryUI.taglist.back = function () { rg.router.go('home'); }; + registryUI.taglist.refresh = function () { + rg.router.go(rg.router.current.name, rg.router.current.params); + } registryUI.taglist.remove = function (name, tag) { var oReq = new Http(); oReq.addEventListener('load', function () { + registryUI.taglist.refresh(); if (this.status == 200) { + if (!this.getAllResponseHeaders().includes('Docker-Content-Digest')) { + registryUI.taglist.createSnackbar('You need tu 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('load', function () { if (this.status == 200 || this.status == 202) { - registryUI.taglist.createSnackbar('Deleting '+ name+ ':' + tag + ' image. Run `registry garbage-collect config.yml` on your registry'); - rg.router.go(rg.router.current.name, rg.router.current.params); + registryUI.taglist.createSnackbar('Deleting ' + name + ':' + tag + ' image. Run `registry garbage-collect config.yml` on your registry'); } else if (this.status == 404) { registryUI.taglist.createSnackbar('Digest not found'); } else { @@ -133,6 +140,9 @@ }); oReq.open('DELETE', registryUI.url() + '/v2/' + name + '/manifests/' + digest); oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json'); + oReq.addEventListener('error', function () { + registryUI.taglist.createSnackbar('An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].'); + }); oReq.send(); } else if (this.status == 404) { registryUI.taglist.createSnackbar('Manifest for' + name + ':' + tag + 'not found'); From c007eea26da6e3cf98961c8a03beb4555c3b2027 Mon Sep 17 00:00:00 2001 From: Joxit Date: Tue, 4 Oct 2016 00:24:01 +0200 Subject: [PATCH 5/6] [delete image] externalize remove part in its own tag. --- gulpfile.js | 2 +- src/index.html | 1 + src/scripts/script.js | 1 + src/scripts/static.js | 3 +- src/tags/remove-image.tag | 63 +++++++++++++++++++++++++++++++++++++++ src/tags/taglist.tag | 43 ++------------------------ 6 files changed, 71 insertions(+), 42 deletions(-) create mode 100644 src/tags/remove-image.tag diff --git a/gulpfile.js b/gulpfile.js index f3b9ce1..35a579b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -49,7 +49,7 @@ gulp.task('riot-tag', ['html'], function() { }); gulp.task('riot-static-tag', ['html'], function() { - return gulp.src(['src/tags/catalog.tag', 'src/tags/app.tag', 'src/tags/taglist.tag']) + return gulp.src(['src/tags/catalog.tag', 'src/tags/app.tag', 'src/tags/taglist.tag', 'src/tags/remove-image.tag']) .pipe(concat('tags-static.js')) .pipe(riot()) .pipe(minifier({}, uglify)) diff --git a/src/index.html b/src/index.html index 1d2d724..4c3808b 100644 --- a/src/index.html +++ b/src/index.html @@ -68,6 +68,7 @@ + diff --git a/src/scripts/script.js b/src/scripts/script.js index 2e6c873..7bef7a1 100644 --- a/src/scripts/script.js +++ b/src/scripts/script.js @@ -60,6 +60,7 @@ registryUI.removeServer = function(url) { registryServer.splice(index, 1); localStorage.setItem('registryServer', JSON.stringify(registryServer)); } +registryUI.isImageRemoveActivated = true; registryUI.catalog = {}; registryUI.taglist = {}; diff --git a/src/scripts/static.js b/src/scripts/static.js index cb9c0af..62ac604 100644 --- a/src/scripts/static.js +++ b/src/scripts/static.js @@ -17,7 +17,8 @@ var registryUI = {} registryUI.url = function() { return '${URL}'; -} +}; +registryUI.isImageRemoveActivated = true; registryUI.catalog = {}; registryUI.taglist = {}; diff --git a/src/tags/remove-image.tag b/src/tags/remove-image.tag new file mode 100644 index 0000000..c326277 --- /dev/null +++ b/src/tags/remove-image.tag @@ -0,0 +1,63 @@ + + + + delete + + + diff --git a/src/tags/taglist.tag b/src/tags/taglist.tag index 5473a96..0a3513a 100644 --- a/src/tags/taglist.tag +++ b/src/tags/taglist.tag @@ -30,16 +30,15 @@ Repository Tag + { registryUI.taglist.name } { item } - - - delete - + + @@ -117,42 +116,6 @@ }; registryUI.taglist.refresh = function () { rg.router.go(rg.router.current.name, rg.router.current.params); - } - registryUI.taglist.remove = function (name, tag) { - var oReq = new Http(); - oReq.addEventListener('load', function () { - registryUI.taglist.refresh(); - if (this.status == 200) { - if (!this.getAllResponseHeaders().includes('Docker-Content-Digest')) { - registryUI.taglist.createSnackbar('You need tu 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('load', function () { - if (this.status == 200 || this.status == 202) { - registryUI.taglist.createSnackbar('Deleting ' + name + ':' + tag + ' image. Run `registry garbage-collect config.yml` on your registry'); - } else if (this.status == 404) { - registryUI.taglist.createSnackbar('Digest not found'); - } else { - registryUI.taglist.createSnackbar(this.responseText); - } - }); - oReq.open('DELETE', registryUI.url() + '/v2/' + name + '/manifests/' + digest); - oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json'); - oReq.addEventListener('error', function () { - registryUI.taglist.createSnackbar('An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].'); - }); - oReq.send(); - } else if (this.status == 404) { - registryUI.taglist.createSnackbar('Manifest for' + name + ':' + tag + 'not found'); - } else { - registryUI.taglist.createSnackbar(this.responseText); - } - }); - oReq.open('HEAD', registryUI.url() + '/v2/' + name + '/manifests/' + tag); - oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json'); - oReq.send(); }; From d1d986448cd1da6651bfd018a2e9503ff737f7c1 Mon Sep 17 00:00:00 2001 From: Joxit Date: Wed, 5 Oct 2016 00:31:49 +0200 Subject: [PATCH 6/6] [delete image] can activate or not this feature for static image and update README --- README.md | 18 ++++++++++++++++-- bin/entrypoint | 4 ++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e58ce35..1428944 100644 --- a/README.md +++ b/README.md @@ -51,26 +51,40 @@ From sources with this command : ```sh git clone https://github.com/Joxit/docker-registry-ui.git docker build -t joxit/docker-registry-ui docker-registry-ui +docker build -t joxit/docker-registry-ui -f docker-registry-ui/Dockerfile.static docker-registry-ui ``` Or build with the url : ```sh docker build -t joxit/docker-registry-ui github.com/Joxit/docker-registry-ui +docker build -t joxit/docker-registry-ui -f Dockerfile.static github.com/Joxit/docker-registry-ui ``` Or pull the image from [docker hub](https://hub.docker.com/r/joxit/docker-registry-ui/) : ```sh docker pull joxit/docker-registry-ui +docker pull joxit/docker-registry-ui:static ``` #### Run the docker -To run the docker and see the website on your 8080 port, try this : +To run the docker and see the website on your 80 port, try this : ```sh -docker run -d -p 8080:8080 joxit/docker-registry-ui +docker run -d -p 80:80 joxit/docker-registry-ui +``` + +#### Run the static docker + +Some env options are available for use this interface for only one server. + +- `URL`: set the static URL to use. (`Required`) +- `DELETE_IMAGES`: if this variable is empty or `false`, delete feature is desactivated. It is activated otherwise. + +```sh +docker run -d -p 80:80 -e URL=http://127.0.0.1:5000 -e DELETE_IMAGES=true joxit/docker-registry-ui:static ``` ## Using CORS diff --git a/bin/entrypoint b/bin/entrypoint index 17f7fe6..27fc379 100755 --- a/bin/entrypoint +++ b/bin/entrypoint @@ -2,6 +2,10 @@ $@ sed -i "s,\${URL},${URL}," scripts/script.js +if [ -z "${DELETE_IMAGES}" ] || [ "${DELETE_IMAGES}" = false ] ; then + sed -i "s/registryUI.isImageRemoveActivated *= *[^,;]*/registryUI.isImageRemoveActivated=false/" scripts/script.js +fi + if [ -z "$@" ]; then nginx -g "daemon off;" else