Compare commits

...

78 Commits
0.3.8 ... 1.0.2

Author SHA1 Message Date
Joxit
476a441715 Release v1.0.2: fix bug introduced by #67
Closes #69
2019-01-25 08:18:23 +01:00
Joxit
8055baa951 Release v1.0.1: fix DELETE_IMAGES #67 2019-01-16 21:26:40 +01:00
Jones Magloire
53557b2591 Merge pull request #67 from bluethon/master
fix - DELETE_IMAGES
2019-01-15 22:30:48 +01:00
bluethon
ea79fd621f fix - DELETE_IMAGES 2019-01-15 22:29:23 +01:00
Joxit
2f014c1d8f Release v1.0.0: Image aggregation and catalog UI revamped 2019-01-09 22:56:05 +01:00
Jones Magloire
e6d9f11b83 Merge pull request #66 from Joxit/feat/56-image-aggregation
Image aggregation
2019-01-08 00:25:30 +01:00
Joxit
a36809408c [feat #56] Add image count for aggregated images 2019-01-07 07:41:08 +01:00
Joxit
7e2e4b6010 [feat #56] Add expand_more icon for aggregated images 2019-01-04 22:02:22 +01:00
Joxit
605e8a8d8e [feat #56] Add level aggregation with animation 2019-01-03 23:33:59 +01:00
Joxit
ac5a70c9df [feat #56] Create new tag catalog-elements with wave effect 2019-01-02 20:55:52 +01:00
Joxit
9b120bb6d5 [feat #56] Update catalog, use material-cards instead of list 2019-01-01 22:46:23 +01:00
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
Joxit
1e185b4034 Release v0.6.0 new history page 2018-12-20 00:01:36 +01:00
Jones Magloire
03e4d6b8c5 Merge pull request #61 from Joxit/fix/image-history
[Fix #59] image history, use v2 manifest instead of v1
2018-12-19 21:32:41 +01:00
Joxit
fe2fcc1104 [fix-image-history] Add some query optimization 2018-12-18 23:25:57 +01:00
Joxit
581975b99e [fix-image-history] Create move history-elements in its own tag tag-history-element
- Add id and ExposedPort
- Better rendering for arrays
- Add more const instead of var
2018-12-17 00:38:25 +01:00
Joxit
ed1e928bf3 [fix-image-history] Add config values for top level history and improve element values rendering
Move icons to js mapping function instead of CSS
In dockerfile, author cand be randered through `MAINTAINER xxx` or `LABEL maintainer=xxx`
2018-12-16 00:06:35 +01:00
Joxit
ffd0a7c628 [fix-image-history] Move byteToSize in app.tag and use it in image history. Better render for created_by 2018-12-14 21:57:42 +01:00
Joxit
cb50dd42d8 [fix-image-history] Sort elements attributes (os, id, created...) 2018-12-13 20:05:28 +01:00
Joxit
5aaedfb0aa [fix-image-history] Move from v1 manifest to manifest v2 2018-12-12 22:41:20 +01:00
Joxit
7cb06d57ee [fix-image-history] Add blobs in DockerImage image, this will superseds current tag-history call 2018-12-11 23:09:22 +01:00
Joxit
a6b6c1531b [docker svg] Change docker logo for a simplified one
Size changed from 12.5Kio to 4.5Kio
2018-12-09 17:34:57 +01:00
Jones Magloire
e89a0112ae Merge pull request #58 from lennartblom/master
New Feature: Overview of tag history content
2018-12-08 15:49:55 +01:00
Joxit
4aa016090c Add more const 2018-12-07 19:46:08 +01:00
Lennart Blom
7163150cf5 Optimize GUI elements and HTTP logic for content preperation 2018-12-06 22:01:35 +01:00
Lennart Blom
c176a082d9 Change wording of headline 2018-12-06 21:13:13 +01:00
Lennart Blom
67ad46a851 Change wording of headline 2018-12-06 21:12:55 +01:00
Lennart Blom
06a11a706b Merge remote-tracking branch 'origin/master' 2018-12-06 21:10:51 +01:00
Lennart Blom
04259ab43d Change wording of headline 2018-12-06 21:10:12 +01:00
Jones Magloire
896031e894 Merge pull request #44 from onizet/master
Improve documentation: traefik sample
2018-12-05 21:46:08 +01:00
Joxit
8643fb16ff Add contributors section 2018-12-03 20:26:41 +01:00
Olivier Nizet
becf8bf887 Move documentation about traefik as a full runnable example 2018-12-03 20:12:33 +01:00
Lennart Blom
472b485455 Remove test logging 2018-12-03 16:48:21 +01:00
Olivier Nizet
637f7635dc Example behind Traefik 2018-12-03 16:45:07 +01:00
Lennart Blom
ae78b2d355 Fix navigation bug
The href="#" was troubling the view that the history appeared just for
a few milliseconds... the button with given onclick event does work now.
2018-12-03 16:36:51 +01:00
Lennart Blom
249d021152 Formate code 2018-12-02 19:15:27 +01:00
Lennart Blom
e3437daefe Use toLocaleString() instead of custom date format 2018-12-02 18:58:04 +01:00
Lennart Blom
246369fdec Move go function to taghistory-namespace within app.tag 2018-12-02 18:56:34 +01:00
Lennart Blom
b94a65d79b Remove space in front of function brackets 2018-12-02 18:52:34 +01:00
Lennart Blom
bf975cd29b Formate code 2018-12-01 21:29:40 +01:00
Lennart Blom
292336c1b6 Format code 2018-12-01 21:23:48 +01:00
Lennart Blom
6d849a9e95 Add information text about the history elements 2018-12-01 21:11:09 +01:00
Lennart Blom
86f78f7604 Remove instance usage of registryUI.taghistory 2018-12-01 21:00:27 +01:00
Lennart Blom
43af3ffdcf Remove logging 2018-12-01 20:52:53 +01:00
Lennart Blom
f364564d0e Remove logging 2018-12-01 20:51:55 +01:00
Lennart Blom
39cf28e562 Remove logging and format code 2018-12-01 20:50:55 +01:00
Lennart Blom
66fe613b6f Optimize format 2018-12-01 20:50:16 +01:00
Lennart Blom
cf883cbfc7 Optimize routing and code format 2018-12-01 20:49:59 +01:00
Lennart Blom
5bb7dfce7d Optimize date format 2018-12-01 20:32:33 +01:00
Lennart Blom
6d798ca75f Styling of history element headlines 2018-12-01 20:19:07 +01:00
Lennart Blom
f6bc4df11f Styling elements and getting structure into data view 2018-12-01 11:44:51 +01:00
Lennart Blom
8ab6ecbf19 Define dynamic parsing of history elements 2018-12-01 11:03:23 +01:00
Lennart Blom
c857bd8db6 Add functionality for new tag history view 2018-12-01 00:24:58 +01:00
Lennart Blom
fe5e962488 Format code 2018-12-01 00:24:28 +01:00
Lennart Blom
96a926652b Include tag-history-button 2018-12-01 00:23:38 +01:00
Lennart Blom
3f860cd0b5 Define taghistory object 2018-12-01 00:23:14 +01:00
Lennart Blom
3b1b6f2e72 Add new "tag-history" column for tag overview 2018-11-30 22:33:34 +01:00
Lennart Blom
20861bbb0d Add .idea directory to .ignore 2018-11-30 22:10:05 +01:00
Joxit
09b77201be [Fixes #52] image-date tag was missing in static version => v0.5.1 2018-11-23 22:22:17 +01:00
Joxit
2f5e0dd307 Upgrade to v0.5.0 2018-11-20 21:56:14 +01:00
Jones Magloire
08c5eaee7e Merge pull request #51 from Joxit/feat/creation-date
[feat #49] Add creation date to taglist
2018-11-18 12:09:51 +01:00
Joxit
4cb79a670f [feat #49] Add creation date to taglist 2018-11-16 22:07:49 +01:00
Joxit
5173110883 Add new example for https://github.com/Joxit/docker-registry-ui/issues/26#issuecomment-415995589 2018-08-25 23:33:33 +02:00
Joxit
d3e93f7064 Fix demo for v0.4.0, sorry 😕 2018-08-04 18:58:36 +02:00
Joxit
6221958c78 Upgrade to v0.4.0
Merge tags and scripts; now it will be `docker-registry-ui.js` and `docker-registry-ui-static.js`
New sort for tags; will use numerical sort when it is possible
2018-07-24 00:05:45 +02:00
Jones Magloire
05c2cf2425 Merge pull request #46 from Joxit/new-tag-sort
New tag sort using digits
2018-07-23 20:36:04 +02:00
Joxit
82efd33c14 [new-tag-sort] Sort on numbers when there are instead of characters 2018-07-22 15:55:36 +02:00
Joxit
13936aadb1 [examples] Add ui-as-proxy example 2018-07-19 21:59:07 +02:00
Joxit
6bff056086 Add arm64v8 support 2018-07-18 22:59:07 +02:00
Joxit
b28fe68dcd Upgrade v0.3.10: Improve error messages 2018-07-15 20:56:16 +02:00
Joxit
d6523a4205 Upgrade v0.3.9: Fix typo 2018-06-21 23:14:20 +02:00
74 changed files with 1453 additions and 463 deletions

View File

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

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.project
node_modules
package-lock.json
registry-data
.idea

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

@@ -11,7 +11,7 @@ This web user interface uses [Riot](https://github.com/Riot/riot) the react-like
## [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")
![preview](https://raw.github.com/Joxit/docker-registry-ui/master/docker-registry-ui.gif "Preview of Docker Registry UI")
## Features
@@ -24,9 +24,13 @@ This web user interface uses [Riot](https://github.com/Riot/riot) the react-like
- 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
- Alpine and Debian based images with supports for arm32v7 and arm64v8
- Copy `docker pull` command to clipbloard
- Show sha256 for specific tag (hover image tag)
- Display image creation date (see #49)
- Display image history (see #58)
- Display image/tag count
- Image aggregation (see #56)
## Getting Started
@@ -122,6 +126,8 @@ 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
```
There are some examples with [docker-compose](https://docs.docker.com/compose/) and docker-registry-ui as proxy [here](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-proxy/) or docker-registry-ui as standalone [here](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-standalone/).
## Using CORS
Your server should be configured to accept CORS.

View File

@@ -20,8 +20,7 @@ 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 dist/scripts/docker-registry-ui-static.js /usr/share/nginx/html/scripts/docker-registry-ui.js
COPY bin/entrypoint /bin
ENTRYPOINT entrypoint

26
arm64v8-static.dockerfile Normal file
View File

@@ -0,0 +1,26 @@
# 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 arm64v8/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/docker-registry-ui-static.js /usr/share/nginx/html/scripts/docker-registry-ui.js
COPY bin/entrypoint /bin
ENTRYPOINT entrypoint

21
arm64v8.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 arm64v8/nginx
LABEL maintainer="Jones MAGLOIRE @Joxit"
WORKDIR /usr/share/nginx/html/
COPY dist/ /usr/share/nginx/html/

View File

@@ -1,10 +1,10 @@
#!/bin/sh
$@
sed -i "s,\${URL},${URL}," scripts/script.js
sed -i "s,\${REGISTRY_TITLE},${REGISTRY_TITLE}," scripts/script.js
sed -i "s,\${URL},${URL}," scripts/docker-registry-ui.js
sed -i "s,\${REGISTRY_TITLE},${REGISTRY_TITLE}," scripts/docker-registry-ui.js
if [ -z "${DELETE_IMAGES}" ] || [ "${DELETE_IMAGES}" = false ] ; then
sed -i "s/registryUI.isImageRemoveActivated *= *[^,;]*/registryUI.isImageRemoveActivated=false/" scripts/script.js
sed -i -r "s/(isImageRemoveActivated[:=])[^,;]*/\1false/" scripts/docker-registry-ui.js
fi
if [ -n "${REGISTRY_URL}" ] ; then

View File

@@ -20,8 +20,7 @@ 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 dist/scripts/docker-registry-ui-static.js /usr/share/nginx/html/scripts/docker-registry-ui.js
COPY bin/entrypoint /bin
ENTRYPOINT entrypoint

View File

@@ -1,5 +1,5 @@
<!--
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
@@ -28,8 +28,7 @@
<body>
<app></app>
<script src="../dist/scripts/vendor.js"></script>
<script src="../dist/scripts/tags.js"></script>
<script src="../dist/scripts/script.js"></script>
<script src="../dist/scripts/docker-registry-ui.js"></script>
<script>
(function(i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
dist/images/docker-logo.svg vendored Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg2" x="0px" y="0px" viewBox="-5.724 -43.601 730 600" fill="#777"><path d="m595.942,422.343 c -7.543,0.119 -13.562,6.331 -13.443,13.875 0.119,7.544 6.332,13.562 13.875,13.443 7.495,-0.118 13.494,-6.254 13.445,-13.75 -0.085,-7.578 -6.297,-13.652 -13.875,-13.568 0,0 -10e-4,0 0,0 m 0,24.398 c -5.975,0.272 -11.039,-4.352 -11.311,-10.326 -0.271,-5.976 4.352,-11.04 10.327,-11.312 5.975,-0.271 11.039,4.352 11.311,10.327 0.01,0.19 0.013,0.382 0.011,0.573 0.204,5.723 -4.27,10.527 -9.992,10.731 -0.115,0.005 -0.23,0.007 -0.346,0.007"/><path d="m599.081,436.342 v -0.185 c 1.512,-0.292 2.65,-1.544 2.8,-3.076 0.057,-1.175 -0.432,-2.311 -1.323,-3.077 -1.445,-0.765 -3.076,-1.106 -4.707,-0.984 -1.743,-0.024 -3.484,0.12 -5.2,0.431 v 13.538 h 3.077 v -5.446 h 1.477 c 1.754,0 2.554,0.646 2.83,2.154 0.184,1.143 0.536,2.252 1.047,3.292 h 3.415 c -0.53,-1.062 -0.873,-2.207 -1.016,-3.385 -0.138,-1.473 -1.088,-2.744 -2.462,-3.292 m -3.723,-0.985 h -1.508 v -3.908 c 0.583,-0.069 1.172,-0.069 1.754,0 1.97,0 2.893,0.831 2.893,2.062 0,1.231 -1.415,2 -3.076,2"/><path d="M707.494,193.557c-1.938-1.539-20.029-15.199-58.181-15.199c-10.074,0.044-20.127,0.908-30.061,2.584 c-7.384-50.612-49.228-75.288-51.104-76.395l-10.245-5.908l-6.738,9.723c-8.438,13.061-14.598,27.459-18.214,42.582 c-6.831,28.891-2.677,56.027,11.999,79.226c-17.722,9.876-46.151,12.307-51.904,12.522H22.367 c-12.294,0.017-22.27,9.952-22.337,22.245c-0.549,41.234,6.437,82.222,20.614,120.946c16.214,42.521,40.336,73.842,71.719,93.01 c35.167,21.537,92.302,33.844,157.067,33.844c29.258,0.092,58.461-2.556,87.226-7.907c39.986-7.342,78.463-21.318,113.839-41.352 c29.149-16.88,55.383-38.354,77.688-63.596c37.29-42.213,59.505-89.226,76.026-131.007c2.215,0,4.431,0,6.584,0 c40.828,0,65.935-16.338,79.78-30.029c9.201-8.732,16.384-19.369,21.045-31.167l2.923-8.553L707.494,193.557z"/><path d="M65.995,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.476-5.538 c-0.01,0-0.021,0-0.031,0H65.995c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C60.488,226.443,62.953,228.909,65.995,228.909L65.995,228.909"/><path d="M152.913,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.059,0-5.538,2.479-5.538,5.538v56.181C147.392,226.448,149.866,228.909,152.913,228.909"/><path d="M241.153,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C235.646,226.443,238.112,228.909,241.153,228.909L241.153,228.909"/><path d="M328.348,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C322.841,226.443,325.307,228.909,328.348,228.909L328.348,228.909"/><path d="M152.913,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.046,0-5.521,2.46-5.538,5.507v56.181C147.392,145.597,149.861,148.066,152.913,148.083"/><path d="M241.153,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C235.646,145.591,238.107,148.066,241.153,148.083"/><path d="M328.348,148.083h63.073c3.052-0.017,5.521-2.486,5.538-5.538V86.364c-0.017-3.047-2.491-5.507-5.538-5.507h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C322.841,145.591,325.302,148.066,328.348,148.083"/><path d="M328.348,67.227h63.073c3.047,0,5.521-2.461,5.538-5.507V5.507C396.942,2.46,394.468,0,391.421,0h-63.073 c-3.042,0-5.507,2.465-5.507,5.507l0,0v56.212C322.841,64.761,325.307,67.227,328.348,67.227"/><path d="M416.312,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.041,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C410.805,226.443,413.271,228.909,416.312,228.909"/></svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

4
dist/index.html vendored
View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016 Jones Magloire @Joxit
Copyright (C) 2016-2019 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
@@ -13,4 +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><app></app><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><app></app><script src="scripts/vendor.js"></script><script src="scripts/docker-registry-ui.js"></script></body></html>

File diff suppressed because one or more lines are too long

18
dist/scripts/docker-registry-ui.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,18 +0,0 @@
/*!
* docker-registry-ui
* 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/>.
*/
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,18 +0,0 @@
/*!
* docker-registry-ui
* 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/>.
*/
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

18
dist/scripts/tags.js vendored

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

File diff suppressed because one or more lines are too long

BIN
docker-registry-ui.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 KiB

View File

@@ -0,0 +1,22 @@
# Traefik example
Host the docker registry ui behind [traefik](http://traefik.io) with Docker Swarm mode.
## How to run
Open a terminal console and type
```bash
bash run-swarm.sh
```
## Authentication
The registry is protected via __Basic authentication__ but feel free to use wathever you like.
In this sample, credentials are: **admin / admin**.
To generate a new password for basic auth, run the command: `htpasswd -nb username password`.
## Contributors
Thank you [@onizet](https://github.com/onizet) for this example.

View File

View File

@@ -0,0 +1,33 @@
version: '3.1'
services:
registry:
image: registry:2.6.2
volumes:
- /opt/docker-registry:/var/lib/registry
environment:
- REGISTRY_HTTP_SECRET=my_registry_secret
- REGISTRY_STORAGE_DELETE_ENABLED=true
deploy:
placement:
constraints: [node.role == manager]
ui:
image: joxit/docker-registry-ui:static
environment:
- DELETE_IMAGES=true
- REGISTRY_TITLE=My Private Docker Registry
- REGISTRY_URL=http://docker-registry_registry:5000
depends_on: ['registry']
networks: ['proxy', 'default']
deploy:
labels:
traefik.backend: 'registry.mydomain.com'
traefik.frontend.rule: 'Host:registry.mydomain.com'
traefik.enable: 'true'
traefik.port: 80
traefik.docker.network: 'traefik-net'
traefik.frontend.auth.basic: 'admin:$apr1$XXrpwZre$ItZSXpoeB6bdPLCGT7eXG0'
traefik.frontend.passHostHeader: 'true'
networks:
proxy: {external: {name: 'traefik-net'}}

42
examples/traefik/run-swarm.sh Executable file
View File

@@ -0,0 +1,42 @@
#!/usr/bin/env bash
if ! [[ `docker network ls | grep "traefik-net"` ]] &>/dev/null; then
echo "Setup traefik network"
docker network create --driver=overlay --attachable traefik-net
fi
if ! [[ `docker service ls | grep "traefik2"` ]] &>/dev/null; then
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# ensure acme.json wich will contains the letsencrypt certificates
touch "$dir"/acme.json && chmod 600 "$dir"/acme.json
docker service create --name traefik2 --detach=false \
--constraint node.role==manager \
--update-parallelism 1 --update-delay 10s \
--mode global \
--publish 80:80 \
--publish 443:443 \
--read-only \
--mount type=bind,source="$(pwd)"/acme.json,target=/etc/traefik/acme.json \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--network traefik-net \
traefik:1.7.4-alpine \
--entrypoints='Name:http Address::80 Redirect.EntryPoint:https' \
--entrypoints='Name:https Address::443 TLS' \
--defaultentrypoints=http,https \
--acme \
--acme.storage=/etc/traefik/acme.json \
--acme.entryPoint=https \
--acme.httpChallenge.entryPoint=http \
--acme.email=contact@mydomain.com \
--docker \
--docker.swarmMode \
--docker.domain=mydomain.com \
--docker.exposedByDefault=false \
--docker.watch \
--api
fi
docker stack deploy --compose-file docker-compose-swarm.yml docker-registry

View File

@@ -0,0 +1,21 @@
# Docker Registry Static as proxy example
You can set up the static user interface as proxy in several ways.
If you want to populate your registry, use `populate.sh` script.
The interface and the docker registry will be accessible with <http://localhost>.
The simplest way is with `simple.yml` docker-compose file.
```sh
docker-compose -f simple.yml up -d
./populate.sh
```
You can add some credentials to access your registry wit `credentials.yml` docker-compose file.
Credentials for this example are login: `registry` and password: `ui` using bcrypt.
```sh
docker-compose -f credentials.yml up -d
./populate.sh
```

View File

@@ -0,0 +1,25 @@
version: '2.0'
services:
registry:
image: registry:2.6.2
volumes:
- ./registry-data:/var/lib/registry
- ./registry-config/credentials.yml:/etc/docker/registry/config.yml
- ./registry-config/htpasswd:/etc/docker/registry/htpasswd
networks:
- registry-ui-net
ui:
image: joxit/docker-registry-ui:static
ports:
- 80:80
environment:
- REGISTRY_TITLE=My Private Docker Registry
- REGISTRY_URL=http://registry:5000
depends_on:
- registry
networks:
- registry-ui-net
networks:
registry-ui-net:

View File

@@ -0,0 +1,17 @@
#!/bin/bash
docker tag joxit/docker-registry-ui:static localhost/joxit/docker-registry-ui:static
docker tag joxit/docker-registry-ui:static localhost/joxit/docker-registry-ui:0.3
docker tag joxit/docker-registry-ui:static localhost/joxit/docker-registry-ui:0.3.0
docker tag joxit/docker-registry-ui:static localhost/joxit/docker-registry-ui:0.3.0-static
docker tag joxit/docker-registry-ui:static localhost/joxit/docker-registry-ui:0.3-static
docker push localhost/joxit/docker-registry-ui
docker tag registry:2.6.2 localhost/registry:latest
docker tag registry:2.6.2 localhost/registry:2.6.2
docker tag registry:2.6.2 localhost/registry:2.6
docker tag registry:2.6.2 localhost/registry:2.6.0
docker tag registry:2.6.2 localhost/registry:2
docker push localhost/registry

View File

@@ -0,0 +1,25 @@
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://localhost']
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

View File

@@ -0,0 +1 @@
registry:$2y$11$1bmuJLK8HrQl5ACS/WeqRuJLUArUZfUcP2R23asmozEpfN76.pCHy

View File

@@ -0,0 +1,23 @@
version: '2.0'
services:
registry:
image: registry:2.6.2
volumes:
- ./registry-data:/var/lib/registry
networks:
- docker-registry-ui
ui:
image: joxit/docker-registry-ui:static
ports:
- 80:80
environment:
- REGISTRY_TITLE=My Private Docker Registry
- REGISTRY_URL=http://registry:5000
depends_on:
- registry
networks:
- docker-registry-ui
networks:
registry-ui-net:

View File

@@ -0,0 +1,22 @@
# Docker Registry Static as standalone example
You can set up the static user interface as standalone in several ways.
If you want to populate your registry, use `populate.sh` script.
The interface will be accessible with <http://localhost>.
Your docker registry will be accessible with <http://localhost:5000>.
The simplest way is with `simple.yml` docker-compose file.
```sh
docker-compose -f simple.yml up -d
./populate.sh
```
You can add some credentials to access your registry wit `credentials.yml` docker-compose file.
Credentials for this example are login: `registry` and password: `ui` using bcrypt.
```sh
docker-compose -f credentials.yml up -d
./populate.sh
```

View File

@@ -0,0 +1,20 @@
version: '2.0'
services:
registry:
image: registry:2.6.2
ports:
- 5000:5000
volumes:
- ./registry-data:/var/lib/registry
- ./registry-config/credentials.yml:/etc/docker/registry/config.yml
- ./registry-config/htpasswd:/etc/docker/registry/htpasswd
ui:
image: joxit/docker-registry-ui:static
ports:
- 80:80
environment:
- REGISTRY_TITLE=My Private Docker Registry
- URL=http://localhost:5000
depends_on:
- registry

View File

@@ -0,0 +1,17 @@
#!/bin/bash
docker tag joxit/docker-registry-ui:static localhost:5000/joxit/docker-registry-ui:static
docker tag joxit/docker-registry-ui:static localhost:5000/joxit/docker-registry-ui:0.3
docker tag joxit/docker-registry-ui:static localhost:5000/joxit/docker-registry-ui:0.3.0
docker tag joxit/docker-registry-ui:static localhost:5000/joxit/docker-registry-ui:0.3.0-static
docker tag joxit/docker-registry-ui:static localhost:5000/joxit/docker-registry-ui:0.3-static
docker push localhost:5000/joxit/docker-registry-ui
docker tag registry:2.6.2 localhost:5000/registry:latest
docker tag registry:2.6.2 localhost:5000/registry:2.6.2
docker tag registry:2.6.2 localhost:5000/registry:2.6
docker tag registry:2.6.2 localhost:5000/registry:2.6.0
docker tag registry:2.6.2 localhost:5000/registry:2
docker push localhost:5000/registry

View File

@@ -0,0 +1,25 @@
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://localhost']
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

View File

@@ -0,0 +1 @@
registry:$2y$11$1bmuJLK8HrQl5ACS/WeqRuJLUArUZfUcP2R23asmozEpfN76.pCHy

View File

@@ -0,0 +1,21 @@
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://localhost']
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']

View File

@@ -0,0 +1,19 @@
version: '2.0'
services:
registry:
image: registry:2.6.2
ports:
- 5000:5000
volumes:
- ./registry-data:/var/lib/registry
- ./registry-config/simple.yml:/etc/docker/registry/config.yml
ui:
image: joxit/docker-registry-ui:static
ports:
- 80:80
environment:
- REGISTRY_TITLE=My Private Docker Registry
- URL=http://localhost:5000
depends_on:
- registry

View File

@@ -1,24 +1,40 @@
'use strict';
var cleanCSS = require('gulp-clean-css');
var concat = require('gulp-concat');
var del = require('del');
var filter = require('gulp-filter');
var fs = require('fs');
var gIf = require('gulp-if');
var gulp = require('gulp');
var htmlmin = require('gulp-htmlmin');
var license = require('gulp-license');
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');
const cleanCSS = require('gulp-clean-css');
const concat = require('gulp-concat');
const del = require('del');
const filter = require('gulp-filter');
const gIf = require('gulp-if');
const gulp = require('gulp');
const parallel = gulp.parallel;
const series = gulp.series;
const htmlmin = require('gulp-htmlmin');
const license = require('gulp-license');
const riot = require('gulp-riot');
const uglify = require('uglify-es');
const minifier = require('gulp-uglify/composer')(uglify);
const useref = require('gulp-useref');
const injectVersion = require('gulp-inject-version');
const merge = require('stream-series');
gulp.task('html', function() {
const allTags = ['src/tags/*.tag', 'src/tags/dialogs/*.tag'];
const allScripts = [
'src/scripts/http.js',
'src/scripts/script.js'
];
const staticTags = ['src/tags/*.tag'];
const staticScripts = [
'src/scripts/http.js',
'src/scripts/static.js'
];
function html() {
var htmlFilter = filter('**/*.html', {restore: true});
return gulp.src(['src/index.html'])
.pipe(useref())
.pipe(gIf(['*.js', '!*.min.js'], minifier({}, uglify))) // FIXME
.pipe(gIf(['*.js', '!*.min.js'], minifier())) // FIXME
.pipe(htmlFilter)
.pipe(htmlmin({
removeComments: false,
@@ -29,75 +45,47 @@ gulp.task('html', function() {
}))
.pipe(htmlFilter.restore)
.pipe(gulp.dest('dist'));
});
};
gulp.task('clean', function(done) {
function clean() {
return del(['dist']);
});
};
gulp.task('riot-tag', ['html'], function() {
return gulp.src('src/tags/*.tag')
.pipe(concat('tags.js'))
.pipe(riot())
.pipe(minifier({}, uglify))
function appStatic() {
return merge(gulp.src(staticScripts), gulp.src(staticTags).pipe(riot()))
.pipe(concat('docker-registry-ui-static.js'))
.pipe(minifier())
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
year: '2016-2018',
year: '2016-2019',
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/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))
function app() {
return merge(gulp.src(allScripts), gulp.src(allTags).pipe(riot()))
.pipe(concat('docker-registry-ui.js'))
.pipe(minifier())
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
year: '2016-2018',
year: '2016-2019',
organization: 'Jones Magloire @Joxit'
}))
.pipe(injectVersion())
.pipe(gulp.dest('dist/scripts'));
});
};
gulp.task('scripts-static', ['html'], function() {
return gulp.src(['src/scripts/http.js', 'src/scripts/static.js'])
.pipe(concat('script-static.js'))
.pipe(minifier({}, uglify))
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
year: '2016-2018',
organization: 'Jones Magloire @Joxit'
}))
.pipe(gulp.dest('dist/scripts'));
});
gulp.task('scripts', ['html'], function() {
return gulp.src(['src/scripts/http.js', 'src/scripts/script.js'])
.pipe(concat('script.js'))
.pipe(minifier({}, uglify))
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
year: '2016-2018',
organization: 'Jones Magloire @Joxit'
}))
.pipe(gulp.dest('dist/scripts'));
});
gulp.task('vendor', ['html'], function() {
function vendor() {
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'));
});
};
gulp.task('styles', ['html'], function() {
function styles() {
return gulp.src(['src/*.css'])
.pipe(concat('style.css'))
.pipe(cleanCSS({
@@ -106,22 +94,28 @@ gulp.task('styles', ['html'], function() {
.pipe(license('agpl3', {
tiny: false,
project: 'docker-registry-ui',
year: '2016-2018',
year: '2016-2019',
organization: 'Jones Magloire @Joxit'
}))
.pipe(gulp.dest('dist/'));
});
};
gulp.task('fonts', function() {
function fonts() {
return gulp.src('src/fonts/*')
.pipe(filter('**/*.{otf,eot,svg,ttf,woff,woff2}'))
.pipe(gulp.dest('dist/fonts'));
});
};
gulp.task('sources', ['riot-tag', 'riot-static-tag', 'scripts', 'vendor', 'scripts-static', 'styles'], function() {
gulp.start();
});
function svgs() {
return gulp.src(['src/images/*.svg'])
.pipe(htmlmin({
removeComments: false,
collapseWhitespace: true,
removeRedundantAttributes: true,
removeEmptyAttributes: true,
minifyJS: uglify
}))
.pipe(gulp.dest('dist/images/'));
};
gulp.task('build', ['clean'], function() {
gulp.start(['sources', 'fonts']);
});
exports.build = series(clean, html, parallel(fonts, styles, vendor, app, appStatic, svgs));

View File

@@ -1,6 +1,6 @@
{
"name": "docker-registry-ui",
"version": "0.3.8",
"version": "1.0.2",
"scripts": {
"build": "./node_modules/gulp/bin/gulp.js build"
},
@@ -14,21 +14,21 @@
"dependencies": {},
"devDependencies": {
"del": "^3.0.0",
"gulp": "^3.9",
"gulp-clean-css": "^3.9.4",
"gulp": "^4.0",
"gulp-clean-css": "^4.0.0",
"gulp-concat": "^2.6.0",
"gulp-filter": "^5.1.0",
"gulp-htmlmin": "^3.0.0",
"gulp-htmlmin": "^5.0.1",
"gulp-if": "^2.0.0",
"gulp-inject-version": "^1.0.1",
"gulp-license": "^1.1.0",
"gulp-riot": "^1.1.4",
"gulp-uglify": "^2.1.2",
"gulp-useref": "^3.1.5",
"riot": "^3.10.3",
"gulp-riot": "^1.1.5",
"gulp-uglify": "^3.0.1",
"gulp-useref": "^3.1.6",
"riot": "^3.13.2",
"riot-mui": "^0.1.1",
"riot-route": "^3.1.3",
"uglify-js": "^3.4.0",
"uglify-js-harmony": "^2.7.7"
"riot-route": "^3.1.4",
"stream-series": "^0.1.1",
"uglify-es": "^3.3.10"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg2"
x="0px"
y="0px"
viewBox="-5.724 -43.601 730 600" fill="#777">
<path
d="m595.942,422.343 c -7.543,0.119 -13.562,6.331 -13.443,13.875 0.119,7.544 6.332,13.562 13.875,13.443 7.495,-0.118 13.494,-6.254 13.445,-13.75 -0.085,-7.578 -6.297,-13.652 -13.875,-13.568 0,0 -10e-4,0 0,0 m 0,24.398 c -5.975,0.272 -11.039,-4.352 -11.311,-10.326 -0.271,-5.976 4.352,-11.04 10.327,-11.312 5.975,-0.271 11.039,4.352 11.311,10.327 0.01,0.19 0.013,0.382 0.011,0.573 0.204,5.723 -4.27,10.527 -9.992,10.731 -0.115,0.005 -0.23,0.007 -0.346,0.007"/>
<path
d="m599.081,436.342 v -0.185 c 1.512,-0.292 2.65,-1.544 2.8,-3.076 0.057,-1.175 -0.432,-2.311 -1.323,-3.077 -1.445,-0.765 -3.076,-1.106 -4.707,-0.984 -1.743,-0.024 -3.484,0.12 -5.2,0.431 v 13.538 h 3.077 v -5.446 h 1.477 c 1.754,0 2.554,0.646 2.83,2.154 0.184,1.143 0.536,2.252 1.047,3.292 h 3.415 c -0.53,-1.062 -0.873,-2.207 -1.016,-3.385 -0.138,-1.473 -1.088,-2.744 -2.462,-3.292 m -3.723,-0.985 h -1.508 v -3.908 c 0.583,-0.069 1.172,-0.069 1.754,0 1.97,0 2.893,0.831 2.893,2.062 0,1.231 -1.415,2 -3.076,2"/>
<path
d="M707.494,193.557c-1.938-1.539-20.029-15.199-58.181-15.199c-10.074,0.044-20.127,0.908-30.061,2.584 c-7.384-50.612-49.228-75.288-51.104-76.395l-10.245-5.908l-6.738,9.723c-8.438,13.061-14.598,27.459-18.214,42.582 c-6.831,28.891-2.677,56.027,11.999,79.226c-17.722,9.876-46.151,12.307-51.904,12.522H22.367 c-12.294,0.017-22.27,9.952-22.337,22.245c-0.549,41.234,6.437,82.222,20.614,120.946c16.214,42.521,40.336,73.842,71.719,93.01 c35.167,21.537,92.302,33.844,157.067,33.844c29.258,0.092,58.461-2.556,87.226-7.907c39.986-7.342,78.463-21.318,113.839-41.352 c29.149-16.88,55.383-38.354,77.688-63.596c37.29-42.213,59.505-89.226,76.026-131.007c2.215,0,4.431,0,6.584,0 c40.828,0,65.935-16.338,79.78-30.029c9.201-8.732,16.384-19.369,21.045-31.167l2.923-8.553L707.494,193.557z"/>
<path
d="M65.995,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.476-5.538 c-0.01,0-0.021,0-0.031,0H65.995c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C60.488,226.443,62.953,228.909,65.995,228.909L65.995,228.909"/>
<path
d="M152.913,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.059,0-5.538,2.479-5.538,5.538v56.181C147.392,226.448,149.866,228.909,152.913,228.909"/>
<path
d="M241.153,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C235.646,226.443,238.112,228.909,241.153,228.909L241.153,228.909"/>
<path
d="M328.348,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C322.841,226.443,325.307,228.909,328.348,228.909L328.348,228.909"/>
<path
d="M152.913,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.046,0-5.521,2.46-5.538,5.507v56.181C147.392,145.597,149.861,148.066,152.913,148.083"/>
<path
d="M241.153,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C235.646,145.591,238.107,148.066,241.153,148.083"/>
<path
d="M328.348,148.083h63.073c3.052-0.017,5.521-2.486,5.538-5.538V86.364c-0.017-3.047-2.491-5.507-5.538-5.507h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C322.841,145.591,325.302,148.066,328.348,148.083"/>
<path
d="M328.348,67.227h63.073c3.047,0,5.521-2.461,5.538-5.507V5.507C396.942,2.46,394.468,0,391.421,0h-63.073 c-3.042,0-5.507,2.465-5.507,5.507l0,0v56.212C322.841,64.761,325.307,67.227,328.348,67.227"/>
<path
d="M416.312,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.041,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C410.805,226.443,413.271,228.909,416.312,228.909"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016 Jones Magloire @Joxit
Copyright (C) 2016-2019 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
@@ -37,20 +37,23 @@
<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 -->
<!-- build:js scripts/docker-registry-ui.js -->
<script src="tags/catalog.tag" type="riot/tag"></script>
<script src="tags/catalog-element.tag" type="riot/tag"></script>
<script src="tags/tag-history-button.tag" type="riot/tag"></script>
<script src="tags/tag-history.tag" type="riot/tag"></script>
<script src="tags/tag-history-element.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/dialogs/add.tag" type="riot/tag"></script>
<script src="tags/dialogs/change.tag" type="riot/tag"></script>
<script src="tags/dialogs/remove.tag" type="riot/tag"></script>
<script src="tags/dialogs/menu.tag" type="riot/tag"></script>
<script src="tags/image-size.tag" type="riot/tag"></script>
<script src="tags/image-date.tag" type="riot/tag"></script>
<script src="tags/app.tag" type="riot/tag"></script>
<!-- endbuild -->
<!-- build:js scripts/script.js -->
<script src="scripts/http.js"></script>
<script src="scripts/script.js"></script>
<!-- endbuild -->

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

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Jones Magloire @Joxit
* Copyright (C) 2016-2019 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
@@ -24,13 +24,13 @@ function Http() {
Http.prototype.addEventListener = function(e, f) {
this._events[e] = f;
var self = this;
const self = this;
switch (e) {
case 'loadend':
{
self.oReq.addEventListener('loadend', function() {
if (this.status == 401) {
var req = new XMLHttpRequest();
const req = new XMLHttpRequest();
req.open(self._method, self._url);
for (key in self._events) {
req.addEventListener(key, self._events[key]);
@@ -92,7 +92,9 @@ 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 'Incorrect server endpoint.';
} else if (this.withCredentials && !this.hasHeader('Access-Control-Allow-Credentials')) {
return 'The `Access-Control-Allow-Credentials` header in the response is missing and must be set to `true` when the request\'s credentials mode is on. Origin `'+ registryUI.url() +'` is therefore not allowed access.';
}
return 'An error occured';
return 'An error occured: Check your connection and your registry must have `Access-Control-Allow-Origin` header set to `' + window.location.origin + '`';
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Jones Magloire @Joxit
* Copyright (C) 2016-2019 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
@@ -20,7 +20,7 @@ registryUI.URL_PARAM_REGEX = /^url=/;
registryUI.name = registryUI.url = function(byPassQueryParam) {
if (!registryUI._url) {
var url = registryUI.getUrlQueryParam();
const url = registryUI.getUrlQueryParam();
if (url) {
try {
registryUI._url = registryUI.decodeURI(url);
@@ -35,7 +35,7 @@ registryUI.name = registryUI.url = function(byPassQueryParam) {
}
registryUI.getRegistryServer = function(i) {
try {
var res = JSON.parse(localStorage.getItem('registryServer'));
const res = JSON.parse(localStorage.getItem('registryServer'));
if (res instanceof Array) {
return (!isNaN(i)) ? res[i] : res.map(function(url) {
return url.trim().replace(/\/*$/, '');
@@ -45,9 +45,9 @@ registryUI.getRegistryServer = function(i) {
return (!isNaN(i)) ? '' : [];
}
registryUI.addServer = function(url) {
var registryServer = registryUI.getRegistryServer();
const registryServer = registryUI.getRegistryServer();
url = url.trim().replace(/\/*$/, '');
var index = registryServer.indexOf(url);
const index = registryServer.indexOf(url);
if (index != -1) {
return;
}
@@ -56,11 +56,11 @@ registryUI.addServer = function(url) {
registryUI.updateHistory(url);
}
localStorage.setItem('registryServer', JSON.stringify(registryServer));
}
};
registryUI.changeServer = function(url) {
var registryServer = registryUI.getRegistryServer();
url = url.trim().replace(/\/*$/, '');
var index = registryServer.indexOf(url);
const index = registryServer.indexOf(url);
if (index == -1) {
return;
}
@@ -68,11 +68,11 @@ registryUI.changeServer = function(url) {
registryServer = [url].concat(registryServer);
registryUI.updateHistory(url);
localStorage.setItem('registryServer', JSON.stringify(registryServer));
}
};
registryUI.removeServer = function(url) {
var registryServer = registryUI.getRegistryServer();
const registryServer = registryUI.getRegistryServer();
url = url.trim().replace(/\/*$/, '');
var index = registryServer.indexOf(url);
const index = registryServer.indexOf(url);
if (index == -1) {
return;
}
@@ -90,9 +90,9 @@ registryUI.updateHistory = function(url) {
}
registryUI.getUrlQueryParam = function () {
var search = window.location.search;
const search = window.location.search;
if (registryUI.URL_QUERY_PARAM_REGEX.test(search)) {
var param = search.split(/^\?|&/).find(function(param) {
const param = search.split(/^\?|&/).find(function(param) {
return param && registryUI.URL_PARAM_REGEX.test(param);
});
return param ? param.replace(registryUI.URL_PARAM_REGEX, '') : param;
@@ -110,5 +110,8 @@ registryUI.decodeURI = function(url) {
registryUI.isImageRemoveActivated = true;
registryUI.catalog = {};
registryUI.taglist = {};
registryUI.taghistory = {};
riot.mount('*');
window.addEventListener('DOMContentLoaded', function() {
riot.mount('*');
});

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Jones Magloire @Joxit
* Copyright (C) 2016-2019 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
@@ -24,7 +24,8 @@ registryUI.name = function() {
registryUI.isImageRemoveActivated = true;
registryUI.catalog = {};
registryUI.taglist = {};
registryUI.taghistory = {};
riot.mount('catalog');
riot.mount('taglist');
riot.mount('app');
window.addEventListener('DOMContentLoaded', function() {
riot.mount('*');
});

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
html>body {
html > body {
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif !important;
}
@@ -88,18 +88,24 @@ h2 {
overflow: hidden;
}
.material-card-title-action h2 .item-count {
font-size: 0.7em;
margin-left: 1em;
}
.list {
display: block;
padding: 8px 0;
list-style: none;
}
.list.highlight>li:hover {
.list.highlight:hover {
background-color: #eee;
cursor: pointer;
}
.list>li {
.list > span,
.list > li {
box-sizing: border-box;
line-height: 1;
height: 48px;
@@ -107,7 +113,8 @@ h2 {
overflow: hidden;
}
.list>li i.material-icons {
.list > span i.material-icons,
.list > li i.material-icons {
margin-right: 32px;
height: 24px;
width: 24px;
@@ -116,7 +123,29 @@ h2 {
color: #757575;
}
.list>li>span {
.list > span .right i.material-icons.animated {
transition: all 350ms cubic-bezier(.4,0,.2,1);
margin-right: 10px;
}
.list > span .right {
position: absolute;
align-self: end;
display: flex;
align-items: center;
right: 0;
}
.list > span i.material-icons.animated.expanded {
transform: rotate(180deg);
}
.list > span .item-count {
font-size: 0.75em;
}
.list > span,
.list > li > span {
height: 100%;
text-decoration: none;
box-sizing: border-box;
@@ -128,6 +157,11 @@ h2 {
align-items: center;
}
material-card.list {
margin-top: 10px;
margin-bottom: 10px;
}
.material-card-title-action {
-webkit-align-items: center;
-ms-flex-align: center;
@@ -170,16 +204,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 {
@@ -194,6 +233,7 @@ material-card table td {
text-align: right;
}
tag-history-button button:hover,
material-card table th.material-card-th-sorted-ascending:hover, material-card table th.material-card-th-sorted-descending:hover {
cursor: pointer;
}
@@ -217,6 +257,7 @@ material-card table th.material-card-th-sorted-descending:before {
content: "\e5db";
}
material-button .content i.material-icons,
.material-icons {
color: #777;
}
@@ -294,6 +335,7 @@ material-popup .popup {
footer {
width: 100%;
position: fixed;
z-index: 75;
bottom: 0;
}
@@ -322,6 +364,85 @@ select {
padding: 12px 5px;
}
.copy-to-clipboard a:hover {
cursor: pointer;
.show-tag-history {
width: 30px;
text-align: center;
}
.remove-tag {
padding: 12px 5px;
width: 30px;
text-align: center;
}
catalog material-card,
tag-history material-card {
min-height: auto;
}
tag-history-element i {
font-size: 20px;
padding: 0px;
}
tag-history-element.docker_version .headline .material-icons {
background-size: 24px auto;
background-image: url("images/docker-logo.svg");
background-repeat: no-repeat;
}
tag-history-element {
display: block;
padding: 20px;
min-width: 100px;
width: 420px;
float: left;
overflow-x: auto;
}
tag-history-element .headline p {
font-weight: bold;
line-height: 20px;
position: relative;
display: inline;
top: -4px;
}
tag-history-element.id div.value {
font-size: 12px;
}
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;
}
catalog-element material-card {
z-index: 2;
position: relative;
}
catalog-element catalog-element material-card {
transition: all 350ms cubic-bezier(.4,0,.2,1);
z-index: 1;
position: relative;
}
catalog-element catalog-element.showing material-card,
catalog-element catalog-element.hide material-card {
margin-top: -50px;
opacity: 0;
}

View File

@@ -1,18 +1,18 @@
<!--
Copyright (C) 2016 Jones Magloire @Joxit
Copyright (C) 2016-2019 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 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.
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/>.
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/>.
-->
<app>
<header>
@@ -24,6 +24,7 @@
<main>
<catalog if="{route.routeName == 'home'}"></catalog>
<taglist if="{route.routeName == 'taglist'}"></taglist>
<tag-history if="{route.routeName == 'taghistory'}"></tag-history>
<change></change>
<add></add>
<remove></remove>
@@ -31,7 +32,8 @@
</main>
<footer>
<material-footer>
<a class="material-footer-logo" href="https://joxit.github.io/docker-registry-ui/">Docker Registry UI %%GULP_INJECT_VERSION%%</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>
@@ -44,57 +46,74 @@
</material-footer>
</footer>
<script>
registryUI.appTag = this;
route.base('#!')
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
registryUI.taglist.name = image;
if (registryUI.taglist.display) {
registryUI.taglist.loadend = false;
registryUI.taglist.display();
}
registryUI.appTag.update();
});
route('/taghistory/image/*/tag/*', function(image, tag) {
route.routeName = 'taghistory';
registryUI.taghistory.image = image;
registryUI.taghistory.tag = tag;
if (registryUI.taghistory.display) {
registryUI.taghistory.loadend = false;
}
registryUI.appTag.update();
});
registryUI.home = function() {
if(route.routeName == 'home') {
registryUI.catalog.display();
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.taghistory.go = function(image, tag) {
route('/taghistory/image/' + image + '/tag/' + tag);
};
registryUI.errorSnackbar = function (message) {
registryUI.snackbar = function(message, isError) {
registryUI.appTag.tags['material-snackbar'].addToast({'message': message, 'isError': isError}, 15000);
};
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;
const 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, '\\?')
.replace(/\*/g, '([^?#]+?)')
.replace(/\.\./, '.*')
const re = new RegExp('^' + f + '$')
const args = path.match(re)
.replace(/\.\./, '.*');
const re = new RegExp('^' + f + '$');
const args = path.match(re);
if (args) return args.slice(1)
});
registryUI.DockerImage = function (name, tag) {
registryUI.isDigit = function(char) {
return char >= '0' && char <= '9';
};
registryUI.DockerImage = function(name, tag) {
this.name = name;
this.tag = tag;
riot.observable(this);
@@ -110,10 +129,39 @@
}
return this.fillInfo();
});
this.on('get-date', function() {
if (this.date !== undefined) {
return this.trigger('date', this.date);
}
return this.fillInfo();
});
};
registryUI.DockerImage._tagReduce = function(acc, e) {
if (acc.length > 0 && registryUI.isDigit(acc[acc.length - 1].charAt(0)) == registryUI.isDigit(e)) {
acc[acc.length - 1] += e;
} else {
acc.push(e);
}
return acc;
};
registryUI.DockerImage.compare = function(e1, e2) {
return e1.tag.localeCompare(e2.tag);
const tag1 = e1.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce, []);
const tag2 = e2.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce, []);
for (var i = 0; i < tag1.length && i < tag2.length; i++) {
const compare = tag1[i].localeCompare(tag2[i]);
if (registryUI.isDigit(tag1[i].charAt(0)) && registryUI.isDigit(tag2[i].charAt(0))) {
const diff = tag1[i] - tag2[i];
if (diff != 0) {
return diff;
}
} else if (compare != 0) {
return compare;
}
}
return e1.tag.length - e2.tag.length;
};
registryUI.DockerImage.prototype.fillInfo = function() {
@@ -121,17 +169,19 @@
return;
}
this._fillInfoWaiting = true;
var oReq = new Http();
var self = this;
oReq.addEventListener('loadend', function () {
const oReq = new Http();
const 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) {
const response = JSON.parse(this.responseText);
self.size = response.layers.reduce(function(acc, e) {
return acc + e.size;
}, 0);
self.sha256 = response.config.digest;
self.layers = response.layers;
self.trigger('size', self.size);
self.trigger('sha256', self.sha256);
self.getBlobs(response.config.digest)
} else if (this.status == 404) {
registryUI.errorSnackbar('Manifest for ' + self.name + ':' + self.tag + ' not found');
} else {
@@ -141,7 +191,51 @@
oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/manifests/' + self.tag);
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
oReq.send();
}
};
registryUI.DockerImage.prototype.getBlobs = function(blob) {
const oReq = new Http();
const self = this;
oReq.addEventListener('loadend', function() {
if (this.status == 200 || this.status == 202) {
const response = JSON.parse(this.responseText);
self.creationDate = new Date(response.created);
self.blobs = response;
self.blobs.history.filter(function(e) {
return !e.empty_layer;
}).forEach(function(e, i) {
e.size = self.layers[i].size;
e.id = self.layers[i].digest.replace('sha256:', '');
});
self.blobs.id = blob.replace('sha256:', '');
self.trigger('creation-date', self.creationDate);
self.trigger('blobs', self.blobs);
} else if (this.status == 404) {
registryUI.errorSnackbar('Blobs for ' + self.name + ':' + self.tag + ' not found');
} else {
registryUI.snackbar(this.responseText);
}
});
oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/blobs/' + blob);
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
oReq.send();
};
registryUI.bytesToSize = function (bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == undefined || isNaN(bytes)) {
return '?';
} else if (bytes == 0) {
return '0 Byte';
}
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

@@ -0,0 +1,59 @@
<!--
Copyright (C) 2016-2019 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/>.
-->
<catalog-element>
<!-- Begin of tag -->
<material-card class="list highlight" item="{item}" expanded="{expanded}">
<material-waves onmousedown="{launch}" center="true" color="#ddd" />
<span>
<i class="material-icons">send</i>
{ typeof opts.item === "string" ? opts.item : opts.item.repo }
<div hide="{typeof opts.item === "string"}" class="item-count right">
{ opts.item.images && opts.item.images.length } images
<i class="material-icons animated {expanded: opts.expanded}">expand_more</i>
</div>
</span>
</material-card>
<catalog-element hide="{typeof opts.item === "string"}" class="animated {hide: !expanded, expanding: expanding}" each="{item in item.images}" />
<script>
this.on('mount', function() {
const self = this;
const card = this.tags['material-card'];
if (!card) {
return;
}
// Launch waves
card.launch = function(e) {
card.tags['material-waves'].trigger('launch',e);
}
if (this.item.images && this.item.images.length === 1) {
this.item = this.item.images[0];
}
card.root.onclick = function(e) {
if (!self.item.repo) {
registryUI.taglist.go(self.item);
} else {
self.expanded = !self.expanded;
self.update({expanded: self.expanded, expanding: true});
setTimeout(function() {
self.update({expanded: self.expanded, expanding: false});
}, 50)
}
}
})
</script>
<!-- End of tag -->
</catalog-element>

View File

@@ -1,68 +1,73 @@
<!--
Copyright (C) 2016 Jones Magloire @Joxit
Copyright (C) 2016-2019 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 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.
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/>.
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/>.
-->
<catalog>
<!-- 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.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}');">
<span>
<i class="material-icons">send</i>
{ item }
</span>
</li>
</ul>
</material-card>
<catalog-element each="{ item in registryUI.catalog.repositories }" />
<script>
registryUI.catalog.instance = this;
registryUI.catalog.display = function () {
registryUI.catalog.display = function() {
registryUI.catalog.repositories = [];
var oReq = new Http();
oReq.addEventListener('load', function () {
const oReq = new Http();
oReq.addEventListener('load', function() {
registryUI.catalog.repositories = [];
if (this.status == 200) {
registryUI.catalog.repositories = JSON.parse(this.responseText).repositories || [];
registryUI.catalog.repositories.sort();
registryUI.catalog.length = registryUI.catalog.repositories.length; registryUI.catalog.repositories = registryUI.catalog.repositories.reduce(function(acc, e) {
const slash = e.indexOf('/');
if (slash > 0) {
const repoName = e.substring(0, slash) + '/';
if (acc.length == 0 || acc[acc.length - 1].repo != repoName) {
acc.push({repo: repoName, images: []});
}
acc[acc.length - 1].images.push(e);
return acc;
}
acc.push(e);
return acc;
}, []);
} else if (this.status == 404) {
registryUI.snackbar('Server not found', true);
} else {
registryUI.snackbar(this.responseText);
}
});
oReq.addEventListener('error', function () {
oReq.addEventListener('error', function() {
registryUI.snackbar(this.getErrorMessage(), true);
registryUI.catalog.repositories = [];
});
oReq.addEventListener('loadend', function () {
oReq.addEventListener('loadend', function() {
registryUI.catalog.loadend = true;
registryUI.catalog.instance.update();
});
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

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016-2019 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
@@ -16,19 +16,19 @@
-->
<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 () {
var copyText = this.refs['input'];
const 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.')
registryUI.snackbar('`' + this.dockerCmd + '` has been copied to clipboard.')
};
</script>
</copy-to-clipboard>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016 Jones Magloire @Joxit
Copyright (C) 2016-2019 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

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016 Jones Magloire @Joxit
Copyright (C) 2016-2019 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

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016 Jones Magloire @Joxit
Copyright (C) 2016-2019 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
@@ -28,7 +28,7 @@
registryUI.menuTag = registryUI.menuTag || {};
registryUI.menuTag.update = this.update;
this.one('mount', function(args) {
var self = this;
const self = this;
registryUI.menuTag.close = function() {
self.tags['material-dropdown'].close();
self.update();

View File

@@ -0,0 +1,61 @@
<!--
Copyright (C) 2016-2019 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>
<material-popup>
<div class="material-popup-title">Remove your Registry Server ?</div>
<div class="material-popup-content">
<ul class="list">
<li each="{ url in registryUI.getRegistryServer() }">
<span>
<a href="#" onClick="registryUI.removeTag.removeUrl('{url}');">
<i class="material-icons">delete</i>
</a>
<span class="url">{ url }</span>
</span>
</li>
</ul>
</div>
<div class="material-popup-action">
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="registryUI.removeTag.close();">
Close
</material-button>
</div>
</material-popup>
<script type="text/javascript">
registryUI.removeTag = registryUI.removeTag || {}
registryUI.removeTag.update = this.update;
registryUI.removeTag.removeUrl = function(url) {
registryUI.removeServer(url);
registryUI.removeTag.close();
};
registryUI.removeTag.close = function() {
registryUI.removeTag.dialog.close();
registryUI.removeTag.update();
};
registryUI.removeTag.show = function() {
registryUI.removeTag.dialog.open();
};
this.one('mount', function() {
registryUI.removeTag.dialog = this.tags['material-popup'];
});
</script>
</remove>

43
src/tags/image-date.tag Normal file
View File

@@ -0,0 +1,43 @@
<!--
Copyright (C) 2016-2019 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-date>
<div title="Creation date { this.localDate }">{ this.dateFormat(this.date) } ago</div>
<script type="text/javascript">
const self = this;
this.dateFormat = function(date) {
if (date === undefined) {
return '';
}
const labels = ['a second', 'seconds', 'a minute', 'minutes', 'an hour', 'hours', 'a day', 'days', 'a month', 'months', 'a year', 'years'];
const maxSeconds = [1, 60, 3600, 86400, 2592000, 31104000, Infinity];
const diff = (new Date() - date) / 1000;
for (var i = 0; i < maxSeconds.length - 1; i++) {
if (maxSeconds[i] * 2 >= diff) {
return labels[i * 2];
} else if (maxSeconds[i + 1] > diff) {
return Math.floor(diff / maxSeconds[i]) + ' ' + labels[i * 2 + 1];
}
}
};
opts.image.on('creation-date', function(date) {
self.date = date;
self.localDate = date.toLocaleString()
self.update();
});
opts.image.trigger('get-date');
</script>
</image-date>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2018 Jones Magloire @Joxit
Copyright (C) 2016-2019 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
@@ -15,19 +15,9 @@
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>
<div title="Compressed size of your image.">{ registryUI.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];
};
const self = this;
opts.image.on('size', function(size) {
self.size = size;
self.update();

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016-2019 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
@@ -17,7 +17,7 @@
<image-tag>
<div title="{ this.sha256 }">{ opts.image.tag }</div>
<script type="text/javascript">
var self = this;
const self = this;
opts.image.on('sha256', function(sha256) {
self.sha256 = sha256.substring(0, 19);
self.update();

View File

@@ -1,62 +1,65 @@
<!--
Copyright (C) 2016 Jones Magloire @Joxit
Copyright (C) 2016-2019 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 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.
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/>.
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) {
var 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;
}
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.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

@@ -1,59 +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/>.
-->
<remove>
<material-popup>
<div class="material-popup-title">Remove your Registry Server ?</div>
<div class="material-popup-content">
<ul class="list">
<li each="{ url in registryUI.getRegistryServer() }">
<span>
<a href="#" onClick="registryUI.removeTag.removeUrl('{url}');">
<i class="material-icons">delete</i>
</a>
<span class="url">{ url }</span>
</span>
</li>
</ul>
</div>
<div class="material-popup-action">
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="registryUI.removeTag.close();">Close</material-button>
</div>
</material-popup>
<script type="text/javascript">
registryUI.removeTag = registryUI.removeTag || {}
registryUI.removeTag.update = this.update;
registryUI.removeTag.removeUrl = function (url) {
registryUI.removeServer(url);
registryUI.removeTag.close();
};
registryUI.removeTag.close = function () {
registryUI.removeTag.dialog.close();
registryUI.removeTag.update();
};
registryUI.removeTag.show = function () {
registryUI.removeTag.dialog.open();
};
this.one('mount', function () {
registryUI.removeTag.dialog = this.tags['material-popup'];
});
</script>
</remove>

View File

@@ -0,0 +1,31 @@
<!--
Copyright (C) 2016-2019 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/>.
-->
<tag-history-button>
<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>
</material-button>
<script>
this.on('mount', function() {
const self = this;
this.refs.button.root.onclick = function() {
registryUI.taghistory._image = self.opts.image;
registryUI.taghistory.go(self.opts.image.name, self.opts.image.tag);
};
});
this.update()
</script>
</tag-history-button>

View File

@@ -0,0 +1,64 @@
<!--
Copyright (C) 2016-2019 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/>.
-->
<tag-history-element class="{entry.key}">
<div class="headline"><i class="material-icons">{ this.getIcon(entry.key) }</i>
<p>{ entry.key.replace('_', ' ') }</p>
</div>
<div class="value" if={!(entry.value instanceof Array)}> { entry.value }</div>
<div class="value" each={ e in entry.value } if={entry.value instanceof Array}> { e }</div>
<script type="text/javascript">
this.getIcon = function(attribute) {
switch (attribute) {
case 'architecture':
return 'memory';
case 'created':
return 'event';
case 'docker_version':
return '';
case 'os':
return 'developer_board';
case 'Cmd':
return 'launch';
case 'Entrypoint':
return 'input';
case 'Env':
return 'notes';
case 'Labels':
return 'label';
case 'User':
return 'face';
case 'Volumes':
return 'storage';
case 'WorkingDir':
return 'home';
case 'author':
return 'account_circle';
case 'id':
case 'digest':
return 'settings_ethernet';
case 'created_by':
return 'build';
case 'size':
return 'get_app';
case 'ExposedPorts':
return 'router';
default:
''
}
}
</script>
</tag-history-element>

138
src/tags/tag-history.tag Normal file
View File

@@ -0,0 +1,138 @@
<!--
Copyright (C) 2016-2019 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/>.
-->
<tag-history>
<material-card ref="tag-history-tag" class="tag-history">
<div class="material-card-title-action">
<material-button waves-center="true" rounded="true" waves-color="#ddd">
<i class="material-icons">arrow_back</i>
</material-button>
<h2>
History of { registryUI.taghistory.image }:{ registryUI.taghistory.tag } <i class="material-icons">history</i>
</h2>
</div>
</material-card>
<div hide="{ registryUI.taghistory.loadend }" class="spinner-wrapper">
<material-spinner/>
</div>
<material-card each="{ guiElement in this.elements }" class="tag-history-element">
<tag-history-element each="{ entry in guiElement }" if="{ entry.value && entry.value.length > 0}"/>
</material-card>
<script type="text/javascript">
const self = this;
const eltIdx = function(e) {
switch (e) {
case 'id': return 1;
case 'created': return 2;
case 'created_by': return 3;
case 'size': return 4;
case 'os': return 5;
case 'architecture': return 6;
case 'linux': return 7;
case 'docker_version': return 8;
default: return 10;
}
};
const eltSort = function(e1, e2) {
return eltIdx(e1.key) - eltIdx(e2.key);
};
const modifySpecificAttributeTypes = function(attribute, value) {
switch (attribute) {
case 'created':
return new Date(value).toLocaleString();
case 'created_by':
const cmd = value.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+)/);
return (cmd && cmd [1]) || 'RUN'
case 'size':
return registryUI.bytesToSize(value);
case 'Entrypoint':
case 'Cmd':
return (value || []).join(' ');
case 'Labels':
return Object.keys(value || {}).map(function(elt) {
return value[elt] ? elt + '=' + value[elt] : '';
});
case 'Volumes':
case 'ExposedPorts':
return Object.keys(value);
}
return value || '';
};
const getConfig = function(blobs) {
const res = ['architecture', 'User', 'created', 'docker_version', 'os', 'Cmd', 'Entrypoint', 'Env', 'Labels', 'User', 'Volumes', 'WorkingDir', 'author', 'id', 'ExposedPorts'].reduce(function(acc, e) {
const value = blobs[e] || blobs.config[e];
if (value) {
acc[e] = value;
}
return acc;
}, {});
if (!res.author && (res.Labels && res.Labels.maintainer)) {
res.author = blobs.config.Labels.maintainer;
delete res.Labels.maintainer;
}
return res;
};
const processBlobs = function(blobs) {
function exec(elt) {
const guiElements = [];
for (const attribute in elt) {
if (elt.hasOwnProperty(attribute) && attribute != 'empty_layer') {
const value = elt[attribute];
const guiElement = {
"key": attribute,
"value": modifySpecificAttributeTypes(attribute, value)
};
guiElements.push(guiElement);
}
}
return guiElements.sort(eltSort);
}
self.elements.push(exec(getConfig(blobs)));
blobs.history.reverse().forEach(function(elt) { self.elements.push(exec(elt)) });
registryUI.taghistory.loadend = true;
self.update();
};
registryUI.taghistory.display = function() {
self.elements = []
const blobs = registryUI.taghistory._image && registryUI.taghistory._image.blobs;
if (blobs) {
window.scrollTo(0, 0);
return processBlobs(blobs);
}
const image = new registryUI.DockerImage(registryUI.taghistory.image, registryUI.taghistory.tag);
image.fillInfo()
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>
</tag-history>

View File

@@ -1,64 +1,83 @@
<!--
Copyright (C) 2016 Jones Magloire @Joxit
Copyright (C) 2016-2019 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 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.
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/>.
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/>.
-->
<taglist>
<!-- 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>
</div>
<table show="{ registryUI.taglist.loadend }" style="border: none;">
<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>
<tr>
<th class="material-card-th-left">Repository</th>
<th></th>
<th>Creation date</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 class="show-tag-history">History</th>
<th class="remove-tag" show="{ registryUI.isImageRemoveActivated }"></th>
</tr>
</thead>
<tbody>
<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 image={ image }/>
</td>
</tr>
<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-date image="{ image }"/>
</td>
<td>
<image-size image="{ image }"/>
</td>
<td>
<image-tag image="{ image }"/>
</td>
<td class="show-tag-history">
<tag-history-button image={ image }/>
</td>
<td show="{ registryUI.isImageRemoveActivated }">
<remove-image image={ image }/>
</td>
</tr>
</tbody>
</table>
</material-card>
<script>
registryUI.taglist.instance = this;
registryUI.taglist.display = function () {
registryUI.taglist.display = function() {
registryUI.taglist.tags = [];
if (route.routeName == 'taglist') {
var oReq = new Http();
const oReq = new Http();
registryUI.taglist.instance.update();
oReq.addEventListener('load', function () {
oReq.addEventListener('load', function() {
registryUI.taglist.tags = [];
if (this.status == 200) {
registryUI.taglist.tags = JSON.parse(this.responseText).tags || [];
@@ -71,11 +90,11 @@
registryUI.snackbar(this.responseText, true);
}
});
oReq.addEventListener('error', function () {
oReq.addEventListener('error', function() {
registryUI.snackbar(this.getErrorMessage(), true);
registryUI.taglist.tags = [];
registryUI.taglist.tags = [];
});
oReq.addEventListener('loadend', function () {
oReq.addEventListener('loadend', function() {
registryUI.taglist.loadend = true;
registryUI.taglist.instance.update();
});
@@ -87,7 +106,7 @@
registryUI.taglist.display();
registryUI.taglist.instance.update();
registryUI.taglist.reverse = function () {
registryUI.taglist.reverse = function() {
if (registryUI.taglist.asc) {
registryUI.taglist.tags.reverse();
registryUI.taglist.asc = false;
@@ -97,9 +116,6 @@
}
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,9 +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/script-static.js /usr/share/nginx/html/scripts/script.js
COPY dist/scripts/tags-static.js /usr/share/nginx/html/scripts/tags.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