Compare commits

...

22 Commits
0.3.1 ... 0.3.8

Author SHA1 Message Date
Joxit
3b5f5201a6 Upgrade to v0.3.8: Fix for REGISTRY_URL for #42 2018-06-20 22:17:35 +02:00
Joxit
cc624754b5 Upgrade to v0.3.7: Add copy to clipboard for images with tag and add sha256
Fixes: #42
2018-06-19 21:42:20 +02:00
Jones Magloire
b78fd358d7 Merge pull request #43 from Joxit/feat/copy-to-clipboard
[feat #42] Add copy to clipboard for images with tag and add sha256
2018-06-18 22:33:49 +02:00
Joxit
3430878e7d [feat #42] Add sha256 for images tag 2018-06-17 22:48:04 +02:00
Joxit
354d3159bd [feat #42] Add copy to clipboard for images with tag 2018-06-15 21:31:48 +02:00
Jones Magloire
86c46deb7e Merge pull request #41 from onizet/master
Fix '403 Request Entity Too Large'
2018-06-13 15:15:05 +02:00
Olivier Nizet
ace12d0ac8 Update with recommended values
- Disable client_max_body_size to avoid the error '403 Request Entity Too Large' which prevent uploading "big" images.
- Apply recommended settings by Docker (https://docs.docker.com/registry/recipes/nginx)
2018-06-13 13:23:09 +02:00
Joxit
4d1f47e808 Fix: #39 use _category?n=100000 for registries with many images 2018-06-09 00:09:32 +02:00
Jones Magloire
642afd695d Merge pull request #37 from Joxit/http-on-https-message
[feat #35] Better error message for http registries on https sites
2018-05-13 00:06:40 +02:00
Joxit
b5e07ea14f [feat #35] Better error message for http registries on https sites 2018-05-10 23:36:39 +02:00
Joxit
ad01f3ece9 Add arm32v7 support 2018-04-05 00:08:34 +02:00
Jones Magloire
4cae0dec8a Update README.md
Add example with `REGISTRY_TITLE`
2018-03-23 09:54:37 +01:00
Joxit
d5451f37c6 Add debian image version (joxit/docker-registry-ui:debian) 2018-03-18 23:58:36 +01:00
Joxit
7ebab2f71c Update version to 0.3.4 2018-03-16 23:46:15 +01:00
Jones Magloire
480cde5750 Merge pull request #32 from Joxit/feat/add-title-docker-static
[feat #28] Add Title when using `REGISTRY_URL` …
2018-03-12 00:07:29 +01:00
Joxit
d7af9f1822 Update version to 0.3.3 2018-03-04 00:27:00 +01:00
Jones Magloire
6f5a5ff756 Merge pull request #31 from Joxit/feat/display-image-size
[feat #30] Display image size
2018-03-04 00:11:51 +01:00
Joxit
b98b0c383d [feat #28] Add Title when using REGISTRY_URL (will be REGISTRY_TITLE) 2018-03-02 23:20:11 +01:00
Joxit
23a57e6c34 [feat #30] Display image size 2018-02-27 20:03:16 +01:00
Jones Magloire
e41d6cfb85 Merge pull request #27 from syndbg/patch-2
Fix `REGISTRY_URL` not working
2018-02-18 17:28:04 +01:00
Anton Antonov
c563f7dd4c Fix REGISTRY_URL not working
Tested with nginx version `1.13.8` and  docker image `joxit/docker-registry-ui:static`
2018-02-14 15:47:22 +02:00
Joxit
bf3e3c9fa8 Add REGISTRY_URL option for #25, this will avoid CORS errors 2018-01-25 18:39:47 +01:00
32 changed files with 478 additions and 96 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
*
!dist
!bin
!nginx

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.project
node_modules
package-lock.json

View File

@@ -1,4 +1,4 @@
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
FROM nginx:alpine
MAINTAINER Jones MAGLOIRE @Joxit
LABEL maintainer="Jones MAGLOIRE @Joxit"
WORKDIR /usr/share/nginx/html/

View File

@@ -1,26 +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/>.
FROM nginx:alpine
MAINTAINER Jones MAGLOIRE @Joxit
WORKDIR /usr/share/nginx/html/
COPY dist/ /usr/share/nginx/html/
COPY dist/scripts/script-static.js /usr/share/nginx/html/scripts/script.js
COPY dist/scripts/tags-static.js /usr/share/nginx/html/scripts/tags.js
COPY bin/entrypoint /bin
ENTRYPOINT entrypoint

1
Dockerfile.static Symbolic link
View File

@@ -0,0 +1 @@
static.dockerfile

View File

@@ -21,6 +21,12 @@ This web user interface uses [Riot](https://github.com/Riot/riot) the react-like
- One interface for many registries
- Use a secured docker registry
- Share your docker registry with query parameter `url` (e.g. `https://joxit.github.io/docker-registry-ui/demo?url=https://registry.example.com`)
- Use `joxit/docker-registry-ui:static` as reverse proxy to your docker registry (This will avoid CORS).
- Display image size (see #30)
- Add Title when using REGISTRY_URL (see #28)
- Alpine and Debian based images with supports for arm32v7
- Copy `docker pull` command to clipbloard
- Show sha256 for specific tag (hover image tag)
## Getting Started
@@ -53,22 +59,34 @@ 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
# Alpine
docker build -t joxit/docker-registry-ui:latest docker-registry-ui
docker build -t joxit/docker-registry-ui:static -f docker-registry-ui/static.dockerfile docker-registry-ui
# Debian
docker build -t joxit/docker-registry-ui:debian -f docker-registry-ui/debian.dockerfile docker-registry-ui
docker build -t joxit/docker-registry-ui:static -f docker-registry-ui/debian-static.dockerfile 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
# Alpine
docker build -t joxit/docker-registry-ui:latest github.com/Joxit/docker-registry-ui
docker build -t joxit/docker-registry-ui:static -f static.dockerfile github.com/Joxit/docker-registry-ui
# Debian
docker build -t joxit/docker-registry-ui:debian -f debian.dockerfile github.com/Joxit/docker-registry-ui
docker build -t joxit/docker-registry-ui:debian-static -f debian-static.dockerfile 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
# Alpine
docker pull joxit/docker-registry-ui:latest
docker pull joxit/docker-registry-ui:static
# Debian
docker pull joxit/docker-registry-ui:debian
docker pull joxit/docker-registry-ui:debian-static
```
#### Run the docker
@@ -83,13 +101,27 @@ docker run -d -p 80:80 joxit/docker-registry-ui
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.
- `URL`: set the static URL to use (You will need CORS configuration). Example: `http://127.0.0.1:5000`. (`Required`)
- `REGISTRY_URL`: your docker registry URL to contact (CORS configuration is not needed). Example: `http://my-docker-container:5000`. (Can't be used with `URL`, since 0.3.2).
- `DELETE_IMAGES`: if this variable is empty or `false`, delete feature is deactivated. It is activated otherwise.
- `REGISTRY_TITLE`: Set a custom title for your user interface when using `REGISTRY_URL` (since 0.3.4)
Example with `URL` option.
```sh
docker run -d -p 80:80 -e URL=http://127.0.0.1:5000 -e DELETE_IMAGES=true joxit/docker-registry-ui:static
```
Example with `REGISTRY_URL`, this will add a proxy to your registry.
Your registry will be accessible here : `http://127.0.0.1/v2`, this will avoid CORS errors (see #25).
Be careful, `joxit/docker-registry-ui` and `registry:2` will communicate, both containers should be in the same network or use your private IP.
```sh
docker network create registry-ui-net
docker run -d --net registry-ui-net --name registry-srv registry:2
docker run -d --net registry-ui-net -p 80:80 -e REGISTRY_URL=http://registry-srv:5000 -e DELETE_IMAGES=true -e REGISTRY_TITLE="My registry" joxit/docker-registry-ui:static
```
## Using CORS
Your server should be configured to accept CORS.

27
arm32v7-static.dockerfile Normal file
View File

@@ -0,0 +1,27 @@
# 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/>.
FROM arm32v7/nginx
LABEL maintainer="Jones MAGLOIRE @Joxit"
WORKDIR /usr/share/nginx/html/
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
COPY dist/ /usr/share/nginx/html/
COPY dist/scripts/script-static.js /usr/share/nginx/html/scripts/script.js
COPY dist/scripts/tags-static.js /usr/share/nginx/html/scripts/tags.js
COPY bin/entrypoint /bin
ENTRYPOINT entrypoint

21
arm32v7.dockerfile Normal file
View File

@@ -0,0 +1,21 @@
# 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/>.
FROM arm32v7/nginx
LABEL maintainer="Jones MAGLOIRE @Joxit"
WORKDIR /usr/share/nginx/html/
COPY dist/ /usr/share/nginx/html/

View File

@@ -1,11 +1,17 @@
#!/bin/sh
$@
sed -i "s,\${URL},${URL}," scripts/script.js
sed -i "s,\${REGISTRY_TITLE},${REGISTRY_TITLE}," scripts/script.js
if [ -z "${DELETE_IMAGES}" ] || [ "${DELETE_IMAGES}" = false ] ; then
sed -i "s/registryUI.isImageRemoveActivated *= *[^,;]*/registryUI.isImageRemoveActivated=false/" scripts/script.js
fi
if [ -n "${REGISTRY_URL}" ] ; then
sed -i "s,\${REGISTRY_URL},${REGISTRY_URL}," /etc/nginx/conf.d/default.conf
sed -i "s,#!,," /etc/nginx/conf.d/default.conf
fi
if [ -z "$@" ]; then
nginx -g "daemon off;"
else

27
debian-static.dockerfile Normal file
View File

@@ -0,0 +1,27 @@
# 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/>.
FROM nginx:latest
LABEL maintainer="Jones MAGLOIRE @Joxit"
WORKDIR /usr/share/nginx/html/
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
COPY dist/ /usr/share/nginx/html/
COPY dist/scripts/script-static.js /usr/share/nginx/html/scripts/script.js
COPY dist/scripts/tags-static.js /usr/share/nginx/html/scripts/tags.js
COPY bin/entrypoint /bin
ENTRYPOINT entrypoint

21
debian.dockerfile Normal file
View File

@@ -0,0 +1,21 @@
# 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/>.
FROM nginx:latest
LABEL maintainer="Jones MAGLOIRE @Joxit"
WORKDIR /usr/share/nginx/html/
COPY dist/ /usr/share/nginx/html/

View File

@@ -1,6 +1,6 @@
/*!
* docker-registry-ui
* 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
@@ -15,4 +15,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/>.
*/
function Http(){this.oReq=new XMLHttpRequest,this.oReq.hasHeader=Http.hasHeader,this._events={},this._headers={}}Http.prototype.addEventListener=function(e,t){this._events[e]=t;var s=this;switch(e){case"loadend":s.oReq.addEventListener("loadend",function(){if(401==this.status){var e=new XMLHttpRequest;e.open(s._method,s._url);for(key in s._events)e.addEventListener(key,s._events[key]);for(key in s._headers)e.setRequestHeader(key,s._headers[key]);e.withCredentials=!0,e.hasHeader=Http.hasHeader,e.send()}else t.bind(this)()});break;case"load":s.oReq.addEventListener("load",function(){401!==this.status&&t.bind(this)()});break;default:s.oReq.addEventListener(e,function(){t.bind(this)()})}},Http.prototype.setRequestHeader=function(e,t){this.oReq.setRequestHeader(e,t),this._headers[e]=t},Http.prototype.open=function(e,t){this._method=e,this._url=t,this.oReq.open(e,t)},Http.prototype.send=function(){this.oReq.send()},Http.hasHeader=function(e){return this.getAllResponseHeaders().split("\n").some(function(t){return new RegExp("^"+e+":","i").test(t)})};var registryUI={};registryUI.url=function(){return"${URL}"},registryUI.isImageRemoveActivated=!0,registryUI.catalog={},registryUI.taglist={},riot.mount("catalog"),riot.mount("taglist"),riot.mount("app");
function Http(){this.oReq=new XMLHttpRequest,this.oReq.hasHeader=Http.hasHeader,this.oReq.getErrorMessage=Http.getErrorMessage,this._events={},this._headers={}}Http.prototype.addEventListener=function(e,t){this._events[e]=t;var r=this;switch(e){case"loadend":r.oReq.addEventListener("loadend",function(){if(401==this.status){var e=new XMLHttpRequest;e.open(r._method,r._url);for(key in r._events)e.addEventListener(key,r._events[key]);for(key in r._headers)e.setRequestHeader(key,r._headers[key]);e.withCredentials=!0,e.hasHeader=Http.hasHeader,e.getErrorMessage=Http.getErrorMessage,e.send()}else t.bind(this)()});break;case"load":r.oReq.addEventListener("load",function(){401!==this.status&&t.bind(this)()});break;default:r.oReq.addEventListener(e,function(){t.bind(this)()})}},Http.prototype.setRequestHeader=function(e,t){this.oReq.setRequestHeader(e,t),this._headers[e]=t},Http.prototype.open=function(e,t){this._method=e,this._url=t,this.oReq.open(e,t)},Http.prototype.send=function(){this.oReq.send()},Http.hasHeader=function(e){return this.getAllResponseHeaders().split("\n").some(function(t){return new RegExp("^"+e+":","i").test(t)})},Http.getErrorMessage=function(){return registryUI.url()&&registryUI.url().match("^http://")&&"https:"===window.location.protocol?"Mixed Content: The page at `"+window.location.origin+"` was loaded over HTTPS, but requested an insecure server endpoint `"+registryUI.url()+"`. This request has been blocked; the content must be served over HTTPS.":registryUI.url()?"An error occured":"Incorrect server endpoint."};var registryUI={};registryUI.url=function(){return"${URL}"},registryUI.name=function(){return"${REGISTRY_TITLE}"},registryUI.isImageRemoveActivated=!0,registryUI.catalog={},registryUI.taglist={},riot.mount("catalog"),riot.mount("taglist"),riot.mount("app");

View File

@@ -1,6 +1,6 @@
/*!
* docker-registry-ui
* 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
@@ -15,4 +15,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/>.
*/
function Http(){this.oReq=new XMLHttpRequest,this.oReq.hasHeader=Http.hasHeader,this._events={},this._headers={}}Http.prototype.addEventListener=function(e,t){this._events[e]=t;var r=this;switch(e){case"loadend":r.oReq.addEventListener("loadend",function(){if(401==this.status){var e=new XMLHttpRequest;e.open(r._method,r._url);for(key in r._events)e.addEventListener(key,r._events[key]);for(key in r._headers)e.setRequestHeader(key,r._headers[key]);e.withCredentials=!0,e.hasHeader=Http.hasHeader,e.send()}else t.bind(this)()});break;case"load":r.oReq.addEventListener("load",function(){401!==this.status&&t.bind(this)()});break;default:r.oReq.addEventListener(e,function(){t.bind(this)()})}},Http.prototype.setRequestHeader=function(e,t){this.oReq.setRequestHeader(e,t),this._headers[e]=t},Http.prototype.open=function(e,t){this._method=e,this._url=t,this.oReq.open(e,t)},Http.prototype.send=function(){this.oReq.send()},Http.hasHeader=function(e){return this.getAllResponseHeaders().split("\n").some(function(t){return new RegExp("^"+e+":","i").test(t)})};var registryUI={};registryUI.URL_QUERY_PARAM_REGEX=/[&?]url=/,registryUI.URL_PARAM_REGEX=/^url=/,registryUI.url=function(e){if(!registryUI._url){var t=registryUI.getUrlQueryParam();if(t)try{return registryUI._url=registryUI.decodeURI(t),registryUI._url}catch(e){console.log(e)}registryUI._url=registryUI.getRegistryServer(0)}return registryUI._url},registryUI.getRegistryServer=function(e){try{var t=JSON.parse(localStorage.getItem("registryServer"));if(t instanceof Array)return isNaN(e)?t.map(function(e){return e.trim().replace(/\/*$/,"")}):t[e]}catch(e){}return isNaN(e)?[]:""},registryUI.addServer=function(e){var t=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,"");var r=t.indexOf(e);r==-1&&(t.push(e),registryUI._url||registryUI.updateHistory(e),localStorage.setItem("registryServer",JSON.stringify(t)))},registryUI.changeServer=function(e){var t=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,"");var r=t.indexOf(e);r!=-1&&(t.splice(r,1),t=[e].concat(t),registryUI.updateHistory(e),localStorage.setItem("registryServer",JSON.stringify(t)))},registryUI.removeServer=function(e){var t=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,"");var r=t.indexOf(e);r!=-1&&(t.splice(r,1),localStorage.setItem("registryServer",JSON.stringify(t)),e==registryUI.url()&&(registryUI.updateHistory(registryUI.getRegistryServer(0)),route("")))},registryUI.updateHistory=function(e){history.pushState(null,"",(e?"?url="+registryUI.encodeURI(e):"?")+window.location.hash),registryUI._url=e},registryUI.getUrlQueryParam=function(){var e=window.location.search;if(registryUI.URL_QUERY_PARAM_REGEX.test(e)){var t=e.split(/^\?|&/).find(function(e){return e&&registryUI.URL_PARAM_REGEX.test(e)});return t?t.replace(registryUI.URL_PARAM_REGEX,""):t}},registryUI.encodeURI=function(e){return e.indexOf("&")<0?window.encodeURIComponent(e):btoa(e)},registryUI.decodeURI=function(e){return e.startsWith("http")?window.decodeURIComponent(e):atob(e)},registryUI.isImageRemoveActivated=!0,registryUI.catalog={},registryUI.taglist={},riot.mount("*");
function Http(){this.oReq=new XMLHttpRequest,this.oReq.hasHeader=Http.hasHeader,this.oReq.getErrorMessage=Http.getErrorMessage,this._events={},this._headers={}}Http.prototype.addEventListener=function(e,r){this._events[e]=r;var t=this;switch(e){case"loadend":t.oReq.addEventListener("loadend",function(){if(401==this.status){var e=new XMLHttpRequest;e.open(t._method,t._url);for(key in t._events)e.addEventListener(key,t._events[key]);for(key in t._headers)e.setRequestHeader(key,t._headers[key]);e.withCredentials=!0,e.hasHeader=Http.hasHeader,e.getErrorMessage=Http.getErrorMessage,e.send()}else r.bind(this)()});break;case"load":t.oReq.addEventListener("load",function(){401!==this.status&&r.bind(this)()});break;default:t.oReq.addEventListener(e,function(){r.bind(this)()})}},Http.prototype.setRequestHeader=function(e,r){this.oReq.setRequestHeader(e,r),this._headers[e]=r},Http.prototype.open=function(e,r){this._method=e,this._url=r,this.oReq.open(e,r)},Http.prototype.send=function(){this.oReq.send()},Http.hasHeader=function(e){return this.getAllResponseHeaders().split("\n").some(function(r){return new RegExp("^"+e+":","i").test(r)})},Http.getErrorMessage=function(){return registryUI.url()&&registryUI.url().match("^http://")&&"https:"===window.location.protocol?"Mixed Content: The page at `"+window.location.origin+"` was loaded over HTTPS, but requested an insecure server endpoint `"+registryUI.url()+"`. This request has been blocked; the content must be served over HTTPS.":registryUI.url()?"An error occured":"Incorrect server endpoint."};var registryUI={};registryUI.URL_QUERY_PARAM_REGEX=/[&?]url=/,registryUI.URL_PARAM_REGEX=/^url=/,registryUI.name=registryUI.url=function(e){if(!registryUI._url){var r=registryUI.getUrlQueryParam();if(r)try{return registryUI._url=registryUI.decodeURI(r),registryUI._url}catch(e){console.log(e)}registryUI._url=registryUI.getRegistryServer(0)}return registryUI._url},registryUI.getRegistryServer=function(e){try{var r=JSON.parse(localStorage.getItem("registryServer"));if(r instanceof Array)return isNaN(e)?r.map(function(e){return e.trim().replace(/\/*$/,"")}):r[e]}catch(e){}return isNaN(e)?[]:""},registryUI.addServer=function(e){var r=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,"");var t=r.indexOf(e);t==-1&&(r.push(e),registryUI._url||registryUI.updateHistory(e),localStorage.setItem("registryServer",JSON.stringify(r)))},registryUI.changeServer=function(e){var r=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,"");var t=r.indexOf(e);t!=-1&&(r.splice(t,1),r=[e].concat(r),registryUI.updateHistory(e),localStorage.setItem("registryServer",JSON.stringify(r)))},registryUI.removeServer=function(e){var r=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,"");var t=r.indexOf(e);t!=-1&&(r.splice(t,1),localStorage.setItem("registryServer",JSON.stringify(r)),e==registryUI.url()&&(registryUI.updateHistory(registryUI.getRegistryServer(0)),route("")))},registryUI.updateHistory=function(e){history.pushState(null,"",(e?"?url="+registryUI.encodeURI(e):"?")+window.location.hash),registryUI._url=e},registryUI.getUrlQueryParam=function(){var e=window.location.search;if(registryUI.URL_QUERY_PARAM_REGEX.test(e)){var r=e.split(/^\?|&/).find(function(e){return e&&registryUI.URL_PARAM_REGEX.test(e)});return r?r.replace(registryUI.URL_PARAM_REGEX,""):r}},registryUI.encodeURI=function(e){return e.indexOf("&")<0?window.encodeURIComponent(e):btoa(e)},registryUI.decodeURI=function(e){return e.startsWith("http")?window.decodeURIComponent(e):atob(e)},registryUI.isImageRemoveActivated=!0,registryUI.catalog={},registryUI.taglist={},riot.mount("*");

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

4
dist/style.css vendored
View File

@@ -1,6 +1,6 @@
/*!
* docker-registry-ui
* 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
@@ -15,4 +15,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/>.
*/
@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(MaterialIcons-Regular.eot);src:local('fonts/Material Icons'),local('fonts/MaterialIcons-Regular'),url(fonts/MaterialIcons-Regular.woff2) format('woff2'),url(fonts/MaterialIcons-Regular.woff) format('woff'),url(fonts/MaterialIcons-Regular.ttf) format('truetype')}.material-icons{font-family:'Material Icons';font-weight:400;font-style:normal;font-size:24px;display:inline-block;width:1em;height:1em;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'}html>body{font-family:Roboto,Helvetica,Arial,sans-serif!important}body,html{margin:0;height:100%}main{margin-bottom:100px}.section-centerd{margin:auto}.full-table{width:100%;border:none}.url{font-size:14px;word-break:break-all}.material-card-title-action a{color:inherit;text-decoration:none;font-weight:inherit}material-card{min-height:200px;max-width:75%;margin:auto;margin-top:20px;margin-bottom:20px}material-spinner{align-self:center}.spinner-wrapper{margin-top:50px;display:flex;flex-direction:column}material-navbar{height:64px}.logo{padding:0 16px 0 72px;text-decoration:none;font-size:20px;line-height:1;letter-spacing:.02em;font-weight:400}h2{padding:16px;margin:auto;font-size:24px;font-weight:300;line-height:normal;overflow:hidden}.list{display:block;padding:8px 0;list-style:none}.list.highlight>li:hover{background-color:#eee;cursor:pointer}.list>li{box-sizing:border-box;line-height:1;height:48px;padding:0 16px;overflow:hidden}.list>li i.material-icons{margin-right:32px;height:24px;width:24px;font-size:24px;box-sizing:border-box;color:#757575}.list>li>span{height:100%;text-decoration:none;box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.material-card-title-action{-webkit-align-items:center;-ms-flex-align:center;align-items:center;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;padding:16px}.material-card-title-action h2{margin:0}material-card table{width:100%;border:none;position:relative;border:1px solid rgba(0,0,0,.12);border-collapse:collapse;white-space:nowrap;font-size:13px;background-color:#fff;border:none}material-card table th{font-size:18px;vertical-align:bottom;line-height:24px;height:48px;color:rgba(0,0,0,.54);box-sizing:border-box;padding:0 18px 12px 18px;text-align:right}.material-card-th-left{text-align:left}material-card table tbody tr:hover{background-color:#eee}material-card table tbody tr{position:relative;height:48px;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background-color}material-card table td{font-size:16px;position:relative;height:48px;border-top:1px solid rgba(0,0,0,.12);border-bottom:1px solid rgba(0,0,0,.12);padding:12px 18px;box-sizing:border-box;vertical-align:middle;text-align:right}material-card table th.material-card-th-sorted-ascending:hover,material-card table th.material-card-th-sorted-descending:hover{cursor:pointer}material-card table th.material-card-th-sorted-ascending:hover:before,material-card table th.material-card-th-sorted-descending:hover:before{color:rgba(0,0,0,.26)}material-card table th.material-card-th-sorted-ascending:before,material-card table th.material-card-th-sorted-descending:before{font-family:'Material Icons';font-weight:400;font-style:normal;line-height:1;font-size:16px;content:"\e5d8";margin-right:5px;vertical-align:sub}material-card table th.material-card-th-sorted-descending:before{content:"\e5db"}.material-icons{color:#777}material-snackbar .toast{height:auto}menu{position:absolute;top:0;right:16px;color:#000}menu .overlay{position:fixed;height:100%;width:100%;top:0;right:0;z-index:1}#menu-control-button{background:rgba(255,255,255,0);float:right}#menu-control-button i{color:#fff;font-size:24px}#menu-control-dropdown{display:inline-block;position:relative}.dropdown{min-width:124px;padding:8px 0;margin:0}#menu-control-dropdown p,dropdown-item{padding:0 16px;margin:auto;line-height:48px;height:48px;cursor:pointer}#menu-control-dropdown p:hover{background-color:#eee}#menu-control-dropdown p:active,.material-button-active:active{background-color:#e0e0e0}material-popup material-button{background-color:#fff;color:#000}material-popup material-button:hover material-waves{background-color:hsla(0,0%,75%,.2)}material-popup .popup{max-width:450px}footer{width:100%;position:fixed;bottom:0}.select-padding{padding:20px 0}select{position:relative;outline:0;box-shadow:none;padding:0;width:100%;background:0 0;border:none;font-weight:400;line-height:24px;height:24px;border-bottom:1px solid #2f6975;appearance:none;-moz-appearance:none;-webkit-appearance:none}
@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(MaterialIcons-Regular.eot);src:local('fonts/Material Icons'),local('fonts/MaterialIcons-Regular'),url(fonts/MaterialIcons-Regular.woff2) format('woff2'),url(fonts/MaterialIcons-Regular.woff) format('woff'),url(fonts/MaterialIcons-Regular.ttf) format('truetype')}.material-icons{font-family:'Material Icons';font-weight:400;font-style:normal;font-size:24px;display:inline-block;width:1em;height:1em;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'}html>body{font-family:Roboto,Helvetica,Arial,sans-serif!important}body,html{margin:0;height:100%}main{margin-bottom:100px}.section-centerd{margin:auto}.full-table{width:100%;border:none}.url{font-size:14px;word-break:break-all}.material-card-title-action a{color:inherit;text-decoration:none;font-weight:inherit}material-card{min-height:200px;max-width:75%;margin:auto;margin-top:20px;margin-bottom:20px}material-spinner{align-self:center}.spinner-wrapper{margin-top:50px;display:flex;flex-direction:column}material-navbar{height:64px}.logo{padding:0 16px 0 72px;text-decoration:none;font-size:20px;line-height:1;letter-spacing:.02em;font-weight:400}h2{padding:16px;margin:auto;font-size:24px;font-weight:300;line-height:normal;overflow:hidden}.list{display:block;padding:8px 0;list-style:none}.list.highlight>li:hover{background-color:#eee;cursor:pointer}.list>li{box-sizing:border-box;line-height:1;height:48px;padding:0 16px;overflow:hidden}.list>li i.material-icons{margin-right:32px;height:24px;width:24px;font-size:24px;box-sizing:border-box;color:#757575}.list>li>span{height:100%;text-decoration:none;box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.material-card-title-action{-webkit-align-items:center;-ms-flex-align:center;align-items:center;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;padding:16px}.material-card-title-action h2{margin:0}material-card table{width:100%;border:none;position:relative;border:1px solid rgba(0,0,0,.12);border-collapse:collapse;white-space:nowrap;font-size:13px;background-color:#fff;border:none}material-card table th{font-size:18px;vertical-align:bottom;line-height:24px;height:48px;color:rgba(0,0,0,.54);box-sizing:border-box;padding:0 18px 12px 18px;text-align:right}.material-card-th-left{text-align:left}material-card table tbody tr:hover{background-color:#eee}material-card table tbody tr{position:relative;height:48px;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background-color}material-card table td{font-size:16px;position:relative;height:48px;border-top:1px solid rgba(0,0,0,.12);border-bottom:1px solid rgba(0,0,0,.12);padding:12px 18px;box-sizing:border-box;vertical-align:middle;text-align:right}material-card table th.material-card-th-sorted-ascending:hover,material-card table th.material-card-th-sorted-descending:hover{cursor:pointer}material-card table th.material-card-th-sorted-ascending:hover:before,material-card table th.material-card-th-sorted-descending:hover:before{color:rgba(0,0,0,.26)}material-card table th.material-card-th-sorted-ascending:before,material-card table th.material-card-th-sorted-descending:before{font-family:'Material Icons';font-weight:400;font-style:normal;line-height:1;font-size:16px;content:"\e5d8";margin-right:5px;vertical-align:sub}material-card table th.material-card-th-sorted-descending:before{content:"\e5db"}.material-icons{color:#777}material-snackbar .toast{height:auto}menu{position:absolute;top:0;right:16px;color:#000}menu .overlay{position:fixed;height:100%;width:100%;top:0;right:0;z-index:1}#menu-control-button{background:rgba(255,255,255,0);float:right}#menu-control-button i{color:#fff;font-size:24px}#menu-control-dropdown{display:inline-block;position:relative}.dropdown{min-width:124px;padding:8px 0;margin:0}#menu-control-dropdown p,dropdown-item{padding:0 16px;margin:auto;line-height:48px;height:48px;cursor:pointer}#menu-control-dropdown p:hover{background-color:#eee}#menu-control-dropdown p:active,.material-button-active:active{background-color:#e0e0e0}material-popup material-button{background-color:#fff;color:#000}material-popup material-button:hover material-waves{background-color:hsla(0,0%,75%,.2)}material-popup .popup{max-width:450px}footer{width:100%;position:fixed;bottom:0}.select-padding{padding:20px 0}select{position:relative;outline:0;box-shadow:none;padding:0;width:100%;background:0 0;border:none;font-weight:400;line-height:24px;height:24px;border-bottom:1px solid #2f6975;appearance:none;-moz-appearance:none;-webkit-appearance:none}.copy-to-clipboard{padding:12px 5px}.copy-to-clipboard a:hover{cursor:pointer}

View File

@@ -12,6 +12,7 @@ var riot = require('gulp-riot');
var minifier = require('gulp-uglify/minifier');
var uglify = require('uglify-js-harmony');
var useref = require('gulp-useref');
var injectVersion = require('gulp-inject-version');
gulp.task('html', function() {
var htmlFilter = filter('**/*.html', {restore: true});
@@ -42,23 +43,25 @@ gulp.task('riot-tag', ['html'], function() {
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
year: '2016',
year: '2016-2018',
organization: 'Jones Magloire @Joxit'
}))
.pipe(injectVersion())
.pipe(gulp.dest('dist/scripts'));
});
gulp.task('riot-static-tag', ['html'], function() {
return gulp.src(['src/tags/catalog.tag', 'src/tags/app.tag', 'src/tags/taglist.tag', 'src/tags/remove-image.tag'])
return gulp.src(['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'])
.pipe(concat('tags-static.js'))
.pipe(riot())
.pipe(minifier({}, uglify))
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
year: '2016',
year: '2016-2018',
organization: 'Jones Magloire @Joxit'
}))
.pipe(injectVersion())
.pipe(gulp.dest('dist/scripts'));
});
@@ -69,7 +72,7 @@ gulp.task('scripts-static', ['html'], function() {
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
year: '2016',
year: '2016-2018',
organization: 'Jones Magloire @Joxit'
}))
.pipe(gulp.dest('dist/scripts'));
@@ -82,7 +85,7 @@ gulp.task('scripts', ['html'], function() {
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
year: '2016',
year: '2016-2018',
organization: 'Jones Magloire @Joxit'
}))
.pipe(gulp.dest('dist/scripts'));
@@ -103,7 +106,7 @@ gulp.task('styles', ['html'], function() {
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
year: '2016',
year: '2016-2018',
organization: 'Jones Magloire @Joxit'
}))
.pipe(gulp.dest('dist/'));

43
nginx/default.conf Normal file
View File

@@ -0,0 +1,43 @@
server {
listen 80;
server_name localhost;
#! resolver 127.0.0.11; # This is for docker container name resolver
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
# disable any limits to avoid HTTP 413 for large image uploads
client_max_body_size 0;
# required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)
chunked_transfer_encoding on;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
#! location /v2 {
#! # Do not allow connections from docker 1.5 and earlier
#! # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
#! if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
#! return 404;
#! }
#! proxy_pass ${REGISTRY_URL};
#! }
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}

View File

@@ -1,6 +1,6 @@
{
"name": "docker-registry-ui",
"version": "0.3.1",
"version": "0.3.8",
"scripts": {
"build": "./node_modules/gulp/bin/gulp.js build"
},
@@ -15,20 +15,20 @@
"devDependencies": {
"del": "^3.0.0",
"gulp": "^3.9",
"gulp-clean-css": "^3.7.0",
"gulp-clean-css": "^3.9.4",
"gulp-concat": "^2.6.0",
"gulp-filter": "^5.0.1",
"gulp-filter": "^5.1.0",
"gulp-htmlmin": "^3.0.0",
"gulp-if": "^2.0.0",
"gulp-inject-version": "^1.0.1",
"gulp-license": "^1.1.0",
"gulp-riot": "^1.1.1",
"gulp-riot": "^1.1.4",
"gulp-uglify": "^2.1.2",
"gulp-useref": "^3.1.3",
"pump": "^1.0.2",
"riot": "^3.7.4",
"gulp-useref": "^3.1.5",
"riot": "^3.10.3",
"riot-mui": "^0.1.1",
"riot-route": "^3.1.2",
"uglify-js": "^3.1.8",
"riot-route": "^3.1.3",
"uglify-js": "^3.4.0",
"uglify-js-harmony": "^2.7.7"
}
}

View File

@@ -40,11 +40,14 @@
<!-- build:js scripts/tags.js -->
<script src="tags/catalog.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/image-size.tag" type="riot/tag"></script>
<script src="tags/app.tag" type="riot/tag"></script>
<!-- endbuild -->
<!-- build:js scripts/script.js -->

View File

@@ -17,6 +17,7 @@
function Http() {
this.oReq = new XMLHttpRequest();
this.oReq.hasHeader = Http.hasHeader;
this.oReq.getErrorMessage = Http.getErrorMessage;
this._events = {};
this._headers = {};
}
@@ -39,6 +40,7 @@ Http.prototype.addEventListener = function(e, f) {
}
req.withCredentials = true;
req.hasHeader = Http.hasHeader;
req.getErrorMessage = Http.getErrorMessage;
req.send();
} else {
f.bind(this)();
@@ -84,4 +86,13 @@ Http.hasHeader = function(header) {
return this.getAllResponseHeaders().split('\n').some(function(h) {
return new RegExp('^' + header + ':', 'i').test(h);
});
};
Http.getErrorMessage = function() {
if (registryUI.url() && registryUI.url().match('^http://') && window.location.protocol === 'https:') {
return 'Mixed Content: The page at `' + window.location.origin + '` was loaded over HTTPS, but requested an insecure server endpoint `' + registryUI.url() + '`. This request has been blocked; the content must be served over HTTPS.';
} else if (!registryUI.url()) {
return 'Incorrect server endpoint.'
}
return 'An error occured';
};

View File

@@ -18,7 +18,7 @@ var registryUI = {}
registryUI.URL_QUERY_PARAM_REGEX = /[&?]url=/;
registryUI.URL_PARAM_REGEX = /^url=/;
registryUI.url = function(byPassQueryParam) {
registryUI.name = registryUI.url = function(byPassQueryParam) {
if (!registryUI._url) {
var url = registryUI.getUrlQueryParam();
if (url) {

View File

@@ -18,6 +18,9 @@ var registryUI = {}
registryUI.url = function() {
return '${URL}';
};
registryUI.name = function() {
return '${REGISTRY_TITLE}' || registryUI.url();
};
registryUI.isImageRemoveActivated = true;
registryUI.catalog = {};
registryUI.taglist = {};

View File

@@ -15,9 +15,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
html > body {
html>body {
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif !important;
}
html, body {
margin: 0;
height: 100%;
@@ -64,9 +65,11 @@ material-spinner {
display: flex;
flex-direction: column;
}
material-navbar {
height: 64px;
}
.logo {
padding: 0 16px 0 72px;
text-decoration: none;
@@ -75,6 +78,7 @@ material-navbar {
letter-spacing: .02em;
font-weight: 400;
}
h2 {
padding: 16px;
margin: auto;
@@ -83,15 +87,18 @@ h2 {
line-height: normal;
overflow: hidden;
}
.list {
display: block;
padding: 8px 0;
list-style: none;
}
.list.highlight>li:hover {
background-color: #eee;
cursor: pointer;
}
.list>li {
box-sizing: border-box;
line-height: 1;
@@ -99,6 +106,7 @@ h2 {
padding: 0 16px;
overflow: hidden;
}
.list>li i.material-icons {
margin-right: 32px;
height: 24px;
@@ -107,6 +115,7 @@ h2 {
box-sizing: border-box;
color: #757575;
}
.list>li>span {
height: 100%;
text-decoration: none;
@@ -138,7 +147,7 @@ material-card table {
width: 100%;
border: none;
position: relative;
border: 1px solid rgba(0,0,0,.12);
border: 1px solid rgba(0, 0, 0, .12);
border-collapse: collapse;
white-space: nowrap;
font-size: 13px;
@@ -151,7 +160,7 @@ material-card table th {
vertical-align: bottom;
line-height: 24px;
height: 48px;
color: rgba(0,0,0,.54);
color: rgba(0, 0, 0, .54);
box-sizing: border-box;
padding: 0 18px 12px 18px;
text-align: right;
@@ -169,7 +178,7 @@ material-card table tbody tr {
position: relative;
height: 48px;
transition-duration: .28s;
transition-timing-function: cubic-bezier(.4,0,.2,1);
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
transition-property: background-color;
}
@@ -177,24 +186,23 @@ material-card table td {
font-size: 16px;
position: relative;
height: 48px;
border-top: 1px solid rgba(0,0,0,.12);
border-bottom: 1px solid rgba(0,0,0,.12);
border-top: 1px solid rgba(0, 0, 0, .12);
border-bottom: 1px solid rgba(0, 0, 0, .12);
padding: 12px 18px;
box-sizing: border-box;
vertical-align: middle;
text-align: right;
}
material-card table th.material-card-th-sorted-ascending:hover,
material-card table th.material-card-th-sorted-descending:hover {
material-card table th.material-card-th-sorted-ascending:hover, material-card table th.material-card-th-sorted-descending:hover {
cursor: pointer;
}
material-card table th.material-card-th-sorted-ascending:hover:before,
material-card table th.material-card-th-sorted-descending:hover:before {
color: rgba(0,0,0,.26);
material-card table th.material-card-th-sorted-ascending:hover:before, material-card table th.material-card-th-sorted-descending:hover:before {
color: rgba(0, 0, 0, .26);
}
material-card table th.material-card-th-sorted-ascending:before,
material-card table th.material-card-th-sorted-descending:before {
material-card table th.material-card-th-sorted-ascending:before, material-card table th.material-card-th-sorted-descending:before {
font-family: 'Material Icons';
font-weight: 400;
font-style: normal;
@@ -204,6 +212,7 @@ material-card table th.material-card-th-sorted-descending:before {
margin-right: 5px;
vertical-align: sub;
}
material-card table th.material-card-th-sorted-descending:before {
content: "\e5db";
}
@@ -233,7 +242,7 @@ menu .overlay {
}
#menu-control-button {
background: rgba(255,255,255,0);
background: rgba(255, 255, 255, 0);
float: right;
}
@@ -275,7 +284,7 @@ material-popup material-button {
}
material-popup material-button:hover material-waves {
background-color: hsla(0,0%,75%,.2);
background-color: hsla(0, 0%, 75%, .2);
}
material-popup .popup {
@@ -307,4 +316,12 @@ select {
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
}
.copy-to-clipboard {
padding: 12px 5px;
}
.copy-to-clipboard a:hover {
cursor: pointer;
}

View File

@@ -31,7 +31,7 @@
</main>
<footer>
<material-footer>
<a class="material-footer-logo" href="https://joxit.github.io/docker-registry-ui/">Docker Registry UI</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>
@@ -77,6 +77,13 @@
registryUI.errorSnackbar = function (message) {
return registryUI.snackbar(message, true);
}
registryUI.cleanName = function() {
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, '\\?')
@@ -86,6 +93,55 @@
const args = path.match(re)
if (args) return args.slice(1)
});
registryUI.DockerImage = function (name, tag) {
this.name = name;
this.tag = tag;
riot.observable(this);
this.on('get-size', function() {
if (this.size !== undefined) {
return this.trigger('size', this.size);
}
return this.fillInfo();
});
this.on('get-sha256', function() {
if (this.size !== undefined) {
return this.trigger('sha256', this.sha256);
}
return this.fillInfo();
});
};
registryUI.DockerImage.compare = function(e1, e2) {
return e1.tag.localeCompare(e2.tag);
};
registryUI.DockerImage.prototype.fillInfo = function() {
if (this._fillInfoWaiting) {
return;
}
this._fillInfoWaiting = true;
var oReq = new Http();
var self = this;
oReq.addEventListener('loadend', function () {
if (this.status == 200 || this.status == 202) {
var response = JSON.parse(this.responseText);
self.size = response.layers.reduce(function (acc, e) {
return acc + e.size;
}, 0);
self.sha256 = response.config.digest;
self.trigger('size', self.size);
self.trigger('sha256', self.sha256);
} else if (this.status == 404) {
registryUI.errorSnackbar('Manifest for ' + self.name + ':' + self.tag + ' not found');
} else {
registryUI.snackbar(this.responseText);
}
});
oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/manifests/' + self.tag);
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
oReq.send();
}
route.start(true);
</script>
</app>

View File

@@ -18,7 +18,7 @@
<!-- Begin of tag -->
<material-card ref="catalog-tag" class="catalog">
<div class="material-card-title-action">
<h2>Repositories of { registryUI.url() }</h2>
<h2>Repositories of { registryUI.name() }</h2>
</div>
<div hide="{ registryUI.catalog.loadend }" class="spinner-wrapper">
<material-spinner></material-spinner>
@@ -50,14 +50,14 @@
}
});
oReq.addEventListener('error', function () {
registryUI.snackbar('An error occured', true);
registryUI.snackbar(this.getErrorMessage(), true);
registryUI.catalog.repositories = [];
});
oReq.addEventListener('loadend', function () {
registryUI.catalog.loadend = true;
registryUI.catalog.instance.update();
});
oReq.open('GET', registryUI.url() + '/v2/_catalog');
oReq.open('GET', registryUI.url() + '/v2/_catalog?n=100000');
oReq.send();
};
registryUI.catalog.go = function (image) {
@@ -66,4 +66,4 @@
registryUI.catalog.display();
</script>
<!-- End of tag -->
</catalog>
</catalog>

View File

@@ -0,0 +1,34 @@
<!--
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/>.
-->
<copy-to-clipboard>
<input ref="input" style="display: none; width: 1px; height: 1px;" value="{ this.dockerCmd }">
<a onclick="{ this.copy }" title="Copy pull command.">
<i class="material-icons">content_copy</i>
</a>
<script type="text/javascript">
this.dockerCmd = 'docker pull ' + registryUI.cleanName() + '/' + opts.image.name + ':' + opts.image.tag;
this.copy = function () {
var copyText = this.refs['input'];
copyText.style.display = 'block';
copyText.select();
document.execCommand('copy');
copyText.style.display = 'none';
registryUI.snackbar('`' + this.dockerCmd + '` has been copied to clipbloard.')
};
</script>
</copy-to-clipboard>

37
src/tags/image-size.tag Normal file
View File

@@ -0,0 +1,37 @@
<!--
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-size>
<div title="Compressed size of your image.">{ this.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];
};
opts.image.on('size', function(size) {
self.size = size;
self.update();
});
opts.image.trigger('get-size');
</script>
</image-size>

27
src/tags/image-tag.tag Normal file
View File

@@ -0,0 +1,27 @@
<!--
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/>.
-->
<image-tag>
<div title="{ this.sha256 }">{ opts.image.tag }</div>
<script type="text/javascript">
var self = this;
opts.image.on('sha256', function(sha256) {
self.sha256 = sha256.substring(0, 19);
self.update();
});
opts.image.trigger('get-sha256');
</script>
</image-tag>

View File

@@ -15,13 +15,12 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<remove-image>
<a href="#" onclick="registryUI.removeImage.remove('{ opts.name }', '{ opts.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.update = this.update;
registryUI.removeImage = registryUI.removeImage || {};
registryUI.removeImage.remove = function (name, tag) {
var oReq = new Http();
oReq.addEventListener('loadend', function () {

View File

@@ -21,7 +21,7 @@
<a href="#!" onclick="registryUI.home();">
<i class="material-icons">arrow_back</i>
</a>
<h2>Tags of { registryUI.url() + '/' + registryUI.taglist.name }</h2>
<h2>Tags of { registryUI.name() + '/' + registryUI.taglist.name }</h2>
</div>
<div hide="{ registryUI.taglist.loadend }" class="spinner-wrapper">
<material-spinner></material-spinner>
@@ -30,16 +30,22 @@
<thead>
<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="{ item in registryUI.taglist.tags }">
<td class="material-card-th-left">{ registryUI.taglist.name }</td>
<td>{ item }</td>
<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 name={ registryUI.taglist.name } tag={ item }/>
<remove-image image={ image }/>
</td>
</tr>
</tbody>
@@ -56,7 +62,9 @@
registryUI.taglist.tags = [];
if (this.status == 200) {
registryUI.taglist.tags = JSON.parse(this.responseText).tags || [];
registryUI.taglist.tags.sort();
registryUI.taglist.tags = registryUI.taglist.tags.map(function(tag) {
return new registryUI.DockerImage(registryUI.taglist.name, tag);
}).sort(registryUI.DockerImage.compare);
} else if (this.status == 404) {
registryUI.snackbar('Server not found', true);
} else {
@@ -64,7 +72,7 @@
}
});
oReq.addEventListener('error', function () {
registryUI.snackbar('An error occured', true);
registryUI.snackbar(this.getErrorMessage(), true);
registryUI.taglist.tags = [];
});
oReq.addEventListener('loadend', function () {
@@ -84,7 +92,7 @@
registryUI.taglist.tags.reverse();
registryUI.taglist.asc = false;
} else {
registryUI.taglist.tags.sort();
registryUI.taglist.tags.sort(registryUI.DockerImage.compare);
registryUI.taglist.asc = true;
}
registryUI.taglist.instance.update();

27
static.dockerfile Normal file
View File

@@ -0,0 +1,27 @@
# 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/>.
FROM nginx:alpine
LABEL maintainer="Jones MAGLOIRE @Joxit"
WORKDIR /usr/share/nginx/html/
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
COPY dist/ /usr/share/nginx/html/
COPY dist/scripts/script-static.js /usr/share/nginx/html/scripts/script.js
COPY dist/scripts/tags-static.js /usr/share/nginx/html/scripts/tags.js
COPY bin/entrypoint /bin
ENTRYPOINT entrypoint