Compare commits

..

19 Commits
0.1.0 ... 0.2.2

Author SHA1 Message Date
Joxit
d34d793b73 Update 0.2.2: Fix issue #19 2017-08-06 11:39:01 +02:00
Joxit
36cbda1263 Update version 0.2.1: Fix issue #18
Fix `UNAUTHORIZED` error message popping up when browsing
2017-06-11 23:04:15 +02:00
Joxit
2d9b3886de [demo] Add analytics 2017-05-20 00:58:02 +02:00
Joxit
be553b0fb6 Add analytics 2017-05-04 21:20:33 +02:00
Joxit
deb401a418 Update Readme.md 2017-04-18 22:10:01 +02:00
Joxit
7adfd108da Add demo link 2017-04-17 21:44:52 +02:00
Jones Magloire
ded1a0be17 Set theme jekyll-theme-cayman 2017-04-13 11:45:20 +02:00
Joxit
102db94ae4 Update version to 0.2.0 2016-10-10 00:49:48 +02:00
Jones Magloire
7afb100550 Merge pull request #15 from Joxit/delete-image
Delete docker images (issue #14)
2016-10-09 21:19:38 +02:00
Joxit
d1d986448c [delete image] can activate or not this feature for static image and update README 2016-10-05 00:31:49 +02:00
Joxit
c007eea26d [delete image] externalize remove part in its own tag. 2016-10-04 00:24:01 +02:00
Joxit
dbcd0031d3 [delete image] Add some error messages. 2016-10-04 22:09:09 +02:00
Joxit
6180c206b4 [delete image] Update readme for delete feature 2016-10-04 22:09:09 +02:00
Joxit
bc215978ac [delete image] Fix docker remove image, need HEAD request and not a GET for digest. 2016-10-04 22:09:09 +02:00
Joxit
7150fe3245 [delete image] Add request for deleting a manifest/image.
Add setRequestHeader in Http class.
2016-10-04 22:09:06 +02:00
Joxit
70f860f813 Update gulpfile to uglify riot tags and html 2016-09-24 13:53:42 +02:00
Joxit
30f88520c8 [Fix] error when there is no images/tags 2016-09-18 15:06:16 +02:00
Joxit
c46129f9d0 Fix spinner and loading for taglist and catalog 2016-09-11 23:49:13 +02:00
Joxit
897e6eca0b Fix sort/reverse class 2016-09-10 01:10:43 +02:00
21 changed files with 695 additions and 626 deletions

108
README.md
View File

@@ -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.
@@ -8,17 +9,20 @@ All registry will be stored in the [local storage](https://en.wikipedia.org/wiki
This web user interface use [Riot](https://github.com/Riot/riot) the react-like user interface micro-library and [Material Design Lite](https://github.com/google/material-design-lite) components.
## [GitHub Page](https://joxit.github.io/docker-registry-ui) and [Live Demo](https://joxit.github.io/docker-registry-ui/demo/)
![screenshot](https://raw.github.com/Joxit/docker-registry-ui/master/screenshot.png "Screenshot of Docker Registry UI")
## 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,36 +34,59 @@ 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
docker build -t joxit/docker-registry-ui -f docker-registry-ui/Dockerfile.static docker-registry-ui
```
Or build with the url :
```sh
```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
@@ -67,13 +94,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: '<your docker-registry-ui url>'
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS'] # Optional
```
Access-Control-Allow-Origin: '<your docker-registry-ui url>'
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
```

3
_config.yml Normal file
View File

@@ -0,0 +1,3 @@
title: Docker Registry v2 User Interface
google_analytics: UA-99119327-1
theme: jekyll-theme-cayman

View File

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

76
demo/index.html Normal file
View File

@@ -0,0 +1,76 @@
<!--
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/>.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../dist/vendor.css">
<link rel="stylesheet" href="../dist/style.css">
<link href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&amp;lang=en" rel="stylesheet" type="text/css">
<title>Docker Registry UI</title>
</head>
<body>
<!-- Always shows a header, even in smaller screens. -->
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<header class="mdl-layout__header">
<div class="mdl-layout__header-row">
<!-- Title --><span class="mdl-layout-title">Docker Registry UI</span>
<menu></menu>
</div>
</header>
<main class="mdl-layout__content">
<div class="page-content">
<app></app>
</div>
</main>
<change></change>
<add></add>
<remove></remove>
<footer class="mdl-mini-footer">
<div class="mdl-mini-footer__left-section">
<div class="mdl-logo">Docker Registry UI</div>
<ul class="mdl-mini-footer__link-list">
<li><a href="https://github.com/Joxit/docker-registry-ui">Contribute on GitHub</a></li>
<li><a href="https://github.com/Joxit/docker-registry-ui/blob/master/LICENSE">Privacy &amp; Terms</a></li>
</ul>
</div>
</footer>
</div>
<script src="../dist/scripts/vendor.js"></script>
<script src="../dist/scripts/tags.js"></script>
<script src="../dist/scripts/script.js"></script>
<script>
(function(i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function() {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-99119327-1', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>

47
dist/index.html vendored
View File

@@ -13,49 +13,4 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="vendor.css">
<link rel="stylesheet" href="style.css">
<link href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&amp;lang=en" rel="stylesheet" type="text/css">
<title>Docker Registry UI</title>
</head>
<body>
<!-- Always shows a header, even in smaller screens. -->
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<header class="mdl-layout__header">
<div class="mdl-layout__header-row">
<!-- Title -->
<span class="mdl-layout-title">Docker Registry UI</span>
<menu></menu>
</div>
</header>
<main class="mdl-layout__content">
<div class="page-content">
<app></app>
</div>
</main>
<change></change>
<add></add>
<remove></remove>
<footer class="mdl-mini-footer">
<div class="mdl-mini-footer__left-section">
<div class="mdl-logo">Docker Registry UI</div>
<ul class="mdl-mini-footer__link-list">
<li><a href="https://github.com/Joxit/docker-registry-ui">Contribute on GitHub</a></li>
<li><a href="https://github.com/Joxit/docker-registry-ui/blob/master/LICENSE">Privacy &amp; Terms</a></li>
</ul>
</div>
</footer>
</div>
<script src="scripts/vendor.js"></script>
<script src="scripts/tags.js"></script>
<script src="scripts/script.js"></script>
</body>
</html>
--><!DOCTYPE html><html><head><meta charset="UTF-8"><link rel="stylesheet" href="vendor.css"><link rel="stylesheet" href="style.css"><link href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&amp;lang=en" rel="stylesheet" type="text/css"><title>Docker Registry UI</title></head><body><!-- Always shows a header, even in smaller screens. --><div class="mdl-layout mdl-js-layout mdl-layout--fixed-header"><header class="mdl-layout__header"><div class="mdl-layout__header-row"><!-- Title --> <span class="mdl-layout-title">Docker Registry UI</span><menu></menu></div></header><main class="mdl-layout__content"><div class="page-content"><app></app></div></main><change></change><add></add><remove></remove><footer class="mdl-mini-footer"><div class="mdl-mini-footer__left-section"><div class="mdl-logo">Docker Registry UI</div><ul class="mdl-mini-footer__link-list"><li><a href="https://github.com/Joxit/docker-registry-ui">Contribute on GitHub</a></li><li><a href="https://github.com/Joxit/docker-registry-ui/blob/master/LICENSE">Privacy &amp; Terms</a></li></ul></div></footer></div><script src="scripts/vendor.js"></script><script src="scripts/tags.js"></script><script src="scripts/script.js"></script></body></html>

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._events={}}Http.prototype.addEventListener=function(t,e){this._events[t]=e;var i=this;switch(t){case"loadend":i.oReq.addEventListener("loadend",function(){if(401==this.status){var t=new XMLHttpRequest;for(key in this.http._events)t.addEventListener(key,this.http._events[key]);t.withCredentials=!0,t.open(this.http._method,this.http._url),t.send()}else e.bind(this)()});break;default:i.oReq.addEventListener(t,function(){e.bind(this)()})}},Http.prototype.open=function(t,e){this._method=t,this._url=e,this.oReq.open(t,e)},Http.prototype.send=function(){this.oReq.http=this,this.oReq.send()};var registryUI={};registryUI.url=function(){return"${URL}"},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._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 t.match(new RegExp("^"+e+":"),"i")})};var registryUI={};registryUI.url=function(){return"${URL}"},registryUI.isImageRemoveActivated=!0,registryUI.catalog={},registryUI.taglist={},riot.mount("catalog"),riot.mount("taglist"),riot.mount("app");

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._events={}}Http.prototype.addEventListener=function(t,e){this._events[t]=e;var r=this;switch(t){case"loadend":r.oReq.addEventListener("loadend",function(){if(401==this.status){var t=new XMLHttpRequest;for(key in this.http._events)t.addEventListener(key,this.http._events[key]);t.withCredentials=!0,t.open(this.http._method,this.http._url),t.send()}else e.bind(this)()});break;default:r.oReq.addEventListener(t,function(){e.bind(this)()})}},Http.prototype.open=function(t,e){this._method=t,this._url=e,this.oReq.open(t,e)},Http.prototype.send=function(){this.oReq.http=this,this.oReq.send()};var registryUI={};registryUI.url=function(){return registryUI.getRegistryServer(0)},registryUI.getRegistryServer=function(t){try{var e=JSON.parse(localStorage.getItem("registryServer"));if(e instanceof Array)return isNaN(t)?e.map(function(t){return t.trim().replace(/\/*$/,"")}):e[t]}catch(r){}return isNaN(t)?[]:""},registryUI.addServer=function(t){var e=registryUI.getRegistryServer();t=t.trim().replace(/\/*$/,"");var r=e.indexOf(t);r==-1&&(e.push(t),localStorage.setItem("registryServer",JSON.stringify(e)))},registryUI.changeServer=function(t){var e=registryUI.getRegistryServer();t=t.trim().replace(/\/*$/,"");var r=e.indexOf(t);r!=-1&&(e.splice(r,1),e=[t].concat(e),localStorage.setItem("registryServer",JSON.stringify(e)))},registryUI.removeServer=function(t){var e=registryUI.getRegistryServer();t=t.trim().replace(/\/*$/,"");var r=e.indexOf(t);r!=-1&&(e.splice(r,1),localStorage.setItem("registryServer",JSON.stringify(e)))},registryUI.catalog={},registryUI.taglist={},riot.mount("add"),riot.mount("change"),riot.mount("remove"),riot.mount("menu"),riot.mount("app");
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 t.match(new RegExp("^"+e+":"),"i")})};var registryUI={};registryUI.url=function(){return registryUI.getRegistryServer(0)},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),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),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)))},registryUI.isImageRemoveActivated=!0,registryUI.catalog={},registryUI.taglist={},riot.mount("add"),riot.mount("change"),riot.mount("remove"),riot.mount("menu"),riot.mount("app");

File diff suppressed because one or more lines are too long

246
dist/scripts/tags.js vendored

File diff suppressed because one or more lines are too long

502
dist/scripts/vendor.js vendored

File diff suppressed because one or more lines are too long

25
dist/vendor.css vendored

File diff suppressed because one or more lines are too long

View File

@@ -9,19 +9,23 @@ var gulp = require('gulp');
var htmlmin = require('gulp-htmlmin');
var license = require('gulp-license');
var riot = require('gulp-riot');
var uglify = require('gulp-uglify');
var minifier = require('gulp-uglify/minifier');
var uglify = require('uglify-js-harmony');
var useref = require('gulp-useref');
gulp.task('html', function() {
var htmlFilter = filter('**/*.html');
var assets;
return gulp.src(['src/index.html'])
.pipe(useref())
.pipe(gIf(['*.js'], uglify({
preserveComments: 'license'
}))) // FIXME
.pipe(gIf(['*.js', '!*.min.js'], minifier({}, uglify))) // FIXME
.pipe(htmlFilter)
.pipe(htmlmin())
.pipe(htmlmin({
removeComments: false,
collapseWhitespace: true,
removeRedundantAttributes: true,
removeEmptyAttributes: true,
minifyJS: uglify
}))
.pipe(htmlFilter.restore())
.pipe(gulp.dest('dist'));
});
@@ -34,6 +38,7 @@ gulp.task('riot-tag', ['html'], function() {
return gulp.src('src/tags/*.tag')
.pipe(concat('tags.js'))
.pipe(riot())
.pipe(minifier({}, uglify))
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
@@ -44,9 +49,10 @@ 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))
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
@@ -59,7 +65,7 @@ gulp.task('riot-static-tag', ['html'], function() {
gulp.task('scripts-static', ['html'], function() {
return gulp.src(['src/scripts/http.js', 'src/scripts/static.js'])
.pipe(concat('script-static.js'))
.pipe(uglify())
.pipe(minifier({}, uglify))
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
@@ -72,7 +78,7 @@ gulp.task('scripts-static', ['html'], function() {
gulp.task('scripts', ['html'], function() {
return gulp.src(['src/scripts/http.js', 'src/scripts/script.js'])
.pipe(concat('script.js'))
.pipe(uglify())
.pipe(minifier({}, uglify))
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',

View File

@@ -1,6 +1,6 @@
{
"name": "docker-registry-ui",
"version": "0.1.0",
"version": "0.2.2",
"scripts": {
"build": "./node_modules/gulp/bin/gulp.js build"
},
@@ -28,6 +28,7 @@
"gulp-useref": "^3.0.0",
"material-design-lite": "^1.1",
"riot": "^2.3",
"riotgear-router": "^1.3.1"
"riotgear-router": "^1.3.1",
"uglify-js-harmony": "^2.6.2"
}
}

View File

@@ -68,6 +68,7 @@
<!-- 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/remove-image.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>

View File

@@ -16,7 +16,9 @@
*/
function Http() {
this.oReq = new XMLHttpRequest();
this.oReq.hasHeader = Http.hasHeader;
this._events = {};
this._headers = {};
}
Http.prototype.addEventListener = function(e, f) {
@@ -28,11 +30,15 @@ Http.prototype.addEventListener = function(e, f) {
self.oReq.addEventListener('loadend', function() {
if (this.status == 401) {
var req = new XMLHttpRequest();
for (key in this.http._events) {
req.addEventListener(key, this.http._events[key]);
req.open(self._method, self._url);
for (key in self._events) {
req.addEventListener(key, self._events[key]);
}
for (key in self._headers) {
req.setRequestHeader(key, self._headers[key]);
}
req.withCredentials = true;
req.open(this.http._method, this.http._url);
req.hasHeader = Http.hasHeader;
req.send();
} else {
f.bind(this)();
@@ -40,6 +46,15 @@ Http.prototype.addEventListener = function(e, f) {
});
break;
}
case 'load':
{
self.oReq.addEventListener('load', function() {
if (this.status !== 401) {
f.bind(this)();
}
});
break;
}
default:
{
self.oReq.addEventListener(e, function() {
@@ -50,6 +65,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;
@@ -57,6 +77,11 @@ Http.prototype.open = function(m, u) {
};
Http.prototype.send = function() {
this.oReq.http = this;
this.oReq.send();
};
Http.hasHeader = function(header) {
return this.getAllResponseHeaders().split('\n').some(function(h) {
return h.match(new RegExp('^' + header + ':'), 'i');
});
};

View File

@@ -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 = {};

View File

@@ -17,7 +17,8 @@
var registryUI = {}
registryUI.url = function() {
return '${URL}';
}
};
registryUI.isImageRemoveActivated = true;
registryUI.catalog = {};
registryUI.taglist = {};

View File

@@ -26,11 +26,13 @@
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;

View File

@@ -22,7 +22,7 @@
<h2 class="mdl-card__title-text">Repositories of { registryUI.url() }</h2>
</div>
<div id="catalog-spinner" hide="{ registryUI.catalog.loadend }" class="mdl-spinner mdl-js-spinner is-active section-centerd"></div>
<ul class="mdl-list">
<ul class="mdl-list" show="{ registryUI.catalog.loadend }">
<li class="mdl-list__item mdl-menu__item" style="opacity: 1;" each="{ item in registryUI.catalog.repositories }" onclick="registryUI.catalog.go('{item}');">
<span class="mdl-list__item-primary-content">
<i class="material-icons mdl-list__item-icon">send</i>
@@ -57,7 +57,8 @@
};
oReq.addEventListener('load', function () {
if (this.status == 200) {
registryUI.catalog.repositories = JSON.parse(this.responseText).repositories.sort();
registryUI.catalog.repositories = JSON.parse(this.responseText).repositories || [];
registryUI.catalog.repositories.sort();
} else if (this.status == 404) {
registryUI.catalog.createSnackbar('Server not found');
} else {
@@ -86,4 +87,4 @@
registryUI.catalog.display();
</script>
<!-- End of tag -->
</catalog>
</catalog>

63
src/tags/remove-image.tag Normal file
View File

@@ -0,0 +1,63 @@
<!--
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-image>
<a href="#" onclick="registryUI.removeImage.remove('{ opts.name }', '{ opts.tag }')">
<i class="material-icons mdl-list__item-icon">delete</i>
</a>
<script type="text/javascript">
registryUI.removeImage = registryUI.removeImage || {}
registryUI.removeImage.update = this.update;
registryUI.removeImage.remove = function (name, tag) {
var oReq = new Http();
oReq.addEventListener('loadend', function () {
registryUI.taglist.refresh();
if (this.status == 200) {
if (!this.hasHeader('Docker-Content-Digest')) {
registryUI.taglist.createSnackbar('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 () {
if (this.status == 200 || this.status == 202) {
registryUI.taglist.refresh();
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();
};
</script>
</remove-image>

View File

@@ -24,18 +24,22 @@
</a>
<h2 class="mdl-card__title-text">Tags of { registryUI.url() + '/' + registryUI.taglist.name }</h2>
</div>
<div id="taglist-spinner" hide="{ registryUI.taglist.loadend }" class="mdl-spinner mdl-js-spinner section-centerd"></div>
<table class="mdl-data-table mdl-js-data-table full-table" style="border: none;">
<div id="taglist-spinner" hide="{ registryUI.taglist.loadend }" class="mdl-spinner mdl-js-spinner section-centerd is-active"></div>
<table class="mdl-data-table mdl-js-data-table full-table" show="{ registryUI.taglist.loadend }" style="border: none;">
<thead>
<tr>
<th class="mdl-data-table__cell--non-numeric">Repository</th>
<th class="mdl-data-table__header--sorted-ascending" onclick="registryUI.taglist.reverse(this);">Tag</th>
<th class="{ registryUI.taglist.asc ? 'mdl-data-table__header--sorted-ascending' : 'mdl-data-table__header--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="mdl-data-table__cell--non-numeric">{ registryUI.taglist.name }</td>
<td>{ item }</td>
<td show="{ registryUI.isImageRemoveActivated }" >
<remove-image name={ registryUI.taglist.name } tag={ item } />
</td>
</tr>
</tbody>
</table>
@@ -55,6 +59,7 @@
: '');
var oReq = new Http();
registryUI.taglist.name = name;
registryUI.taglist.instance.update();
registryUI.taglist.createSnackbar = function (msg) {
var snackbar = document.querySelector('#error-snackbar');
registryUI.taglist.error = msg;
@@ -70,7 +75,8 @@
};
oReq.addEventListener('load', function () {
if (this.status == 200) {
registryUI.taglist.tags = JSON.parse(this.responseText).tags.sort();
registryUI.taglist.tags = JSON.parse(this.responseText).tags || [];
registryUI.taglist.tags.sort();
} else if (this.status == 404) {
registryUI.taglist.createSnackbar('Server not found');
} else {
@@ -86,6 +92,7 @@
});
oReq.open('GET', registryUI.url() + '/v2/' + name + '/tags/list');
oReq.send();
registryUI.taglist.asc = true;
}
};
registryUI.taglist.display();
@@ -94,18 +101,22 @@
componentHandler.upgradeElements(this['taglist-tag']);
});
registryUI.taglist.reverse = function (th) {
if (th.className == 'mdl-data-table__header--sorted-ascending') {
th.className = 'mdl-data-table__header--sorted-descending';
registryUI.taglist.reverse = function () {
if (registryUI.taglist.asc) {
registryUI.taglist.tags.reverse();
registryUI.taglist.asc = false;
} else {
th.className = 'mdl-data-table__header--sorted-ascending';
registryUI.taglist.tags.sort();
registryUI.taglist.asc = true;
}
registryUI.taglist.tags.reverse();
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);
};
</script>
<!-- End of tag -->
</taglist>
</taglist>