Compare commits

...

8 Commits
0.6.0 ... 0.6.1

Author SHA1 Message Date
Joxit
7446452b77 Release v0.6.1: Display image/tag count + button effect
Last release of the year !
2018-12-31 23:59:59 +01:00
Jones Magloire
d361068529 Merge pull request #65 from Joxit/feat/material-button
[material-button] Add material-button for all effective buttons
2018-12-30 22:29:24 +01:00
Joxit
b03f00ebe8 [material-button] Fix dialog buttons 2018-12-29 00:37:18 +01:00
Joxit
d0b7e7ddeb [material-button] Add material-button for all effective buttons 2018-12-28 20:44:25 +01:00
Jones Magloire
7366e709a4 Merge pull request #64 from Joxit/feat/docker-multi-stage
[docker-multi-stage] Add multi-stage-build for alpine version
2018-12-27 00:31:44 +01:00
Joxit
89e2782751 [docker-multi-stage] Add multi-stage-build for alpine version 2018-12-27 00:18:51 +01:00
Jones Magloire
3911310d89 Merge pull request #63 from Joxit/feat/image-tag-count
[feat #56] Add image and tags count
2018-12-25 23:23:33 +01:00
Joxit
d599c1c202 [feat #56] Add image and tags count 2018-12-21 23:55:23 +01:00
18 changed files with 147 additions and 79 deletions

View File

@@ -1,4 +1,7 @@
*
!dist
!bin
!nginx
!nginx
!src
!package.json
!gulpfile.js

View File

@@ -12,10 +12,22 @@
#
# 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 node:10-alpine AS builder
WORKDIR /usr/app
COPY package.json .
RUN yarn install
COPY . .
RUN yarn build
FROM nginx:alpine
LABEL maintainer="Jones MAGLOIRE @Joxit"
WORKDIR /usr/share/nginx/html/
COPY dist/ /usr/share/nginx/html/
COPY --from=builder /usr/app/dist/ /usr/share/nginx/html/

View File

@@ -29,6 +29,7 @@ This web user interface uses [Riot](https://github.com/Riot/riot) the react-like
- Show sha256 for specific tag (hover image tag)
- Display image creation date (see #49)
- Display image history (see #58)
- Display image/tag count
## Getting Started

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/style.css vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"name": "docker-registry-ui",
"version": "0.6.0",
"version": "0.6.1",
"scripts": {
"build": "./node_modules/gulp/bin/gulp.js build"
},

View File

@@ -11,7 +11,9 @@
url(fonts/MaterialIcons-Regular.ttf) format('truetype');
}
.material-icons {
material-button .content i.material-icons,
material-button[rounded=true] .content i.material-icons,
i.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
@@ -36,4 +38,9 @@
/* Support for IE. */
font-feature-settings: 'liga';
}
}
material-button .content i.material-icons,
material-button[rounded=true] .content i.material-icons {
margin: auto;
}

View File

@@ -88,6 +88,11 @@ h2 {
overflow: hidden;
}
.material-card-title-action h2 .item-count {
font-size: 0.7em;
margin-left: 1em;
}
.list {
display: block;
padding: 8px 0;
@@ -170,16 +175,21 @@ material-card table th {
text-align: left;
}
material-card material-button:hover,
material-card table tbody tr:hover {
background-color: #eee;
}
material-card material-button,
material-card table tbody tr {
transition-duration: .28s;
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
transition-property: background-color;
}
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 {
@@ -218,6 +228,7 @@ material-card table th.material-card-th-sorted-descending:before {
content: "\e5db";
}
material-button .content i.material-icons,
.material-icons {
color: #777;
}
@@ -334,10 +345,6 @@ select {
text-align: center;
}
.copy-to-clipboard a:hover {
cursor: pointer;
}
tag-history material-card {
min-height: auto;
}
@@ -377,4 +384,17 @@ tag-history-element.id div.value {
tag-history-button button {
background: none;
border: none;
}
}
material-card material-button {
max-height: 30px;
max-width: 30px;
}
material-button:hover material-waves {
background: none;
}
material-card material-button {
background-color: inherit;
}

View File

@@ -231,6 +231,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.ceil(bytes / Math.pow(1024, i)) + ' ' + sizes[i];
};
registryUI.taglist.go = function(image) {
route('taglist/' + image);
};
route.start(true);
</script>
</app>

View File

@@ -18,13 +18,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- Begin of tag -->
<material-card ref="catalog-tag" class="catalog">
<div class="material-card-title-action">
<h2>Repositories of { registryUI.name() }</h2>
<h2>
Repositories of { registryUI.name() }
<div class="item-count">{ registryUI.catalog.repositories.length } images</div>
</h2>
</div>
<div hide="{ registryUI.catalog.loadend }" class="spinner-wrapper">
<material-spinner></material-spinner>
</div>
<ul class="list highlight" show="{ registryUI.catalog.loadend }">
<li each="{ item in registryUI.catalog.repositories }" onclick="registryUI.catalog.go('{item}');">
<li each="{ item in registryUI.catalog.repositories }" onclick="registryUI.taglist.go('{item}');">
<span>
<i class="material-icons">send</i>
{ item }
@@ -60,9 +63,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
oReq.open('GET', registryUI.url() + '/v2/_catalog?n=100000');
oReq.send();
};
registryUI.catalog.go = function(image) {
route('taglist/' + image);
};
registryUI.catalog.display();
</script>
<!-- End of tag -->

View File

@@ -16,9 +16,9 @@
-->
<copy-to-clipboard>
<input ref="input" style="display: none; width: 1px; height: 1px;" value="{ this.dockerCmd }">
<a onclick="{ this.copy }" title="Copy pull command.">
<material-button waves-center="true" rounded="true" waves-color="#ddd" onclick="{ this.copy }" title="Copy pull command.">
<i class="material-icons">content_copy</i>
</a>
</material-button>
<script type="text/javascript">
this.dockerCmd = 'docker pull ' + registryUI.cleanName() + '/' + opts.image.name + ':' + opts.image.tag;
this.copy = function () {

View File

@@ -15,49 +15,51 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<remove-image>
<a href="#" title="This will delete the image."
onclick="registryUI.removeImage.remove('{ opts.image.name }', '{ opts.image.tag }')">
<material-button waves-center="true" rounded="true" waves-color="#ddd" title="This will delete the image.">
<i class="material-icons">delete</i>
</a>
</material-button>
<script type="text/javascript">
registryUI.removeImage = registryUI.removeImage || {};
registryUI.removeImage.remove = function(name, tag) {
const oReq = new Http();
oReq.addEventListener('loadend', function() {
registryUI.taglist.refresh();
if (this.status == 200) {
if (!this.hasHeader('Docker-Content-Digest')) {
registryUI.errorSnackbar('You need to add Access-Control-Expose-Headers: [\'Docker-Content-Digest\'] in your server configuration.');
return;
}
const digest = this.getResponseHeader('Docker-Content-Digest');
const oReq = new Http();
oReq.addEventListener('loadend', function() {
if (this.status == 200 || this.status == 202) {
registryUI.taglist.refresh();
registryUI.snackbar('Deleting ' + name + ':' + tag + ' image. Run `registry garbage-collect config.yml` on your registry');
} else if (this.status == 404) {
registryUI.errorSnackbar('Digest not found');
} else {
registryUI.snackbar(this.responseText);
const self = this;
this.on('mount', function() {
this.tags['material-button'].root.onclick = function() {
const name = self.opts.image.name;
const tag = self.opts.image.tag;
const oReq = new Http();
oReq.addEventListener('loadend', function() {
registryUI.taglist.go(name);
if (this.status == 200) {
if (!this.hasHeader('Docker-Content-Digest')) {
registryUI.errorSnackbar('You need to add Access-Control-Expose-Headers: [\'Docker-Content-Digest\'] in your server configuration.');
return;
}
});
oReq.open('DELETE', registryUI.url() + '/v2/' + name + '/manifests/' + digest);
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
oReq.addEventListener('error', function() {
registryUI.errorSnackbar('An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].');
});
oReq.send();
} else if (this.status == 404) {
registryUI.errorSnackbar('Manifest for ' + name + ':' + tag + ' not found');
} else {
registryUI.snackbar(this.responseText);
}
});
oReq.open('HEAD', registryUI.url() + '/v2/' + name + '/manifests/' + tag);
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
oReq.send();
};
const digest = this.getResponseHeader('Docker-Content-Digest');
const oReq = new Http();
oReq.addEventListener('loadend', function() {
if (this.status == 200 || this.status == 202) {
registryUI.taglist.display()
registryUI.snackbar('Deleting ' + name + ':' + tag + ' image. Run `registry garbage-collect config.yml` on your registry');
} else if (this.status == 404) {
registryUI.errorSnackbar('Digest not found');
} else {
registryUI.snackbar(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.errorSnackbar('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.errorSnackbar('Manifest for ' + name + ':' + tag + ' not found');
} else {
registryUI.snackbar(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

@@ -15,13 +15,13 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<tag-history-button>
<button ref="button" title="This will show the history of given tag">
<material-button ref="button" title="This will show the history of given tag" waves-center="true" rounded="true" waves-color="#ddd">
<i class="material-icons">history</i>
</button>
</material-button>
<script>
this.on('mount', function() {
const self = this;
this.refs.button.onclick = function() {
this.refs.button.root.onclick = function() {
registryUI.taghistory._image = self.opts.image;
registryUI.taghistory.go(self.opts.image.name, self.opts.image.tag);
};

View File

@@ -17,9 +17,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<tag-history>
<material-card ref="tag-history-tag" class="tag-history">
<div class="material-card-title-action">
<a href="#!taglist/{registryUI.taghistory.image}">
<material-button waves-center="true" rounded="true" waves-color="#ddd">
<i class="material-icons">arrow_back</i>
</a>
</material-button>
<h2>
History of { registryUI.taghistory.image }:{ registryUI.taghistory.tag } <i class="material-icons">history</i>
</h2>
@@ -125,6 +125,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
image.on('blobs', processBlobs);
};
this.on('mount', function() {
self.refs['tag-history-tag'].tags['material-button'].root.onclick = function() {
registryUI.taglist.go(registryUI.taghistory.image);
};
});
registryUI.taghistory.display();
self.update();
</script>

View File

@@ -18,10 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- Begin of tag -->
<material-card ref="taglist-tag" class="taglist">
<div class="material-card-title-action">
<a href="#!" onclick="registryUI.home();">
<material-button waves-center="true" rounded="true" waves-color="#ddd" onclick="registryUI.home();">
<i class="material-icons">arrow_back</i>
</a>
<h2>Tags of { registryUI.name() + '/' + registryUI.taglist.name }</h2>
</material-button>
<h2>
Tags of { registryUI.name() + '/' + registryUI.taglist.name }
<div class="item-count">{ registryUI.taglist.tags.length } tags</div>
</h2>
</div>
<div hide="{ registryUI.taglist.loadend }" class="spinner-wrapper">
<material-spinner></material-spinner>
@@ -113,9 +116,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
}
registryUI.taglist.instance.update();
};
registryUI.taglist.refresh = function() {
route(registryUI.taglist.name);
};
</script>
<!-- End of tag -->
</taglist>

View File

@@ -12,6 +12,18 @@
#
# 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 node:10-alpine AS builder
WORKDIR /usr/app
COPY package.json .
RUN yarn install
COPY . .
RUN yarn build
FROM nginx:alpine
LABEL maintainer="Jones MAGLOIRE @Joxit"
@@ -19,8 +31,8 @@ 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/docker-registry-ui-static.js /usr/share/nginx/html/scripts/docker-registry-ui.js
COPY --from=builder /usr/app/dist/ /usr/share/nginx/html/
COPY --from=builder /usr/app/dist/scripts/docker-registry-ui-static.js /usr/share/nginx/html/scripts/docker-registry-ui.js
COPY bin/entrypoint /bin
ENTRYPOINT entrypoint