Compare commits

..

3 Commits
0.3.0 ... 0.3.2

Author SHA1 Message Date
Joxit
bf3e3c9fa8 Add REGISTRY_URL option for #25, this will avoid CORS errors 2018-01-25 18:39:47 +01:00
Joxit
325a2417f4 Update version to 0.3.1 Fix for #24 2017-11-08 23:50:43 -04:00
Joxit
adab8b03de Use nginx alpine in static dockerfile 2017-10-22 11:59:44 +02:00
17 changed files with 120 additions and 60 deletions

View File

@@ -12,15 +12,16 @@
#
# 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
FROM nginx:alpine
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
ENTRYPOINT entrypoint

View File

@@ -21,6 +21,7 @@ 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).
## Getting Started
@@ -83,13 +84,26 @@ 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.
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 joxit/docker-registry-ui:static
```
## Using CORS
Your server should be configured to accept CORS.

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/sh
$@
sed -i "s,\${URL},${URL}," scripts/script.js
@@ -6,8 +6,13 @@ 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
$@
fi
fi

View File

@@ -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,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.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)})};var registryUI={};registryUI.URL_QUERY_PARAM_REGEX=/[&?]url=/,registryUI.URL_PARAM_REGEX=/^url=/,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)),rg.router.go("home")))},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("*");
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("*");

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

View File

@@ -89,7 +89,7 @@ gulp.task('scripts', ['html'], function() {
});
gulp.task('vendor', ['html'], function() {
return gulp.src(['node_modules/riot/riot.min.js', 'node_modules/riotgear-router/dist/rg-router.min.js', 'node_modules/riot-mui/build/js/riot-mui-min.js'])
return gulp.src(['node_modules/riot/riot.min.js', 'node_modules/riot-route/dist/route.min.js', 'node_modules/riot-mui/build/js/riot-mui-min.js'])
.pipe(concat('vendor.js'))
.pipe(gulp.dest('dist/scripts'));
});

34
nginx/default.conf Normal file
View File

@@ -0,0 +1,34 @@
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;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
#! location /v2 {
#! set $upstream ${REGISTRY_URL};
#! proxy_pass $upstream;
#! }
#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.0",
"version": "0.3.1",
"scripts": {
"build": "./node_modules/gulp/bin/gulp.js build"
},
@@ -23,12 +23,12 @@
"gulp-license": "^1.1.0",
"gulp-riot": "^1.1.1",
"gulp-uglify": "^2.1.2",
"gulp-useref": "^3.0.0",
"gulp-useref": "^3.1.3",
"pump": "^1.0.2",
"riot": "^3.7.0",
"riot": "^3.7.4",
"riot-mui": "^0.1.1",
"riotgear-router": "^1.3.1",
"uglify-js": "^3.0.28",
"riot-route": "^3.1.2",
"uglify-js": "^3.1.8",
"uglify-js-harmony": "^2.7.7"
}
}

View File

@@ -34,7 +34,7 @@
<app></app>
<!-- build:js scripts/vendor.js -->
<script src="../node_modules/riot/riot+compiler.min.js"></script>
<script src="../node_modules/riotgear-router/dist/rg-router.min.js"></script>
<script src="../node_modules/riot-route/dist/route.js"></script>
<script src="../node_modules/riot-mui/build/js/riot-mui.js"></script>
<!-- endbuild -->
<!-- build:js scripts/tags.js -->

View File

@@ -80,7 +80,7 @@ registryUI.removeServer = function(url) {
localStorage.setItem('registryServer', JSON.stringify(registryServer));
if (url == registryUI.url()) {
registryUI.updateHistory(registryUI.getRegistryServer(0));
rg.router.go('home');
route('');
}
}

View File

@@ -48,7 +48,7 @@
if (registryUI.addTag.dialog.getAddServer().length > 0) {
registryUI.addServer(registryUI.addTag.dialog.getAddServer());
}
rg.router.go('home');
registryUI.home();
registryUI.addTag.close();
};
registryUI.addTag.close = function () {

View File

@@ -22,8 +22,8 @@
</material-navbar>
</header>
<main>
<catalog if="{!rg.router.current || rg.router.current.name == 'home'}"></catalog>
<taglist if="{rg.router.current && rg.router.current.name == 'taglist'}"></taglist>
<catalog if="{route.routeName == 'home'}"></catalog>
<taglist if="{route.routeName == 'taglist'}"></taglist>
<change></change>
<add></add>
<remove></remove>
@@ -45,32 +45,47 @@
</footer>
<script>
this.mixin('rg.router');
this.router.add({name: 'home', url: ''});
this.router.add({name: 'taglist', url: '/taglist/:repository/:image'});
this.router.on('go', state => {
switch (state.name) {
case 'taglist':
if (registryUI.taglist.display) {
registryUI.taglist.loadend = false;
registryUI.taglist.display();
}
break;
case 'home':
if (registryUI.catalog.display) {
registryUI.catalog.loadend = false;
registryUI.catalog.display();
}
break;
}
});
registryUI.appTag = this;
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
if (registryUI.taglist.display) {
registryUI.taglist.loadend = false;
registryUI.taglist.display();
}
registryUI.appTag.update();
});
registryUI.home = function() {
if(route.routeName == 'home') {
registryUI.catalog.display();
} else {
route('');
}
};
registryUI.snackbar = function (message, isError) {
registryUI.appTag.tags['material-snackbar'].addToast({'message': message, 'isError': isError});
};
registryUI.errorSnackbar = function (message) {
return registryUI.snackbar(message, true);
}
this.router.start();
route.parser(null, function(path, filter) {
const f = filter
.replace(/\?/g, '\\?')
.replace(/\*/g, '([^?#]+?)')
.replace(/\.\./, '.*')
const re = new RegExp('^' + f + '$')
const args = path.match(re)
if (args) return args.slice(1)
});
route.start(true);
</script>
</app>

View File

@@ -35,8 +35,8 @@
<script>
registryUI.catalog.instance = this;
this.mixin('rg.router');
registryUI.catalog.display = function () {
registryUI.catalog.repositories = [];
var oReq = new Http();
oReq.addEventListener('load', function () {
registryUI.catalog.repositories = [];
@@ -61,10 +61,7 @@
oReq.send();
};
registryUI.catalog.go = function (image) {
rg.router.go('taglist', {
repository: image.split('/')[0],
image: image.split('/')[1]
});
route('taglist/' + image);
};
registryUI.catalog.display();
</script>

View File

@@ -51,7 +51,7 @@
if (registryUI.changeTag.dialog.getServerUrl().length > 0) {
registryUI.changeServer(registryUI.changeTag.dialog.getServerUrl());
}
rg.router.go('home');
registryUI.home();
registryUI.changeTag.dialog.close();
};
registryUI.changeTag.close = function () {

View File

@@ -18,7 +18,7 @@
<!-- Begin of tag -->
<material-card ref="taglist-tag" class="taglist">
<div class="material-card-title-action">
<a href="#" onclick="registryUI.taglist.back();">
<a href="#!" onclick="registryUI.home();">
<i class="material-icons">arrow_back</i>
</a>
<h2>Tags of { registryUI.url() + '/' + registryUI.taglist.name }</h2>
@@ -48,12 +48,9 @@
<script>
registryUI.taglist.instance = this;
registryUI.taglist.display = function () {
if (rg.router.current && rg.router.current.name == 'taglist') {
name = rg.router.current.params.repository + (rg.router.current.params.image
? '/' + rg.router.current.params.image
: '');
registryUI.taglist.tags = [];
if (route.routeName == 'taglist') {
var oReq = new Http();
registryUI.taglist.name = name;
registryUI.taglist.instance.update();
oReq.addEventListener('load', function () {
registryUI.taglist.tags = [];
@@ -74,7 +71,7 @@
registryUI.taglist.loadend = true;
registryUI.taglist.instance.update();
});
oReq.open('GET', registryUI.url() + '/v2/' + name + '/tags/list');
oReq.open('GET', registryUI.url() + '/v2/' + registryUI.taglist.name + '/tags/list');
oReq.send();
registryUI.taglist.asc = true;
}
@@ -92,11 +89,8 @@
}
registryUI.taglist.instance.update();
};
registryUI.taglist.back = function () {
rg.router.go('home');
};
registryUI.taglist.refresh = function () {
rg.router.go(rg.router.current.name, rg.router.current.params);
route(registryUI.taglist.name);
};
</script>
<!-- End of tag -->