mirror of
https://github.com/Joxit/docker-registry-ui.git
synced 2026-02-17 21:19:51 +00:00
Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b427e09e84 | ||
|
|
476a96279e | ||
|
|
e5b0d5d756 | ||
|
|
2337a06db6 | ||
|
|
8c613701d3 | ||
|
|
1bad64443d | ||
|
|
ef3a9795cf | ||
|
|
99ea6cf1d8 | ||
|
|
ccd349b7d5 | ||
|
|
d87cd44a00 | ||
|
|
b8802ef7ab | ||
|
|
e6c20afcf3 | ||
|
|
1220825f31 | ||
|
|
501d0d72a0 | ||
|
|
be813e6617 | ||
|
|
b4e6369a71 | ||
|
|
c9ede6fe61 | ||
|
|
656914f0d7 | ||
|
|
f8c5010fd1 | ||
|
|
42f19fcef7 | ||
|
|
f9620639bf | ||
|
|
d1700ccf74 | ||
|
|
991eaf932d | ||
|
|
e2ee319d4a | ||
|
|
06d6293e79 | ||
|
|
00fe443a7c | ||
|
|
6e7fc1508e | ||
|
|
178cd5a59d | ||
|
|
da9591609e | ||
|
|
f0a40d6087 | ||
|
|
01d8bcfccd | ||
|
|
c60c2f3e95 | ||
|
|
241ee0fd13 | ||
|
|
2e915a82b1 | ||
|
|
e3d592ac65 | ||
|
|
531f9400a0 | ||
|
|
d7f6cd7e1a | ||
|
|
8524c0d18b | ||
|
|
8b7bd6cfb8 | ||
|
|
f56cd055a2 | ||
|
|
79203787bd | ||
|
|
32d895a09d | ||
|
|
bc26eca908 | ||
|
|
e301dda408 | ||
|
|
ce93a4da92 | ||
|
|
7296c2e727 | ||
|
|
94fcd53792 | ||
|
|
95558fe75a | ||
|
|
44f3242b85 | ||
|
|
9b05503e77 | ||
|
|
980c5ddaa9 | ||
|
|
2eeae54bca | ||
|
|
f7fe27c07e | ||
|
|
48aba129ce | ||
|
|
a2e81ac12c | ||
|
|
a48ce7ef75 | ||
|
|
04f4c204d8 | ||
|
|
41d71a1991 | ||
|
|
b3fbaff578 | ||
|
|
5e2ead0c2a | ||
|
|
70d5ae2601 | ||
|
|
3b0e63ff12 | ||
|
|
b6144ae13f | ||
|
|
65bbac3453 | ||
|
|
d380a767af | ||
|
|
36c44b19a3 | ||
|
|
28310f9804 | ||
|
|
6296a192d6 | ||
|
|
a0c88d06f0 | ||
|
|
eec736d4e8 | ||
|
|
9e99b08b82 | ||
|
|
0f805daafa | ||
|
|
8bd1f31c9c | ||
|
|
7777ff28df | ||
|
|
4fee7b44d3 | ||
|
|
79960ea52d | ||
|
|
7716f8b44a | ||
|
|
0ac7a151d9 | ||
|
|
ef149bf1cc | ||
|
|
e5a406a6ba | ||
|
|
dbb746981a | ||
|
|
92fc37adb4 | ||
|
|
02210e0943 | ||
|
|
0199f87087 | ||
|
|
3399030e4e | ||
|
|
660a938d6e | ||
|
|
7356591292 |
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Hi, I use this docker registry UI and I have an issue...
|
||||
|
||||
## Bug description
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
## How to Reproduce
|
||||
|
||||
For UI bug, steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
For service bug, steps to reproduce the behavior:
|
||||
<!-- Remove your credentials or anonymize them -->
|
||||
My docker-compose file
|
||||
```yml
|
||||
|
||||
```
|
||||
|
||||
My private docker registry configuration
|
||||
```yml
|
||||
|
||||
```
|
||||
|
||||
## Expected behavior
|
||||
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
## Screenshots
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## System information
|
||||
|
||||
- OS: [e.g. Debian 10, Windows, Android 9...]
|
||||
<!-- Browser is only for UI bugs -->
|
||||
- Browser:
|
||||
- Name: [e.g. Chrome, Firefox...]
|
||||
- Version: [e.g. 22]
|
||||
- Docker registry UI:
|
||||
- Version: [e.g. 1.4.0]
|
||||
- Server: [docker or dist]
|
||||
<!-- Only for Docker and for where the UI is hosted -->
|
||||
- Docker version: [e.g. 19.03]
|
||||
- Docker registry ui tag: [latest, static, master, 1.4-static...]
|
||||
- OS/Arch: [e.g. linux/amd64]
|
||||
- Tools: [e.g. docker-compose, kubernets..]
|
||||
|
||||
## Additional context
|
||||
|
||||
Add any other context about the problem here.
|
||||
10
.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Custom issue template
|
||||
about: Describe this issue template's purpose here.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
40
.github/workflows/main.yml
vendored
Normal file
40
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Build and pus master/main docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push Standard Version
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
|
||||
push: true
|
||||
tags: |
|
||||
joxit/docker-registry-ui:master
|
||||
joxit/docker-registry-ui:main
|
||||
- name: Build and push Static version
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./static.dockerfile
|
||||
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
|
||||
push: true
|
||||
tags: |
|
||||
joxit/docker-registry-ui:master-static
|
||||
joxit/docker-registry-ui:main-static
|
||||
64
.github/workflows/release.yml
vendored
Normal file
64
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install
|
||||
run: npm install
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: Current tag
|
||||
id: current-tag
|
||||
run: echo "::set-output name=tag::$(git describe --tags | grep -o '^[0-9]*\.[0-9]*')"
|
||||
- name: Download kokai
|
||||
run: curl -sSL https://github.com/Joxit/kokai/releases/download/$(curl -sSL https://api.github.com/repos/Joxit/kokai/releases/latest | jq -r ".tag_name")/kokai-linux-x86_64 > kokai
|
||||
- name: Create Release Note
|
||||
run: chmod +x kokai && ./kokai release --ref ${{ github.ref }} --tag-from-ref . > RELEASE_NOTE.md
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
body_path: RELEASE_NOTE.md
|
||||
draft: false
|
||||
prerelease: false
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push Standard Version
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
|
||||
push: true
|
||||
tags: |
|
||||
joxit/docker-registry-ui:master
|
||||
joxit/docker-registry-ui:${{steps.current-tag.outputs.tag}}
|
||||
- name: Build and push Static version
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./static.dockerfile
|
||||
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
|
||||
push: true
|
||||
tags: |
|
||||
joxit/docker-registry-ui:master-static
|
||||
joxit/docker-registry-ui:${{steps.current-tag.outputs.tag}}-static
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,4 +3,5 @@ node_modules
|
||||
package-lock.json
|
||||
registry-data
|
||||
.idea
|
||||
_site
|
||||
_site
|
||||
*.orig
|
||||
|
||||
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at contact@joxit.dev. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
37
CONTRIBUTORS.md
Normal file
37
CONTRIBUTORS.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Contributors
|
||||
|
||||
## Committers
|
||||
|
||||
- Jones Magloire [@Joxit](https://github.com/Joxit)
|
||||
- Lennart Blom [@lennartblom](https://github.com/lennartblom)
|
||||
- Jakob Ackermann [@das7pad](https://github.com/das7pad)
|
||||
- Olivier Nizet [@onizet](https://github.com/onizet)
|
||||
- Anton Antonov [@syndbg](https://github.com/syndbg)
|
||||
- Bertrand Pechenot [@berpec](https://github.com/berpec)
|
||||
- Sébastien Huss [@sebt3](https://github.com/sebt3)
|
||||
- Vladimir Kozyrev [@fieryvova](https://github.com/fieryvova)
|
||||
- Haibo Jia [@bluethon](https://github.com/bluethon)
|
||||
- Manuel Leitold [@agrippa1994](https://github.com/agrippa1994)
|
||||
- Murad [@muradheydarov](https://github.com/muradheydarov)
|
||||
- Giacomo Mazzamuto [@gmazzamuto](https://github.com/gmazzamuto)
|
||||
- Joe Bureau [@jabstone](https://github.com/jabstone)
|
||||
|
||||
## Because committers are not the only contributors
|
||||
|
||||
- Ewout Prangsma [@ewoutp](https://github.com/ewoutp)
|
||||
- [@soosap](https://github.com/soosap)
|
||||
- Madhukar Mishra [@madhukar93](https://github.com/madhukar93)
|
||||
- Yoan Blanc [@greut](https://github.com/greut)
|
||||
- [@samuelmaier](https://github.com/samuelmaier)
|
||||
- [@sbaloo](https://github.com/sbaloo)
|
||||
- [@rucciva](https://github.com/rucciva)
|
||||
- [@wuyue92tree](https://github.com/wuyue92tree)
|
||||
- Giovanni Toraldo [@gionn](https://github.com/gionn)
|
||||
- [@marcusblake](https://github.com/marcusblake)
|
||||
- Dario Piombo [@pidario](https://github.com/pidario)
|
||||
- Jernej K. [@Cvetk0](https://github.com/Cvetk0)
|
||||
- Cristian Posoiu [@cr1st1p](https://github.com/cr1st1p)
|
||||
- Sepp Zuther [@Herr-Sepp](https://github.com/Herr-Sepp)
|
||||
- Tomas Hulata [@tombokombo](https://github.com/tombokombo)
|
||||
- Ben Jackson [@bjj](https://github.com/bjj)
|
||||
- 三十文 [@xfduan](https://github.com/xfduan)
|
||||
141
README.md
141
README.md
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Project Page
|
||||
title: Docker Registry User Interface
|
||||
---
|
||||
|
||||
# Docker Registry UI
|
||||
@@ -10,17 +10,17 @@ title: Project Page
|
||||
## Overview
|
||||
|
||||
This project aims to provide a simple and complete user interface for your private docker registry.
|
||||
You have the choice between two versions, the **standard interface** and the **static interface**.
|
||||
You have the choice between two versions, the **standard interface** (`joxit/docker-registry-ui:latest`) and the **static interface** (`joxit/docker-registry-ui:static`).
|
||||
|
||||
In the **standard interface**, there is no default registry, you need to add your own within the UI.
|
||||
You can manage more than one registry server.
|
||||
With this version, you can manage **more than one** registry server but all the environment variables will be **unavailable**.
|
||||
All registries will be stored in the [local storage](https://en.wikipedia.org/wiki/Web_storage#Local_and_session_storage) of your browser. No configuration is needed when you launch the UI.
|
||||
|
||||
In the **static interface**, it will connect to a single registry and will not change. The configuration is done at the start of the interface, when you use the docker images whose tags contain the `static` keyword.
|
||||
In the **static interface**, it will connect to a single registry and will not change. The configuration is done at the start of the interface, when you use the docker images whose tags contain the `static` keyword. With this version, you can manage **only one registry** and all environment variables will be **available**.
|
||||
|
||||
This web user interface uses [Riot](https://github.com/Riot/riot) the react-like user interface micro-library and [riot-mui](https://github.com/kysonic/riot-mui) components.
|
||||
|
||||
## [Project Page](https://joxit.dev/docker-registry-ui) and [Live Demo](https://joxit.dev/docker-registry-ui/demo/)
|
||||
## [Project Page](https://joxit.dev/docker-registry-ui), [Live Demo](https://joxit.dev/docker-registry-ui/demo/), [Examples](https://github.com/Joxit/docker-registry-ui/tree/master/examples)
|
||||
|
||||

|
||||
|
||||
@@ -45,85 +45,65 @@ This web user interface uses [Riot](https://github.com/Riot/riot) the react-like
|
||||
- Use `joxit/docker-registry-ui:static` as reverse proxy (with `REGISTRY_URL` environment variable) to your docker registry (This will avoid CORS) **static interface**.
|
||||
- Add Title when using `REGISTRY_URL` (see [#28](https://github.com/Joxit/docker-registry-ui/issues/28)) **static interface**.
|
||||
- Customise docker pull command on static registry UI (see [#71](https://github.com/Joxit/docker-registry-ui/issues/71)) **static interface**.
|
||||
- Add custom header via environment variable and file via `NGINX_PROXY_HEADER_*` (see [#89](https://github.com/Joxit/docker-registry-ui/pull/89)) **static interface**
|
||||
- Show/Hide content digest in taglist via `SHOW_CONTENT_DIGEST` (values are: [`true`, `false`], default: `true`) (see [#126](https://github.com/Joxit/docker-registry-ui/issues/126)) **static interface**.
|
||||
- Limit the number of elements in the image list via `CATALOG_ELEMENTS_LIMIT` (see [#127](https://github.com/Joxit/docker-registry-ui/pull/127)) **static interface**.
|
||||
- Multi arch support in history page (see [#130](https://github.com/Joxit/docker-registry-ui/issues/130) and [#134](https://github.com/Joxit/docker-registry-ui/pull/134))
|
||||
|
||||
## FAQ
|
||||
|
||||
- What is the difference between **`joxit/docker-registry-ui:latest`** and **`joxit/docker-registry-ui:static`** tags ?
|
||||
- The `latest` tag was the first version of the project, one UI for many docker registries. The `static` tag allows you to have an interface for a single registry and also allows you select your features.
|
||||
- Why, when I delete all tags of an image, the image is still in the UI ?
|
||||
- This is a limitation of docker registry, the garbage collector don't remove empty images. If you want to delete dangling images, you will need to delete the folder in your registry data. (see [#77](https://github.com/Joxit/docker-registry-ui/issues/77))
|
||||
- Why the image size in the UI is not the same as displayed during `docker images` ?
|
||||
- The UI displays the compressed size of the image and not the extracted size version.
|
||||
- Can I use HTTPS on the UI ?
|
||||
- Yes, put your favourite reverse proxy on the front of the UI. Your reverse proxy will take care of HTTPS connection.
|
||||
- Does the UI support authentication ?
|
||||
- Yes, but it supports only basic auth. It's a simple standalone frontend, it will use your browser window for authentication.
|
||||
- Can I use the UI and docker client with an insecure registry (registry url without https) ?
|
||||
- Yes you can, you must first configure your docker client. (see [#76](https://github.com/Joxit/docker-registry-ui/issues/76))
|
||||
- What does Mixed Content error mean ?
|
||||
- This means you are using a UI with HTTPS and your registry is using HTTP (unsecured). When you are on a HTTPS site, you can't get HTTP content. Upgrade you registry with a HTTPS connection.
|
||||
- Why the default nginx `Host` is set to `$http_host` ?
|
||||
- This fixes the issue [#88](https://github.com/Joxit/docker-registry-ui/issues/88). More about this in [#113](https://github.com/Joxit/docker-registry-ui/issues/113).
|
||||
- Why DELETE fails with 401 status code (using Basic Auth) ?
|
||||
- This is caused by a bug in docker registry, I suggest to have your UI on the same domain than your registry e.g. registry.example.com/ui/. (see [#104](https://github.com/Joxit/docker-registry-ui/issues/104)).
|
||||
- Can I use the docker registry ui as a standalone application (with Electron) ?
|
||||
- Yes, check out the example [here](https://github.com/Joxit/docker-registry-ui/tree/master/examples/electron). (see [#129](https://github.com/Joxit/docker-registry-ui/pull/129))
|
||||
- I deleted images through the UI, but they are still present on the server. How can I delete them?
|
||||
- When you delete an image with the UI, only the reference is deleted and not the content. To remove dangling images, you need to run the garbage collector of the registry with the command `registry garbage-collect config.yml` or `docker exec registry registry garbage-collect config.yml`. (see [#77](https://github.com/Joxit/docker-registry-ui/issues/77) [#147](https://github.com/Joxit/docker-registry-ui/issues/147))
|
||||
- Why when I delete one tag, all tags with the same SHA are deleted ?
|
||||
- This a docker registry API limitation, there is only one way to [delete images with tag](https://docs.docker.com/registry/spec/api/#deleting-an-image), it's by its `name` and its `manifest` (it's a sha of the content). So when you delete a tag, this will delete all tags of this image with the same SHA/manifest.
|
||||
|
||||
|
||||
Need more informations ? Try my [examples](https://github.com/Joxit/docker-registry-ui/tree/master/examples) or open an issue.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Basic
|
||||
The docker image contains the source code and nginx in order to serve the docker-registry-ui. Please remember the difference between the **standard interface** (`latest` tag) and **static interface** (`static` tags).
|
||||
|
||||
First you need node and npm in order to download dependencies.
|
||||
### Run the standard interface
|
||||
|
||||
You can run the standard interface see the website on your 80 port. You will be able to use the interface for **many registry servers**, but all the configuration via environment variables from the static interface will be **unavailable**.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Joxit/docker-registry-ui.git
|
||||
cd docker-registry-ui
|
||||
npm install
|
||||
docker run -d -p 80:80 joxit/docker-registry-ui:latest
|
||||
```
|
||||
|
||||
Now you can open index.html with your browser or use a http-server
|
||||
### Run the static interface
|
||||
|
||||
```sh
|
||||
npm install -g http-server
|
||||
http-server
|
||||
```
|
||||
Some env options are available for use this interface for **only one server**.
|
||||
|
||||
### Docker
|
||||
|
||||
The docker contains the source code and a node webserver in order to serve the docker-registry-ui.
|
||||
|
||||
#### Get the docker image
|
||||
|
||||
You can get the image in three ways
|
||||
|
||||
From sources with this command:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Joxit/docker-registry-ui.git
|
||||
# Alpine
|
||||
docker build -t joxit/docker-registry-ui:latest docker-registry-ui
|
||||
docker build -t joxit/docker-registry-ui:static -f docker-registry-ui/static.dockerfile docker-registry-ui
|
||||
# Debian
|
||||
docker build -t joxit/docker-registry-ui:debian -f docker-registry-ui/debian.dockerfile docker-registry-ui
|
||||
docker build -t joxit/docker-registry-ui:static -f docker-registry-ui/debian-static.dockerfile docker-registry-ui
|
||||
```
|
||||
|
||||
Or build with the url:
|
||||
|
||||
```sh
|
||||
# Alpine
|
||||
docker build -t joxit/docker-registry-ui:latest github.com/Joxit/docker-registry-ui
|
||||
docker build -t joxit/docker-registry-ui:static -f static.dockerfile github.com/Joxit/docker-registry-ui
|
||||
# Debian
|
||||
docker build -t joxit/docker-registry-ui:debian -f debian.dockerfile github.com/Joxit/docker-registry-ui
|
||||
docker build -t joxit/docker-registry-ui:debian-static -f debian-static.dockerfile github.com/Joxit/docker-registry-ui
|
||||
```
|
||||
|
||||
Or pull the image from [docker hub](https://hub.docker.com/r/joxit/docker-registry-ui/):
|
||||
|
||||
```sh
|
||||
# Alpine
|
||||
docker pull joxit/docker-registry-ui:latest
|
||||
docker pull joxit/docker-registry-ui:static
|
||||
# Debian
|
||||
docker pull joxit/docker-registry-ui:debian
|
||||
docker pull joxit/docker-registry-ui:debian-static
|
||||
```
|
||||
|
||||
#### Run the docker
|
||||
|
||||
To run the docker and see the website on your 80 port, try this:
|
||||
|
||||
```sh
|
||||
docker run -d -p 80:80 joxit/docker-registry-ui
|
||||
```
|
||||
|
||||
#### Run the static interface
|
||||
|
||||
Some env options are available for use this interface for only one server.
|
||||
|
||||
- `URL`: set the static URL to use (You will need CORS configuration). Example: `http://127.0.0.1:5000`. (`Required`)
|
||||
- `REGISTRY_URL`: your docker registry URL to contact (CORS configuration is not needed). Example: `http://my-docker-container:5000`. (Can't be used with `URL`, since 0.3.2).
|
||||
- [`URL`](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-standalone): set the static URL to use (You will need CORS configuration). Example: `http://127.0.0.1:5000`. (`Required`)
|
||||
- [`REGISTRY_URL`](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-proxy): your docker registry URL to contact (CORS configuration is not needed). Example: `http://my-docker-container:5000`. (Can't be used with `URL`, since 0.3.2).
|
||||
- `DELETE_IMAGES`: if this variable is empty or `false`, delete feature is deactivated. It is activated otherwise.
|
||||
- `REGISTRY_TITLE`: Set a custom title for your user interface when using `REGISTRY_URL` (since 0.3.4).
|
||||
- `PULL_URL`: Set a custom url for the docker pull command, this is useful when you use `REGISTRY_URL` and your registry is on a different host (since 1.1.0).
|
||||
- [`NGINX_PROXY_HEADER_*`](https://github.com/Joxit/docker-registry-ui/tree/master/examples/proxy-headers): Set custom headers for your docker registry, usefull when you want to add your credentials. (Can be use only with `REGISTRY_URL`).
|
||||
- [`SHOW_CONTENT_DIGEST`](https://github.com/Joxit/docker-registry-ui/issues/126): Show content digest in docker tag list. Default: `true`.
|
||||
- [`CATALOG_ELEMENTS_LIMIT`](https://github.com/Joxit/docker-registry-ui/pull/132): Limit the number of elements in the catalog page. Default: `100000`.
|
||||
|
||||
Example with `URL` option.
|
||||
|
||||
@@ -157,10 +137,13 @@ If your docker registry need credentials, you will need to send these HEADERS:
|
||||
http:
|
||||
headers:
|
||||
Access-Control-Allow-Origin: ['<your docker-registry-ui url>']
|
||||
Access-Control-Allow-Credentials: true
|
||||
Access-Control-Allow-Credentials: [true]
|
||||
Access-Control-Allow-Headers: ['Authorization', 'Accept']
|
||||
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS'] # Optional
|
||||
```
|
||||
|
||||
An alternative for CORS issues is a plugin on your browser, more info [here](https://github.com/Joxit/docker-registry-ui/issues/25#issuecomment-621104846) (thank you [xmontero](https://github.com/xmontero)).
|
||||
|
||||
## Using delete
|
||||
|
||||
For deleting images, you need to activate the delete feature in your registry:
|
||||
@@ -180,6 +163,8 @@ http:
|
||||
Access-Control-Expose-Headers: ['Docker-Content-Digest']
|
||||
```
|
||||
|
||||
If you are running the **static interface** don't forget the environment variable `DELETE_IMAGES`.
|
||||
|
||||
## Registry example
|
||||
|
||||
Example of docker registry configuration file:
|
||||
@@ -202,7 +187,7 @@ http:
|
||||
X-Content-Type-Options: [nosniff]
|
||||
Access-Control-Allow-Origin: ['http://127.0.0.1:8001']
|
||||
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
|
||||
Access-Control-Allow-Headers: ['Authorization']
|
||||
Access-Control-Allow-Headers: ['Authorization', 'Accept']
|
||||
Access-Control-Max-Age: [1728000]
|
||||
Access-Control-Allow-Credentials: [true]
|
||||
Access-Control-Expose-Headers: ['Docker-Content-Digest']
|
||||
@@ -212,12 +197,20 @@ auth:
|
||||
path: /etc/docker/registry/htpasswd
|
||||
```
|
||||
|
||||
## Standalone Application
|
||||
If you do not want to install the docker-registry-ui on your server, you may
|
||||
check out the [Electron](examples/electron/README.md) standalone application.
|
||||
|
||||
## All examples
|
||||
|
||||
- [Use docker-registry-ui as a proxy (use REGISTRY_URL)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-proxy)
|
||||
- [Use docker-registry-ui as standalone (use URL)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-standalone)
|
||||
- [Use docker-registry-ui with traefik](https://github.com/Joxit/docker-registry-ui/tree/master/examples/traefik)
|
||||
- [Use docker-registry-ui with docker registry and Amazon s3](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-75) ([#75](https://github.com/Joxit/docker-registry-ui/issues/88))
|
||||
- [Use docker-registry-ui with docker registry and Amazon s3](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-75) ([#75](https://github.com/Joxit/docker-registry-ui/issues/75))
|
||||
- [FIX revproxy to registry does not work when published under non-root url](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-73) ([#73](https://github.com/Joxit/docker-registry-ui/issues/73))
|
||||
- [Use docker-registry-ui with HTTPS](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-20) ([#20](https://github.com/Joxit/docker-registry-ui/issues/20))
|
||||
- [Unable to push image when docker-registry-ui is used as a proxy on non 80 port](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-88) ([#88](https://github.com/Joxit/docker-registry-ui/issues/88))
|
||||
- [Add custom headers bases on environment variable and/or file when the ui is used as proxy](https://github.com/Joxit/docker-registry-ui/tree/master/examples/proxy-headers) ([#89](https://github.com/Joxit/docker-registry-ui/pull/89))
|
||||
- [UI showing same sha256 content digest for all tags + Delete is not working](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-116) ([#116](https://github.com/Joxit/docker-registry-ui/issues/116))
|
||||
- [Electron-based Standalone Application](https://github.com/Joxit/docker-registry-ui/tree/master/examples/electron) ([#129](https://github.com/Joxit/docker-registry-ui/pull/129))
|
||||
- [Use docker-registry-ui as proxy with read-only right](https://github.com/Joxit/docker-registry-ui/tree/master/examples/read-only-auth) ([#47](https://github.com/Joxit/docker-registry-ui/issues/47))
|
||||
|
||||
@@ -2,10 +2,12 @@ title: Docker Registry User Interface
|
||||
description: The simplest and most complete UI for your private registry!
|
||||
url: https://joxit.dev/docker-registry-ui
|
||||
google_analytics: UA-99119327-1
|
||||
theme: jekyll-theme-cayman
|
||||
remote_theme: joxit/joxit.github.io
|
||||
author: Jones Magloire
|
||||
twitter:
|
||||
username: Joxit
|
||||
instagram:
|
||||
username: jox.it
|
||||
defaults:
|
||||
- scope:
|
||||
path: ""
|
||||
|
||||
@@ -18,9 +18,9 @@ LABEL maintainer="Jones MAGLOIRE @Joxit"
|
||||
|
||||
WORKDIR /usr/share/nginx/html/
|
||||
|
||||
ENV NGINX_PROXY_HEADER_Host '$http_host'
|
||||
|
||||
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
|
||||
COPY bin/entrypoint /docker-entrypoint.d/90-docker-registry-ui.sh
|
||||
|
||||
@@ -18,9 +18,9 @@ LABEL maintainer="Jones MAGLOIRE @Joxit"
|
||||
|
||||
WORKDIR /usr/share/nginx/html/
|
||||
|
||||
ENV NGINX_PROXY_HEADER_Host '$http_host'
|
||||
|
||||
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
|
||||
COPY bin/entrypoint /docker-entrypoint.d/90-docker-registry-ui.sh
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
$@
|
||||
|
||||
sed -i "s,\${URL},${URL}," scripts/docker-registry-ui.js
|
||||
sed -i "s,\${REGISTRY_TITLE},${REGISTRY_TITLE}," scripts/docker-registry-ui.js
|
||||
sed -i "s,\${PULL_URL},${PULL_URL}," scripts/docker-registry-ui.js
|
||||
@@ -8,13 +8,33 @@ if [ -z "${DELETE_IMAGES}" ] || [ "${DELETE_IMAGES}" = false ] ; then
|
||||
sed -i -r "s/(isImageRemoveActivated[:=])[^,;]*/\1false/" scripts/docker-registry-ui.js
|
||||
fi
|
||||
|
||||
if [ -n "${REGISTRY_URL}" ] ; then
|
||||
sed -i "s,\${REGISTRY_URL},${REGISTRY_URL}," /etc/nginx/conf.d/default.conf
|
||||
sed -i "s,#!,," /etc/nginx/conf.d/default.conf
|
||||
if [ "${SHOW_CONTENT_DIGEST}" = false ] ; then
|
||||
sed -i -r "s/(showContentDigest[:=])[^,;]*/\1false/" scripts/docker-registry-ui.js
|
||||
fi
|
||||
|
||||
if [ -z "$@" ]; then
|
||||
nginx -g "daemon off;"
|
||||
else
|
||||
$@
|
||||
if [ -n "${CATALOG_ELEMENTS_LIMIT}" ] ; then
|
||||
sed -i -r "s/(catalogElementsLimit[:=])[^,;]*/\1${CATALOG_ELEMENTS_LIMIT}/" scripts/docker-registry-ui.js
|
||||
fi
|
||||
|
||||
get_nginx_proxy_headers() {
|
||||
(
|
||||
env &&
|
||||
if [ -f "/etc/nginx/.env" ]; then
|
||||
cat /etc/nginx/.env
|
||||
# Force new line
|
||||
echo ""
|
||||
fi
|
||||
) | while read e; do
|
||||
if [ -n "$(echo $e | grep -o '^NGINX_PROXY_HEADER_')" ]; then
|
||||
key=$(echo ${e%%=*} | sed 's/^NGINX_PROXY_HEADER_//' | sed 's/_/-/g')
|
||||
value=${e#*=}
|
||||
echo -n "proxy_set_header ${key} \"${value}\"; "
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if [ -n "${REGISTRY_URL}" ] ; then
|
||||
sed -i "s,\${REGISTRY_URL},${REGISTRY_URL}," /etc/nginx/conf.d/default.conf
|
||||
sed -i "s^\${NGINX_PROXY_HEADERS}^$(get_nginx_proxy_headers)^" /etc/nginx/conf.d/default.conf
|
||||
sed -i "s,#!,," /etc/nginx/conf.d/default.conf
|
||||
fi
|
||||
|
||||
13
bin/fill-registry
Executable file
13
bin/fill-registry
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
for i in alpine chronograf:alpine consul debian jawg/mapnik3 nginx:alpine postgres:alpine redis:alpine telegraf:alpine joxit/docker-registry-ui joxit/kosmtik joxit/node joxit/rust-openssl joxit/tile-server-ui; do
|
||||
docker pull $i
|
||||
docker tag $i 127.0.0.1:5000/$i
|
||||
docker push 127.0.0.1:5000/$i
|
||||
done
|
||||
|
||||
for i in arm32v7-static 1.2-debian-static master-static 1.2 arm64v8 arm32v7 arm64v8-static master 1.2-debian latest static debian-static debian 1.2-static 1.1 1.1-static 1.1-debian-static 1.1-debian ; do
|
||||
docker pull joxit/docker-registry-ui:$i
|
||||
docker tag joxit/docker-registry-ui:$i 127.0.0.1:5000/joxit/docker-registry-ui:$i
|
||||
docker push 127.0.0.1:5000/joxit/docker-registry-ui:$i
|
||||
done
|
||||
@@ -18,9 +18,9 @@ LABEL maintainer="Jones MAGLOIRE @Joxit"
|
||||
|
||||
WORKDIR /usr/share/nginx/html/
|
||||
|
||||
ENV NGINX_PROXY_HEADER_Host '$http_host'
|
||||
|
||||
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
|
||||
COPY bin/entrypoint /docker-entrypoint.d/90-docker-registry-ui.sh
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="../dist/vendor.css">
|
||||
<link rel="stylesheet" href="../dist/style.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&lang=en" rel="stylesheet" type="text/css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="This is the live demo for Docker Registry User Interface. Try it now! Sources : https://github.com/Joxit/docker-registry-ui" />
|
||||
|
||||
BIN
dist/fonts/Roboto-Bold.ttf
vendored
Normal file
BIN
dist/fonts/Roboto-Bold.ttf
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/Roboto-Bold.woff
vendored
Normal file
BIN
dist/fonts/Roboto-Bold.woff
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/Roboto-Bold.woff2
vendored
Normal file
BIN
dist/fonts/Roboto-Bold.woff2
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/Roboto-Light.ttf
vendored
Normal file
BIN
dist/fonts/Roboto-Light.ttf
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/Roboto-Light.woff
vendored
Normal file
BIN
dist/fonts/Roboto-Light.woff
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/Roboto-Light.woff2
vendored
Normal file
BIN
dist/fonts/Roboto-Light.woff2
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/Roboto-Regular.eot
vendored
Normal file
BIN
dist/fonts/Roboto-Regular.eot
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/Roboto-Regular.ttf
vendored
Normal file
BIN
dist/fonts/Roboto-Regular.ttf
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/Roboto-Regular.woff
vendored
Normal file
BIN
dist/fonts/Roboto-Regular.woff
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/Roboto-Regular.woff2
vendored
Normal file
BIN
dist/fonts/Roboto-Regular.woff2
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/RobotoMono-Regular.eot
vendored
Normal file
BIN
dist/fonts/RobotoMono-Regular.eot
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/RobotoMono-Regular.ttf
vendored
Normal file
BIN
dist/fonts/RobotoMono-Regular.ttf
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/RobotoMono-Regular.woff
vendored
Normal file
BIN
dist/fonts/RobotoMono-Regular.woff
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/RobotoMono-Regular.woff2
vendored
Normal file
BIN
dist/fonts/RobotoMono-Regular.woff2
vendored
Normal file
Binary file not shown.
2
dist/index.html
vendored
2
dist/index.html
vendored
@@ -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&lang=en" rel="stylesheet" type="text/css"><meta name="viewport" content="width=device-width,initial-scale=1"><meta property="og:site_name" content="Docker Registry UI"><meta name="twitter:card" content="summary"><meta name="twitter:site" content="@Joxit"><meta name="twitter:creator" content="@Jones Magloire"><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>
|
||||
--><!DOCTYPE html><html><head><meta charset="UTF-8"><link rel="stylesheet" href="vendor.css"><link rel="stylesheet" href="style.css"><meta name="viewport" content="width=device-width,initial-scale=1"><meta property="og:site_name" content="Docker Registry UI"><meta name="twitter:card" content="summary"><meta name="twitter:site" content="@Joxit"><meta name="twitter:creator" content="@Jones Magloire"><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>
|
||||
2
dist/scripts/docker-registry-ui-static.js
vendored
2
dist/scripts/docker-registry-ui-static.js
vendored
File diff suppressed because one or more lines are too long
2
dist/scripts/docker-registry-ui.js
vendored
2
dist/scripts/docker-registry-ui.js
vendored
File diff suppressed because one or more lines are too long
2
dist/style.css
vendored
2
dist/style.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 764 KiB After Width: | Height: | Size: 686 KiB |
13
examples/README.md
Normal file
13
examples/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## Examples
|
||||
|
||||
- [Use docker-registry-ui as a proxy (use REGISTRY_URL)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-proxy)
|
||||
- [Use docker-registry-ui as standalone (use URL)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/ui-as-standalone)
|
||||
- [Use docker-registry-ui with traefik](https://github.com/Joxit/docker-registry-ui/tree/master/examples/traefik)
|
||||
- [Use docker-registry-ui with docker registry and Amazon s3](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-75) ([#75](https://github.com/Joxit/docker-registry-ui/issues/75))
|
||||
- [FIX revproxy to registry does not work when published under non-root url](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-73) ([#73](https://github.com/Joxit/docker-registry-ui/issues/73))
|
||||
- [Use docker-registry-ui with HTTPS](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-20) ([#20](https://github.com/Joxit/docker-registry-ui/issues/20))
|
||||
- [Unable to push image when docker-registry-ui is used as a proxy on non 80 port](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-88) ([#88](https://github.com/Joxit/docker-registry-ui/issues/88))
|
||||
- [Add custom headers bases on environment variable and/or file when the ui is used as proxy](https://github.com/Joxit/docker-registry-ui/tree/master/examples/proxy-headers) ([#89](https://github.com/Joxit/docker-registry-ui/pull/89))
|
||||
- [UI showing same sha256 content digest for all tags + Delete is not working](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-116) ([#116](https://github.com/Joxit/docker-registry-ui/issues/116))
|
||||
- [Electron-based Standalone Application](https://github.com/Joxit/docker-registry-ui/tree/master/examples/electron) ([#129](https://github.com/Joxit/docker-registry-ui/pull/129))
|
||||
- [Use docker-registry-ui as proxy with read-only right](https://github.com/Joxit/docker-registry-ui/tree/master/examples/read-only-auth) ([#47](https://github.com/Joxit/docker-registry-ui/issues/47))
|
||||
8
examples/electron/.gitignore
vendored
Normal file
8
examples/electron/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# NPM renames .gitignore to .npmignore
|
||||
# In order to prevent that, we remove the initial "."
|
||||
# And the CLI then renames it
|
||||
|
||||
dist/
|
||||
node_modules/
|
||||
Registry*
|
||||
.cache
|
||||
57
examples/electron/README.md
Normal file
57
examples/electron/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Standalone Application
|
||||
|
||||
## Overview
|
||||
|
||||
This standalone application is based on Electron which encapsulates the whole
|
||||
docker-registry-ui in a single executable, that can be run on your local
|
||||
computer.
|
||||
|
||||
## Building
|
||||
* Check out or download the repository, open a terminal at the checkout
|
||||
directory, download the dependencies and build the web app:
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
* After building the web application, navigate to the ```electron``` directory
|
||||
and execute following commands to build the executable:
|
||||
```bash
|
||||
cd electron
|
||||
npm install
|
||||
npm run dist
|
||||
```
|
||||
If you encounter any issues, please check the troubleshooting below.
|
||||
|
||||
|
||||
## Password Protected Registries
|
||||
If you want to interact with password protected Docker Registries, this
|
||||
application will use the keystore of your system to gather the credentials for
|
||||
accessing the Registry.
|
||||
|
||||
This is accomplished with the [keytar](https://www.npmjs.com/package/keytar)
|
||||
package. In concjunction with keytar, the integrated credential
|
||||
helper supports you with managing the credentials to the Registries.
|
||||
|
||||

|
||||
|
||||
|
||||
## Troubleshooting
|
||||
* Problem: The application does not start with ```npm start``` and exits with following message:
|
||||
```
|
||||
[7742:0509/001117.199224:FATAL:setuid_sandbox_host.cc(157)] The SUID sandbox helper binary was found, but is not configured correctly. Rather than run without sandboxing I'm aborting now. You need to make sure that ./node_modules/electron dist/chrome-sandbox is owned by root and has mode 4755.
|
||||
```
|
||||
|
||||
Solution: Add proper rights to the chrome-sanbox
|
||||
```bash
|
||||
sudo chown root ./node_modules/electron/dist/chrome-sandbox
|
||||
sudo chmod 4755 ./node_modules/electron/dist/chrome-sandbox
|
||||
```
|
||||
|
||||
* Problem: I am on Linux and to not have any password wallet for keytar.
|
||||
|
||||
Solution: Install following dependencies according to the official [setup instructions](https://atom.github.io/node-keytar/) for keytar on Linux:
|
||||
* Debian/Ubuntu: ```sudo apt-get install libsecret-1-dev```
|
||||
* Red Hat-based: ```sudo yum install libsecret-devel```
|
||||
* Arch Linux: ```sudo pacman -S libsecret```
|
||||
|
||||
|
||||
8
examples/electron/authentication/index.html
Normal file
8
examples/electron/authentication/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="index.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
211
examples/electron/authentication/index.tsx
Normal file
211
examples/electron/authentication/index.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
import * as React from "react";
|
||||
import {useEffect, useState} from "react";
|
||||
import {render} from "react-dom";
|
||||
import * as keytar from 'keytar';
|
||||
import {ipcRenderer} from 'electron';
|
||||
import {
|
||||
Button,
|
||||
createMuiTheme,
|
||||
CssBaseline,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
ThemeProvider,
|
||||
useMediaQuery
|
||||
} from "@material-ui/core";
|
||||
import {Alert, AlertTitle} from '@material-ui/lab';
|
||||
import {blue} from "@material-ui/core/colors";
|
||||
import {Add as AddIcon, Delete as DeleteIcon, Save as SaveIcon} from "@material-ui/icons";
|
||||
|
||||
const mainStyle = makeStyles((theme) => ({
|
||||
root: {
|
||||
padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
main: {
|
||||
flexGrow: 1,
|
||||
paddingTop: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(2),
|
||||
},
|
||||
input: {
|
||||
width: '100%',
|
||||
},
|
||||
}));
|
||||
|
||||
function CredentialRow({credential, index, onDelete, onUpdate}) {
|
||||
const [account, setAccount] = useState(credential?.account || '');
|
||||
const [password, setPassword] = useState(credential?.password || '');
|
||||
|
||||
const style = mainStyle();
|
||||
return (<TableRow>
|
||||
<TableCell>
|
||||
<TextField
|
||||
className={style.input}
|
||||
type="text"
|
||||
placeholder='https://user@someregistry:5000/'
|
||||
value={account} variant="outlined"
|
||||
onChange={(e) => {
|
||||
setAccount(e.target.value)
|
||||
}}/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TextField type="password"
|
||||
className={style.input}
|
||||
variant="outlined"
|
||||
placeholder='Password'
|
||||
value={password}
|
||||
onChange={(e) => {
|
||||
setPassword(e.target.value)
|
||||
}}/>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton onClick={async () => await onUpdate(credential, index, {account, password})}>
|
||||
<SaveIcon/>
|
||||
</IconButton>
|
||||
<IconButton onClick={async () => await onDelete(credential, index,)}>
|
||||
<DeleteIcon/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>);
|
||||
}
|
||||
|
||||
|
||||
function CredentialsTable({onError}) {
|
||||
const [credentials, setCredentials] = useState(null);
|
||||
|
||||
async function loadItems() {
|
||||
try {
|
||||
const credentials = await keytar.findCredentials('docker-registry-ui');
|
||||
for (const credential of credentials) {
|
||||
// fix for windows
|
||||
credential.password = credential.password.replace(/\000+/g, '');
|
||||
}
|
||||
setCredentials(credentials);
|
||||
} catch (e) {
|
||||
onError(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(item, index) {
|
||||
// delete an item that has not been stored yet
|
||||
if (!item) {
|
||||
const newCredentials = [...credentials];
|
||||
newCredentials.splice(index, 1);
|
||||
setCredentials(newCredentials);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await keytar.deletePassword('docker-registry-ui', item.account);
|
||||
await loadItems();
|
||||
} catch (e) {
|
||||
onError(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUpdate(oldCredentials, index, newCredentials) {
|
||||
try {
|
||||
await handleDelete(oldCredentials, index);
|
||||
await keytar.setPassword('docker-registry-ui', newCredentials.account, newCredentials.password);
|
||||
await loadItems();
|
||||
} catch (e) {
|
||||
console.error("Error while updating key: ", e);
|
||||
onError(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
await loadItems();
|
||||
};
|
||||
|
||||
load();
|
||||
return;
|
||||
}, []);
|
||||
|
||||
if (credentials === null) {
|
||||
return <LinearProgress/>
|
||||
}
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Host of the registry including username</TableCell>
|
||||
<TableCell>Password</TableCell>
|
||||
<TableCell align='right'>
|
||||
<IconButton onClick={() => {
|
||||
setCredentials([...credentials, null])
|
||||
}} disabled={credentials.includes(null)}>
|
||||
<AddIcon/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{credentials.map((credential, index) => <CredentialRow
|
||||
onDelete={handleDelete}
|
||||
onUpdate={handleUpdate}
|
||||
index={index}
|
||||
key={credential?.account || ''}
|
||||
credential={credential}/>)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
|
||||
const [error, setError] = useState();
|
||||
const classes = mainStyle();
|
||||
|
||||
const theme = React.useMemo(
|
||||
() =>
|
||||
createMuiTheme({
|
||||
palette: {
|
||||
type: prefersDarkMode ? 'dark' : 'light',
|
||||
primary: blue,
|
||||
},
|
||||
}),
|
||||
[prefersDarkMode],
|
||||
);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline/>
|
||||
<div className={classes.root}>
|
||||
{error && <Alert severity='error' onClose={() => {
|
||||
setError(null)
|
||||
}}>
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
{error}
|
||||
</Alert>}
|
||||
<main className={classes.main}>
|
||||
<CredentialsTable onError={setError}/>
|
||||
</main>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
render(<App/>, document.getElementById("root"));
|
||||
|
||||
// @ts-ignore
|
||||
if (module.hot) {
|
||||
// @ts-ignore
|
||||
module.hot.accept();
|
||||
}
|
||||
|
||||
BIN
examples/electron/doc/assets/authentication.gif
Normal file
BIN
examples/electron/doc/assets/authentication.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 450 KiB |
229
examples/electron/index.js
Normal file
229
examples/electron/index.js
Normal file
@@ -0,0 +1,229 @@
|
||||
const {app, BrowserWindow, globalShortcut, Menu} = require('electron');
|
||||
const isDevMode = require('electron-is-dev');
|
||||
const keytar = require('keytar');
|
||||
const url = require('url');
|
||||
|
||||
const isMac = process.platform === 'darwin'
|
||||
|
||||
// Place holders for our windows so they don't get garbage collected.
|
||||
let mainWindow = null;
|
||||
|
||||
// Credentials that are fetched from the Keychain
|
||||
let credentials = [];
|
||||
|
||||
// Credentials helper window
|
||||
let credentialsWindow;
|
||||
|
||||
const template = [
|
||||
// { role: 'appMenu' }
|
||||
...(isMac ? [{
|
||||
label: app.name,
|
||||
submenu: [
|
||||
{role: 'about'},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: 'Preferences', accelerator: 'CmdorCtrl+,', click: () => {
|
||||
credentialsWindow.show();
|
||||
}
|
||||
},
|
||||
{type: 'separator'},
|
||||
{role: 'hide'},
|
||||
{role: 'hideothers'},
|
||||
{role: 'unhide'},
|
||||
{type: 'separator'},
|
||||
{role: 'quit'}
|
||||
]
|
||||
}] : []),
|
||||
// { role: 'fileMenu' }
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [
|
||||
...(isMac ? [] : [{role: 'quit'}]),
|
||||
{
|
||||
label: 'Preferences', accelerator: 'CmdorCtrl+,', click: () => {
|
||||
credentialsWindow.show();
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
// { role: 'editMenu' }
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{role: 'undo'},
|
||||
{role: 'redo'},
|
||||
{type: 'separator'},
|
||||
{role: 'cut'},
|
||||
{role: 'copy'},
|
||||
{role: 'paste'},
|
||||
...(isMac ? [
|
||||
{role: 'pasteAndMatchStyle'},
|
||||
{role: 'delete'},
|
||||
{role: 'selectAll'},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: 'Speech',
|
||||
submenu: [
|
||||
{role: 'startspeaking'},
|
||||
{role: 'stopspeaking'}
|
||||
]
|
||||
}
|
||||
] : [
|
||||
{role: 'delete'},
|
||||
{type: 'separator'},
|
||||
{role: 'selectAll'}
|
||||
])
|
||||
]
|
||||
},
|
||||
// { role: 'viewMenu' }
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{role: 'reload'},
|
||||
{role: 'forcereload'},
|
||||
{role: 'toggledevtools'},
|
||||
{type: 'separator'},
|
||||
{role: 'resetzoom'},
|
||||
{role: 'zoomin'},
|
||||
{role: 'zoomout'},
|
||||
{type: 'separator'},
|
||||
{role: 'togglefullscreen'},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: 'Credentials Helper', accelerator: 'CmdorCtrl+k', click: () => {
|
||||
credentialsWindow.show();
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
// { role: 'windowMenu' }
|
||||
{
|
||||
label: 'Window',
|
||||
submenu: [
|
||||
{role: 'minimize'},
|
||||
{role: 'zoom'},
|
||||
...(isMac ? [
|
||||
{type: 'separator'},
|
||||
{role: 'front'},
|
||||
{type: 'separator'},
|
||||
{role: 'window'}
|
||||
] : [
|
||||
{role: 'close'}
|
||||
])
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click: async () => {
|
||||
const {shell} = require('electron')
|
||||
await shell.openExternal('https://joxit.dev/docker-registry-ui/')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
if (isMac) {
|
||||
Menu.setApplicationMenu(menu);
|
||||
}
|
||||
|
||||
async function loadCredentials() {
|
||||
try {
|
||||
credentials = await keytar.findCredentials('docker-registry-ui');
|
||||
for (const credential of credentials) {
|
||||
// fix for windows
|
||||
credential.password = credential.password.replace(/\000+/g, '');
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
credentials = [];
|
||||
}
|
||||
}
|
||||
|
||||
async function createWindow() {
|
||||
return new Promise((resolve, reject) => {
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 920,
|
||||
width: 1600,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
}
|
||||
});
|
||||
|
||||
if (isDevMode) {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
if (!isMac) {
|
||||
mainWindow.setMenu(menu);
|
||||
}
|
||||
|
||||
mainWindow.loadURL(`file://${__dirname}/dist/index.html`);
|
||||
mainWindow.webContents.on('dom-ready', () => {
|
||||
console.log("Main Window DOM ready");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function createCredentialsWindow() {
|
||||
return new Promise((resolve) => {
|
||||
credentialsWindow = new BrowserWindow({
|
||||
width: 1000,
|
||||
height: 400,
|
||||
show: false,
|
||||
title: 'Credential Manager',
|
||||
parent: mainWindow,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
}
|
||||
});
|
||||
|
||||
if (isDevMode) {
|
||||
credentialsWindow.openDevTools();
|
||||
}
|
||||
|
||||
if (!isMac) {
|
||||
credentialsWindow.setMenu(null);
|
||||
}
|
||||
|
||||
credentialsWindow.loadURL(`file://${__dirname}/dist/authentication/index.html`);
|
||||
credentialsWindow.webContents.on('dom-ready', () => {
|
||||
console.log('Credentials Window DOM is ready');
|
||||
resolve();
|
||||
});
|
||||
|
||||
credentialsWindow.on('close', async (e) => {
|
||||
console.log("Closed credential window");
|
||||
credentialsWindow.hide();
|
||||
e.preventDefault();
|
||||
await loadCredentials();
|
||||
mainWindow.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.on('ready', async () => {
|
||||
await Promise.all([
|
||||
loadCredentials(),
|
||||
createWindow(),
|
||||
createCredentialsWindow(),
|
||||
]);
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
|
||||
app.on("login", (event, contents, authencation, info, callback) => {
|
||||
for (const credential of credentials) {
|
||||
const parsedUrl = url.parse(credential.account);
|
||||
if (parsedUrl.hostname === info.host) {
|
||||
return callback(parsedUrl.auth, credential.password);
|
||||
}
|
||||
}
|
||||
callback();
|
||||
});
|
||||
39
examples/electron/package.json
Normal file
39
examples/electron/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "docker-registry-ui",
|
||||
"version": "1.4.8",
|
||||
"productName": "Registry UI",
|
||||
"description": "Electron Application for Docker Registry UI",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "electron ./",
|
||||
"start:dev": "parcel serve -d dist/authentication -t electron --public-url ./ authentication/index.html",
|
||||
"build": "parcel build -d dist/authentication -t electron --public-url ./ authentication/index.html",
|
||||
"rebuild": "electron-rebuild -f -w keytar",
|
||||
"package": "electron-packager --overwrite .",
|
||||
"sync": "copyfiles ../../dist/* ../../dist/**/* ./examples/out",
|
||||
"dist": "npm run rebuild && npm run sync && npm run build && npm run package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.9.13",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.52",
|
||||
"electron-is-dev": "^1.1.0",
|
||||
"keytar": "^5.6.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copyfiles": "^2.2.0",
|
||||
"electron": "^8.0.0",
|
||||
"electron-builder": "^22.6.0",
|
||||
"electron-packager": "^14.2.1",
|
||||
"electron-rebuild": "^1.10.1",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"keywords": [
|
||||
"electron"
|
||||
],
|
||||
"author": "",
|
||||
"license": "AGPL-3.0"
|
||||
}
|
||||
@@ -63,7 +63,7 @@ registry:
|
||||
image:
|
||||
registry: docker.io
|
||||
repository: registry
|
||||
tag: 2.6.2
|
||||
tag: 2.7.1
|
||||
pullPolicy: Always
|
||||
probe:
|
||||
liveness: true
|
||||
|
||||
42
examples/issue-116/docker-compose.yml
Normal file
42
examples/issue-116/docker-compose.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
registry-srv:
|
||||
image: registry:2.7
|
||||
restart: always
|
||||
volumes:
|
||||
- storage:/var/lib/registry
|
||||
- ./htpasswd:/etc/docker/registry/htpasswd
|
||||
ports:
|
||||
- 5000:5000
|
||||
environment:
|
||||
- REGISTRY_HTTP_HEADERS_X-Content-Type-Options=[nosniff]
|
||||
- REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin=['http://localhost']
|
||||
- REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods=['HEAD', 'GET', 'OPTIONS', 'DELETE']
|
||||
- REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers=['Authorization']
|
||||
- REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers=['Docker-Content-Digest']
|
||||
- REGISTRY_HTTP_HEADERS_Access-Control-Allow-Credentials=['true']
|
||||
- REGISTRY_AUTH_HTPASSWD_REALM=basic-realm
|
||||
- REGISTRY_AUTH_HTPASSWD_PATH=/etc/docker/registry/htpasswd
|
||||
- REGISTRY_STORAGE_DELETE_ENABLED=true
|
||||
networks:
|
||||
- registry-ui-net
|
||||
container_name: registry-srv
|
||||
|
||||
registry-ui:
|
||||
image: joxit/docker-registry-ui:static
|
||||
restart: always
|
||||
ports:
|
||||
- 80:80
|
||||
environment:
|
||||
- REGISTRY_TITLE=Private Docker Registry
|
||||
- URL=http://localhost:5000
|
||||
- DELETE_IMAGES=true
|
||||
container_name: registry-ui
|
||||
|
||||
networks:
|
||||
registry-ui-net:
|
||||
|
||||
volumes:
|
||||
storage:
|
||||
driver: local
|
||||
3
examples/issue-116/htpasswd
Normal file
3
examples/issue-116/htpasswd
Normal file
@@ -0,0 +1,3 @@
|
||||
# login: registry
|
||||
# password: ui
|
||||
registry:$2y$11$1bmuJLK8HrQl5ACS/WeqRuJLUArUZfUcP2R23asmozEpfN76.pCHy
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '2.0'
|
||||
services:
|
||||
registry:
|
||||
image: registry:2.6.2
|
||||
image: registry:2.7
|
||||
volumes:
|
||||
- ./registry-data:/var/lib/registry
|
||||
networks:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '2.0'
|
||||
services:
|
||||
registry:
|
||||
image: registry:2.6.2
|
||||
image: registry:2.7
|
||||
volumes:
|
||||
- ./registry-data:/var/lib/registry
|
||||
networks:
|
||||
|
||||
@@ -28,7 +28,7 @@ http:
|
||||
X-Content-Type-Options: [nosniff]
|
||||
Access-Control-Allow-Origin: ['http://127.0.0.1']
|
||||
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
|
||||
Access-Control-Allow-Headers: ['Authorization']
|
||||
Access-Control-Allow-Headers: ['Authorization', 'Accept']
|
||||
Access-Control-Max-Age: [1728000]
|
||||
Access-Control-Allow-Credentials: [true]
|
||||
Access-Control-Expose-Headers: ['Docker-Content-Digest']
|
||||
|
||||
@@ -15,6 +15,7 @@ http:
|
||||
X-Content-Type-Options: [nosniff]
|
||||
Access-Control-Allow-Origin: ['*']
|
||||
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
|
||||
Access-Control-Allow-Headers: ['Accept']
|
||||
Access-Control-Expose-Headers: ['Docker-Content-Digest']
|
||||
health:
|
||||
storagedriver:
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '2'
|
||||
|
||||
services:
|
||||
registry-srv:
|
||||
image: registry:latest
|
||||
image: registry:2.7
|
||||
restart: always
|
||||
volumes:
|
||||
- storage:/var/lib/registry
|
||||
@@ -20,8 +20,6 @@ services:
|
||||
- REGISTRY_TITLE=Private Docker Registry
|
||||
- REGISTRY_URL=http://registry-srv:5000
|
||||
- DELETE_IMAGES=true
|
||||
depends_on:
|
||||
- debugproxy
|
||||
networks:
|
||||
- registry-ui-net
|
||||
container_name: registry-ui
|
||||
|
||||
@@ -25,7 +25,7 @@ data:
|
||||
X-Content-Type-Options: [nosniff]
|
||||
Access-Control-Allow-Origin: ['*']
|
||||
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
|
||||
Access-Control-Allow-Headers: ['Authorization']
|
||||
Access-Control-Allow-Headers: ['Authorization', 'Accept']
|
||||
Access-Control-Max-Age: [1728000]
|
||||
Access-Control-Allow-Credentials: [true]
|
||||
Access-Control-Expose-Headers: ['Docker-Content-Digest']
|
||||
|
||||
@@ -28,7 +28,7 @@ spec:
|
||||
claimName: docker-registry
|
||||
containers:
|
||||
- name: registry
|
||||
image: "docker.io/registry:2.6.2"
|
||||
image: "docker.io/registry:2.7"
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: registry
|
||||
|
||||
9
examples/populate-registry
Executable file
9
examples/populate-registry
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
REGISTRY="${1:-localhost:5000}"
|
||||
|
||||
for image in alpine:latest alpine:edge alpine:3.11 alpine:3.10 alpine:3.9; do
|
||||
docker pull $image
|
||||
docker tag $image $REGISTRY/$image
|
||||
docker push $REGISTRY/$image
|
||||
done
|
||||
19
examples/proxy-headers/README.md
Normal file
19
examples/proxy-headers/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Set custom headers to the registry example
|
||||
|
||||
The interface and the docker registry will be accessible with <http://localhost>.
|
||||
|
||||
This example highlight the usage of custom headers when the UI is used as a proxy. When you wants to use a header name with hyphens, replace them by underscores in the variable. You can put headers in environment variable or in config file `/etc/nginx/.env`. They have the same writing style.
|
||||
|
||||
Headers can be useful in some cases such as avoid sending credentials when you are on the UI. Or give to the registry server other properties such as X-Forward-For header.
|
||||
|
||||
I will set these two headers in this example. X-Forward-For by environment variable and Authorization by file.
|
||||
|
||||
In order to set your credentials in the header, you need to know how [Authorization](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) header works. Here we use the `Basic` authentication scheme, the credentials are constructed like this:
|
||||
- The username and the password are combined with a colon (`registry:ui`).
|
||||
- The resulting string is base64 encoded (`cmVnaXN0cnk6dWk=`). You can simply run `echo -n "registry:ui" | base64`.
|
||||
- In your header, put this value `Basic cmVnaXN0cnk6dWk=`
|
||||
- In your `/etc/nginx/.env`, the file will contains `NGINX_PROXY_HEADER_Authorization=Basic cmVnaXN0cnk6dWk=`
|
||||
|
||||
For X-Forward-For, replace all hyphens by underscores, and the value will be a nginx variable which is `$proxy_add_x_forwarded_for`. In your docker compose you will need to duplicate the `$` character. In your docker-compose, your environment will look like `NGINX_PROXY_HEADER_X_Forwarded_For=$$proxy_add_x_forwarded_for`
|
||||
|
||||
As usual, run the project with `docker-compose up -d` (for background mode)
|
||||
28
examples/proxy-headers/docker-compose.yml
Normal file
28
examples/proxy-headers/docker-compose.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
version: '2.0'
|
||||
services:
|
||||
registry:
|
||||
image: registry:2.7
|
||||
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
|
||||
- NGINX_PROXY_HEADER_X_Forwarded_For=$$proxy_add_x_forwarded_for
|
||||
volumes:
|
||||
- ./nginx.env:/etc/nginx/.env
|
||||
depends_on:
|
||||
- registry
|
||||
networks:
|
||||
- registry-ui-net
|
||||
|
||||
networks:
|
||||
registry-ui-net:
|
||||
1
examples/proxy-headers/nginx.env
Normal file
1
examples/proxy-headers/nginx.env
Normal file
@@ -0,0 +1 @@
|
||||
NGINX_PROXY_HEADER_Authorization=Basic cmVnaXN0cnk6dWk=
|
||||
25
examples/proxy-headers/registry-config/credentials.yml
Normal file
25
examples/proxy-headers/registry-config/credentials.yml
Normal 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', 'Accept']
|
||||
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
|
||||
1
examples/proxy-headers/registry-config/htpasswd
Normal file
1
examples/proxy-headers/registry-config/htpasswd
Normal file
@@ -0,0 +1 @@
|
||||
registry:$2y$11$1bmuJLK8HrQl5ACS/WeqRuJLUArUZfUcP2R23asmozEpfN76.pCHy
|
||||
10
examples/read-only-auth/README.md
Normal file
10
examples/read-only-auth/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Docker registry with read only access
|
||||
|
||||
This is the configuration for a docker registry UI using `REGISTRY_URL` (as a proxy) with read only access to the registry.
|
||||
|
||||
There are two htpasswd files. `read-write.htpasswd` a read and write access to the registry and `read-only.htpasswd` for a read only access.
|
||||
|
||||
All users in `read-only.htpasswd` should be in `read-write.htpasswd`.
|
||||
|
||||
Read only user: login: `read` password: `regisrty`.
|
||||
Read and write user: login: `write` password: `regisrty`.
|
||||
27
examples/read-only-auth/docker-compose.yml
Normal file
27
examples/read-only-auth/docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
version: '2.0'
|
||||
services:
|
||||
registry:
|
||||
image: registry:2.7
|
||||
volumes:
|
||||
- ./registry-data:/var/lib/registry
|
||||
- ./registry.yml:/etc/docker/registry/config.yml
|
||||
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
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf
|
||||
- ./read-write.htpasswd:/etc/nginx/auth/read-write.htpasswd:ro
|
||||
- ./read-only.htpasswd:/etc/nginx/auth/read-only.htpasswd
|
||||
depends_on:
|
||||
- registry
|
||||
networks:
|
||||
- registry-ui-net
|
||||
networks:
|
||||
registry-ui-net:
|
||||
55
examples/read-only-auth/nginx.conf
Normal file
55
examples/read-only-auth/nginx.conf
Normal file
@@ -0,0 +1,55 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
#! resolver 127.0.0.11; # This is for docker container name resolver
|
||||
#charset koi8-r;
|
||||
#access_log /var/log/nginx/host.access.log main;
|
||||
|
||||
# disable any limits to avoid HTTP 413 for large image uploads
|
||||
client_max_body_size 0;
|
||||
|
||||
# required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)
|
||||
chunked_transfer_encoding on;
|
||||
# required for strict SNI checking: see Issue #70 (https://github.com/Joxit/docker-registry-ui/issues/70)
|
||||
proxy_ssl_server_name on;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers "X-Accel-Buffering";
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /v2 {
|
||||
# Do not allow connections from docker 1.5 and earlier
|
||||
# docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
|
||||
if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
|
||||
return 404;
|
||||
}
|
||||
# To add basic authentication to v2 use auth_basic setting.
|
||||
auth_basic "Registry realm";
|
||||
auth_basic_user_file /etc/nginx/auth/read-write.htpasswd;
|
||||
# For requests that *aren't* a PUT, POST, or DELETE
|
||||
limit_except PUT POST DELETE {
|
||||
auth_basic_user_file /etc/nginx/auth/read-only.htpasswd;
|
||||
}
|
||||
|
||||
proxy_pass http://registry:5000;
|
||||
}
|
||||
|
||||
#error_page 404 /404.html;
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
# deny access to .htaccess files, if Apache's document root
|
||||
# concurs with nginx's one
|
||||
#
|
||||
#location ~ /\.ht {
|
||||
# deny all;
|
||||
#}
|
||||
}
|
||||
2
examples/read-only-auth/read-only.htpasswd
Normal file
2
examples/read-only-auth/read-only.htpasswd
Normal file
@@ -0,0 +1,2 @@
|
||||
read:$2y$05$NHpWy4HuCM8Ol2wZJsf6/.cJtGgv61jWzHTCYt/WntzRLDse1IuVO
|
||||
write:$2y$05$aqLmS1hXojRnubpSN4aVDeZ8wLhJtmQr4v0NiZl4KHUHXhDVnyoQm
|
||||
1
examples/read-only-auth/read-write.htpasswd
Normal file
1
examples/read-only-auth/read-write.htpasswd
Normal file
@@ -0,0 +1 @@
|
||||
write:$2y$05$aqLmS1hXojRnubpSN4aVDeZ8wLhJtmQr4v0NiZl4KHUHXhDVnyoQm
|
||||
21
examples/read-only-auth/registry.yml
Normal file
21
examples/read-only-auth/registry.yml
Normal 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', 'Accept']
|
||||
Access-Control-Max-Age: [1728000]
|
||||
Access-Control-Allow-Credentials: [true]
|
||||
Access-Control-Expose-Headers: ['Docker-Content-Digest']
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '3.1'
|
||||
services:
|
||||
registry:
|
||||
image: registry:2.6.2
|
||||
image: registry:2.7
|
||||
volumes:
|
||||
- /opt/docker-registry:/var/lib/registry
|
||||
environment:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '2.0'
|
||||
services:
|
||||
registry:
|
||||
image: registry:2.6.2
|
||||
image: registry:2.7
|
||||
volumes:
|
||||
- ./registry-data:/var/lib/registry
|
||||
- ./registry-config/credentials.yml:/etc/docker/registry/config.yml
|
||||
|
||||
@@ -15,7 +15,7 @@ http:
|
||||
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-Allow-Headers: ['Authorization', 'Accept']
|
||||
Access-Control-Max-Age: [1728000]
|
||||
Access-Control-Allow-Credentials: [true]
|
||||
Access-Control-Expose-Headers: ['Docker-Content-Digest']
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '2.0'
|
||||
services:
|
||||
registry:
|
||||
image: registry:2.6.2
|
||||
image: registry:2.7
|
||||
volumes:
|
||||
- ./registry-data:/var/lib/registry
|
||||
networks:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '2.0'
|
||||
services:
|
||||
registry:
|
||||
image: registry:2.6.2
|
||||
image: registry:2.7
|
||||
ports:
|
||||
- 5000:5000
|
||||
volumes:
|
||||
|
||||
@@ -15,7 +15,7 @@ http:
|
||||
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-Allow-Headers: ['Authorization', 'Accept']
|
||||
Access-Control-Max-Age: [1728000]
|
||||
Access-Control-Allow-Credentials: [true]
|
||||
Access-Control-Expose-Headers: ['Docker-Content-Digest']
|
||||
|
||||
@@ -15,7 +15,7 @@ http:
|
||||
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-Allow-Headers: ['Authorization', 'Accept']
|
||||
Access-Control-Max-Age: [1728000]
|
||||
Access-Control-Allow-Credentials: [true]
|
||||
Access-Control-Expose-Headers: ['Docker-Content-Digest']
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '2.0'
|
||||
services:
|
||||
registry:
|
||||
image: registry:2.6.2
|
||||
image: registry:2.7
|
||||
ports:
|
||||
- 5000:5000
|
||||
volumes:
|
||||
|
||||
@@ -16,7 +16,7 @@ const useref = require('gulp-useref');
|
||||
const injectVersion = require('gulp-inject-version');
|
||||
const merge = require('stream-series');
|
||||
|
||||
const allTags = ['src/tags/*.tag', 'src/tags/dialogs/*.tag'];
|
||||
const allTags = ['src/tags/*.riot', 'src/tags/dialogs/*.riot'];
|
||||
|
||||
const allScripts = [
|
||||
'src/scripts/http.js',
|
||||
@@ -24,7 +24,7 @@ const allScripts = [
|
||||
'src/scripts/utils.js'
|
||||
];
|
||||
|
||||
const staticTags = ['src/tags/*.tag'];
|
||||
const staticTags = ['src/tags/*.riot'];
|
||||
|
||||
const staticScripts = [
|
||||
'src/scripts/http.js',
|
||||
|
||||
@@ -12,6 +12,8 @@ server {
|
||||
chunked_transfer_encoding on;
|
||||
# required for strict SNI checking: see Issue #70 (https://github.com/Joxit/docker-registry-ui/issues/70)
|
||||
proxy_ssl_server_name on;
|
||||
proxy_buffering off;
|
||||
proxy_ignore_headers "X-Accel-Buffering";
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
@@ -24,7 +26,7 @@ server {
|
||||
#! if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
|
||||
#! return 404;
|
||||
#! }
|
||||
#! proxy_set_header Host $http_host;
|
||||
#! ${NGINX_PROXY_HEADERS}
|
||||
#! proxy_pass ${REGISTRY_URL};
|
||||
#! }
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"name": "docker-registry-ui",
|
||||
"version": "1.2.2",
|
||||
"version": "1.5.2",
|
||||
"scripts": {
|
||||
"build": "./node_modules/gulp/bin/gulp.js build"
|
||||
"build": "./node_modules/gulp/bin/gulp.js build",
|
||||
"build:electron": "npm run build && cd examples/electron && npm install && npm run dist"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -14,7 +15,7 @@
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"del": "^3.0.0",
|
||||
"gulp": "^4.0.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-clean-css": "^4.2.0",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-filter": "^5.1.0",
|
||||
|
||||
BIN
src/fonts/Roboto-Bold.ttf
Normal file
BIN
src/fonts/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Bold.woff
Normal file
BIN
src/fonts/Roboto-Bold.woff
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Bold.woff2
Normal file
BIN
src/fonts/Roboto-Bold.woff2
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Light.ttf
Normal file
BIN
src/fonts/Roboto-Light.ttf
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Light.woff
Normal file
BIN
src/fonts/Roboto-Light.woff
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Light.woff2
Normal file
BIN
src/fonts/Roboto-Light.woff2
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Regular.eot
Normal file
BIN
src/fonts/Roboto-Regular.eot
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Regular.ttf
Normal file
BIN
src/fonts/Roboto-Regular.ttf
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Regular.woff
Normal file
BIN
src/fonts/Roboto-Regular.woff
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Regular.woff2
Normal file
BIN
src/fonts/Roboto-Regular.woff2
Normal file
Binary file not shown.
BIN
src/fonts/RobotoMono-Regular.eot
Normal file
BIN
src/fonts/RobotoMono-Regular.eot
Normal file
Binary file not shown.
BIN
src/fonts/RobotoMono-Regular.ttf
Normal file
BIN
src/fonts/RobotoMono-Regular.ttf
Normal file
Binary file not shown.
BIN
src/fonts/RobotoMono-Regular.woff
Normal file
BIN
src/fonts/RobotoMono-Regular.woff
Normal file
Binary file not shown.
BIN
src/fonts/RobotoMono-Regular.woff2
Normal file
BIN
src/fonts/RobotoMono-Regular.woff2
Normal file
Binary file not shown.
@@ -25,8 +25,8 @@
|
||||
<!-- build:css style.css -->
|
||||
<link href="style.css" rel="stylesheet" type="text/css">
|
||||
<link href="material-icons.css" rel="stylesheet" type="text/css">
|
||||
<link href="roboto.css" rel="stylesheet" type="text/css">
|
||||
<!-- endbuild -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&lang=en" rel="stylesheet" type="text/css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta property="og:site_name" content="Docker Registry UI" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
@@ -43,22 +43,24 @@
|
||||
<script src="../node_modules/riot-mui/build/js/riot-mui.js"></script>
|
||||
<!-- endbuild -->
|
||||
<!-- 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/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>
|
||||
<script src="tags/catalog.riot" type="riot/tag"></script>
|
||||
<script src="tags/catalog-element.riot" type="riot/tag"></script>
|
||||
<script src="tags/tag-history-button.riot" type="riot/tag"></script>
|
||||
<script src="tags/tag-history.riot" type="riot/tag"></script>
|
||||
<script src="tags/tag-history-element.riot" type="riot/tag"></script>
|
||||
<script src="tags/taglist.riot" type="riot/tag"></script>
|
||||
<script src="tags/image-tag.riot" type="riot/tag"></script>
|
||||
<script src="tags/remove-image.riot" type="riot/tag"></script>
|
||||
<script src="tags/copy-to-clipboard.riot" type="riot/tag"></script>
|
||||
<script src="tags/dialogs/add.riot" type="riot/tag"></script>
|
||||
<script src="tags/dialogs/change.riot" type="riot/tag"></script>
|
||||
<script src="tags/dialogs/remove.riot" type="riot/tag"></script>
|
||||
<script src="tags/dialogs/menu.riot" type="riot/tag"></script>
|
||||
<script src="tags/image-size.riot" type="riot/tag"></script>
|
||||
<script src="tags/image-date.riot" type="riot/tag"></script>
|
||||
<script src="tags/image-content-digest.riot" type="riot/tag"></script>
|
||||
<script src="tags/pagination.riot" type="riot/tag"></script>
|
||||
<script src="tags/app.riot" type="riot/tag"></script>
|
||||
<script src="scripts/http.js"></script>
|
||||
<script src="scripts/script.js"></script>
|
||||
<script src="scripts/utils.js"></script>
|
||||
|
||||
52
src/roboto.css
Normal file
52
src/roboto.css
Normal file
@@ -0,0 +1,52 @@
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(fonts/Roboto-Regular.eot); /* For IE6-8 */
|
||||
src: local('Roboto Light'),
|
||||
local('Roboto-Light'),
|
||||
url(fonts/Roboto-Light.woff2) format('woff2'),
|
||||
url(fonts/Roboto-Light.woff) format('woff'),
|
||||
url(fonts/Roboto-Light.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(fonts/Roboto-Regular.eot); /* For IE6-8 */
|
||||
src: local('Roboto Regular'),
|
||||
local('Roboto-Regular'),
|
||||
url(fonts/Roboto-Regular.woff2) format('woff2'),
|
||||
url(fonts/Roboto-Regular.woff) format('woff'),
|
||||
url(fonts/Roboto-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(fonts/Roboto-Regular.eot); /* For IE6-8 */
|
||||
src: local('Roboto Bold'),
|
||||
local('Roboto-Bold'),
|
||||
url(fonts/Roboto-Bold.woff2) format('woff2'),
|
||||
url(fonts/Roboto-Bold.woff) format('woff'),
|
||||
url(fonts/Roboto-Bold.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(fonts/RobotoMono-Regular.eot); /* For IE6-8 */
|
||||
src: local('Roboto Mono Regular'),
|
||||
local('RobotoMono-Regular'),
|
||||
local('Roboto-Mono-Regular'),
|
||||
url(fonts/RobotoMono-Regular.woff2) format('woff2'),
|
||||
url(fonts/RobotoMono-Regular.woff) format('woff'),
|
||||
url(fonts/RobotoMono-Regular.ttf) format('truetype');
|
||||
}
|
||||
@@ -22,6 +22,30 @@ function Http() {
|
||||
this._headers = {};
|
||||
}
|
||||
|
||||
Http.prototype.getContentDigest = function(cb) {
|
||||
if (this.oReq.hasHeader('Docker-Content-Digest')) {
|
||||
// Same origin or advanced CORS headers set:
|
||||
// 'Access-Control-Expose-Headers: Docker-Content-Digest'
|
||||
cb(this.oReq.getResponseHeader('Docker-Content-Digest'))
|
||||
} else if (window.crypto && window.TextEncoder) {
|
||||
crypto.subtle.digest(
|
||||
'SHA-256',
|
||||
new TextEncoder().encode(this.oReq.responseText)
|
||||
).then(function (buffer) {
|
||||
cb(
|
||||
'sha256:' + Array.from(
|
||||
new Uint8Array(buffer)
|
||||
).map(function(byte) {
|
||||
return byte.toString(16).padStart(2, '0');
|
||||
}).join('')
|
||||
);
|
||||
})
|
||||
} else {
|
||||
// IE and old Edge
|
||||
// simply do not call the callback and skip the setup downstream
|
||||
}
|
||||
};
|
||||
|
||||
Http.prototype.addEventListener = function(e, f) {
|
||||
this._events[e] = f;
|
||||
const self = this;
|
||||
@@ -41,6 +65,7 @@ Http.prototype.addEventListener = function(e, f) {
|
||||
req.withCredentials = true;
|
||||
req.hasHeader = Http.hasHeader;
|
||||
req.getErrorMessage = Http.getErrorMessage;
|
||||
self.oReq = req;
|
||||
req.send();
|
||||
} else {
|
||||
f.bind(this)();
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
var registryUI = {}
|
||||
registryUI.URL_QUERY_PARAM_REGEX = /[&?]url=/;
|
||||
registryUI.URL_PARAM_REGEX = /^url=/;
|
||||
registryUI.showContentDigest = true;
|
||||
registryUI.catalogElementsLimit = 100000;
|
||||
|
||||
registryUI.name = registryUI.url = function(byPassQueryParam) {
|
||||
registryUI.url = function(byPassQueryParam) {
|
||||
if (!registryUI._url) {
|
||||
const url = registryUI.getUrlQueryParam();
|
||||
if (url) {
|
||||
@@ -33,6 +35,9 @@ registryUI.name = registryUI.url = function(byPassQueryParam) {
|
||||
}
|
||||
return registryUI._url;
|
||||
}
|
||||
registryUI.name = function() {
|
||||
return registryUI.stripHttps(registryUI.url());
|
||||
}
|
||||
registryUI.getRegistryServer = function(i) {
|
||||
try {
|
||||
const res = JSON.parse(localStorage.getItem('registryServer'));
|
||||
@@ -85,7 +90,7 @@ registryUI.removeServer = function(url) {
|
||||
}
|
||||
|
||||
registryUI.updateHistory = function(url) {
|
||||
history.pushState(null, '', (url ? '?url=' + registryUI.encodeURI(url) : '?') + window.location.hash);
|
||||
registryUI.updateQueryString({ url: registryUI.encodeURI(url) })
|
||||
registryUI._url = url;
|
||||
}
|
||||
|
||||
@@ -100,10 +105,12 @@ registryUI.getUrlQueryParam = function () {
|
||||
};
|
||||
|
||||
registryUI.encodeURI = function(url) {
|
||||
if (!url) { return; }
|
||||
return url.indexOf('&') < 0 ? window.encodeURIComponent(url) : btoa(url);
|
||||
};
|
||||
|
||||
registryUI.decodeURI = function(url) {
|
||||
if (!url) { return; }
|
||||
return url.startsWith('http') ? window.decodeURIComponent(url) : atob(url);
|
||||
};
|
||||
|
||||
|
||||
@@ -24,10 +24,17 @@ registryUI.url = function() {
|
||||
return url;
|
||||
};
|
||||
registryUI.name = function() {
|
||||
return '${REGISTRY_TITLE}' || registryUI.url();
|
||||
const name = '${REGISTRY_TITLE}';
|
||||
if (name) {
|
||||
// the user can strip the http prefix if they wish
|
||||
return name;
|
||||
}
|
||||
return registryUI.stripHttps(registryUI.url());
|
||||
};
|
||||
registryUI.pullUrl = '${PULL_URL}';
|
||||
registryUI.isImageRemoveActivated = true;
|
||||
registryUI.showContentDigest = true;
|
||||
registryUI.catalogElementsLimit = 100000;
|
||||
registryUI.catalog = {};
|
||||
registryUI.taglist = {};
|
||||
registryUI.taghistory = {};
|
||||
|
||||
@@ -65,4 +65,63 @@ registryUI.getHistoryIcon = function(attribute) {
|
||||
default:
|
||||
''
|
||||
}
|
||||
}
|
||||
|
||||
registryUI.getPage = function(elts, page, limit) {
|
||||
if (!limit) { limit = 100; }
|
||||
if (!elts) { return []; }
|
||||
return elts.slice((page - 1) * limit, limit * page);
|
||||
}
|
||||
|
||||
registryUI.getNumPages = function(elts, limit) {
|
||||
if (!limit) { limit = 100; }
|
||||
if (!elts) { return 0; }
|
||||
return Math.trunc(elts.length / limit) + 1;
|
||||
}
|
||||
|
||||
registryUI.getPageLabels = function(page, nPages) {
|
||||
var pageLabels = [];
|
||||
var maxItems = 10;
|
||||
if (nPages === 1) { return pageLabels; }
|
||||
if (page !== 1 && nPages >= maxItems) {
|
||||
pageLabels.push({'icon': 'first_page', page: 1});
|
||||
pageLabels.push({'icon': 'chevron_left', page: page - 1});
|
||||
}
|
||||
var start = Math.round(Math.max(1, Math.min(page - maxItems / 2, nPages - maxItems + 1)));
|
||||
for (var i = start; i < Math.min(nPages + 1, start + maxItems); i++) {
|
||||
pageLabels.push({
|
||||
page: i,
|
||||
current: i === page,
|
||||
'space-left': page === 1 && nPages > maxItems,
|
||||
'space-right': page === nPages && nPages > maxItems
|
||||
});
|
||||
}
|
||||
if (page !== nPages && nPages >= maxItems) {
|
||||
pageLabels.push({'icon': 'chevron_right', page: page + 1});
|
||||
pageLabels.push({'icon': 'last_page', page: nPages});
|
||||
}
|
||||
return pageLabels;
|
||||
}
|
||||
|
||||
registryUI.updateQueryString = function(qs) {
|
||||
var search = '';
|
||||
for (var key in qs) {
|
||||
if (qs[key] !== undefined) {
|
||||
search += (search.length > 0 ? '&' : '?') +key + '=' + qs[key];
|
||||
}
|
||||
}
|
||||
history.pushState(null, '', search + window.location.hash);
|
||||
}
|
||||
|
||||
registryUI.stripHttps = function (url) {
|
||||
if (!url) {
|
||||
return '';
|
||||
}
|
||||
return url.replace(/^https?:\/\//, '');
|
||||
};
|
||||
|
||||
registryUI.eventTransfer = function(from, to) {
|
||||
from.on('*', function(event, param) {
|
||||
to.trigger(event, param);
|
||||
})
|
||||
}
|
||||
156
src/style.css
156
src/style.css
@@ -24,10 +24,6 @@ html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.section-centerd {
|
||||
margin: auto;
|
||||
}
|
||||
@@ -48,21 +44,54 @@ main {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
material-card {
|
||||
min-height: 200px;
|
||||
max-width: 75%;
|
||||
material-card, material-tabs, pagination .conatianer {
|
||||
max-width: 95%;
|
||||
margin: auto;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 950px){
|
||||
material-card {
|
||||
width: 95%;
|
||||
max-width: 750px;
|
||||
pagination .conatianer {
|
||||
display: flex;
|
||||
display: -moz-flex;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
}
|
||||
|
||||
pagination .conatianer .pagination-centered {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
/* 1515px * 0.95 = 1440px */
|
||||
@media screen and (min-width: 1515px){
|
||||
material-card, material-tabs, pagination .conatianer {
|
||||
max-width: 1440px;
|
||||
}
|
||||
}
|
||||
|
||||
material-tabs {
|
||||
display: block;
|
||||
-webkit-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16), 0 2px 10px 0 rgba(0,0,0,.12);
|
||||
-ms-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
|
||||
-moz-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
|
||||
-o-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,.16), 0 2px 10px 0 rgba(0,0,0,.12);
|
||||
}
|
||||
|
||||
material-tabs material-button,
|
||||
material-tabs material-button .content .text {
|
||||
background-color: #fff;
|
||||
color: #aaa;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
material-tabs .line-wrapper .line {
|
||||
background-color: #25313b;
|
||||
}
|
||||
material-tabs material-button.selected .content .text {
|
||||
color: #25313b;
|
||||
}
|
||||
|
||||
material-spinner {
|
||||
align-self: center;
|
||||
}
|
||||
@@ -95,6 +124,7 @@ h2 {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.material-card-title-action h2 .source-hint,
|
||||
.material-card-title-action h2 .item-count {
|
||||
font-size: 0.7em;
|
||||
margin-left: 1em;
|
||||
@@ -212,12 +242,14 @@ material-card table th {
|
||||
}
|
||||
|
||||
material-card material-button:hover,
|
||||
material-card table tbody tr:hover {
|
||||
material-card table tbody tr:hover,
|
||||
pagination material-button:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
material-card material-button,
|
||||
material-card table tbody tr {
|
||||
material-card table tbody tr,
|
||||
pagination material-button {
|
||||
transition-duration: .28s;
|
||||
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
|
||||
transition-property: background-color;
|
||||
@@ -269,6 +301,11 @@ material-button .content i.material-icons,
|
||||
color: #777;
|
||||
}
|
||||
|
||||
material-button[disabled] .content i.material-icons,
|
||||
material-checkbox[disabled] .content i.material-icons {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
material-snackbar .toast {
|
||||
height: auto;
|
||||
}
|
||||
@@ -326,7 +363,8 @@ dropdown-item, #menu-control-dropdown p {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
material-popup material-button {
|
||||
material-popup material-button,
|
||||
pagination material-button {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
@@ -342,25 +380,16 @@ material-popup .popup {
|
||||
|
||||
footer {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
z-index: 75;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-height: 750px) {
|
||||
main {
|
||||
min-height: calc(100% - 164px - 2.5em);
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
footer {
|
||||
position: relative;
|
||||
}
|
||||
main {
|
||||
min-height: calc(100% - 136px);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 760px) and (max-height: 750px) {
|
||||
main {
|
||||
min-height: calc(100% - 144px - 2.5em);
|
||||
}
|
||||
material-footer {
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
.select-padding {
|
||||
@@ -385,7 +414,27 @@ select {
|
||||
}
|
||||
|
||||
.copy-to-clipboard {
|
||||
padding: 12px 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
#image-tag-header {
|
||||
/* spacing for clipboard + default th spacing */
|
||||
/* 5 + 2 + 3 + 24 + 3 + 2 + 18 */
|
||||
padding-right: 57px;
|
||||
}
|
||||
image-tag, .copy-to-clipboard {
|
||||
display: inline-block;
|
||||
}
|
||||
image-content-digest {
|
||||
display: none;
|
||||
font-family: 'Roboto Mono', 'Roboto', 'Helvetica', 'Arial', sans-serif !important;
|
||||
}
|
||||
@media screen and (min-width: 1024px) {
|
||||
#image-content-digest-header {
|
||||
padding-right: 57px;
|
||||
}
|
||||
image-content-digest {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.show-tag-history {
|
||||
@@ -403,6 +452,14 @@ select {
|
||||
padding: 7px 5px;
|
||||
}
|
||||
|
||||
taglist .creation-date {
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
taglist .image-size {
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
catalog material-card,
|
||||
tag-history material-card {
|
||||
min-height: auto;
|
||||
@@ -423,6 +480,7 @@ tag-history-element {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
min-width: 100px;
|
||||
min-height: 3em;
|
||||
width: 420px;
|
||||
float: left;
|
||||
overflow-x: auto;
|
||||
@@ -445,7 +503,8 @@ tag-history-button button {
|
||||
border: none;
|
||||
}
|
||||
|
||||
material-card material-button {
|
||||
material-card material-button,
|
||||
pagination material-button {
|
||||
max-height: 30px;
|
||||
max-width: 30px;
|
||||
}
|
||||
@@ -454,7 +513,8 @@ material-button:hover material-waves {
|
||||
background: none;
|
||||
}
|
||||
|
||||
material-card material-button {
|
||||
material-card material-button,
|
||||
pagination material-button {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
@@ -475,6 +535,16 @@ catalog-element catalog-element.hide material-card {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
catalog-element catalog-element > .content {
|
||||
margin-left: 3em;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1515px){
|
||||
catalog-element catalog-element > .content material-card {
|
||||
max-width: calc(1440px - 3em);
|
||||
}
|
||||
}
|
||||
|
||||
remove-image {
|
||||
width: 30px;
|
||||
}
|
||||
@@ -506,4 +576,30 @@ material-checkbox .checkbox {
|
||||
|
||||
material-checkbox .checkbox.checked {
|
||||
background-color: #777;
|
||||
}
|
||||
|
||||
pagination material-button {
|
||||
padding: 0.2em 0.75em;
|
||||
}
|
||||
|
||||
pagination material-button .content {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
line-height: 1.9em;
|
||||
}
|
||||
|
||||
pagination material-button.current {
|
||||
border: 1px solid rgba(0, 0, 0, .12);
|
||||
}
|
||||
|
||||
pagination material-button.current.space-left {
|
||||
margin-left: 85px;
|
||||
}
|
||||
|
||||
pagination material-button.current.space-right {
|
||||
margin-right: 85px;
|
||||
}
|
||||
|
||||
pagination material-button .content i.material-icons {
|
||||
color: #000;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
</material-footer>
|
||||
</footer>
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
registryUI.appTag = this;
|
||||
route.base('#!');
|
||||
route('', function() {
|
||||
@@ -92,12 +92,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
registryUI.errorSnackbar = function(message) {
|
||||
return registryUI.snackbar(message, true);
|
||||
};
|
||||
registryUI.showErrorCanNotReadContentDigest = function() {
|
||||
registryUI.errorSnackbar(
|
||||
'Access on registry response was blocked. Try adding the header ' +
|
||||
'`Access-Control-Expose-Headers: Docker-Content-Digest`' +
|
||||
' to your proxy or registry: ' +
|
||||
'https://docs.docker.com/registry/configuration/#http'
|
||||
);
|
||||
};
|
||||
registryUI.cleanName = function() {
|
||||
const url = registryUI.pullUrl || (registryUI.url() && registryUI.url().length > 0 && registryUI.url()) || window.location.host;
|
||||
if (url) {
|
||||
return url.startsWith('http') ? url.replace(/https?:\/\//, '') : url;
|
||||
}
|
||||
return '';
|
||||
return registryUI.stripHttps(url);
|
||||
};
|
||||
route.parser(null, function(path, filter) {
|
||||
const f = filter
|
||||
@@ -113,9 +118,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
return char >= '0' && char <= '9';
|
||||
};
|
||||
|
||||
registryUI.DockerImage = function(name, tag) {
|
||||
registryUI.DockerImage = function(name, tag, list) {
|
||||
this.name = name;
|
||||
this.tag = tag;
|
||||
this.list = list;
|
||||
this.chars = 0;
|
||||
riot.observable(this);
|
||||
this.on('get-size', function() {
|
||||
if (this.size !== undefined) {
|
||||
@@ -130,8 +137,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
return this.fillInfo();
|
||||
});
|
||||
this.on('get-date', function() {
|
||||
if (this.date !== undefined) {
|
||||
return this.trigger('date', this.date);
|
||||
if (this.creationDate !== undefined) {
|
||||
return this.trigger('creation-date', this.creationDate);
|
||||
}
|
||||
return this.fillInfo();
|
||||
});
|
||||
this.on('content-digest-chars', function (chars) {
|
||||
this.chars = chars;
|
||||
});
|
||||
this.on('get-content-digest-chars', function() {
|
||||
return this.trigger('content-digest-chars', this.chars);
|
||||
});
|
||||
this.on('get-content-digest', function() {
|
||||
if (this.digest !== undefined) {
|
||||
return this.trigger('content-digest', this.digest);
|
||||
}
|
||||
return this.fillInfo();
|
||||
});
|
||||
@@ -174,6 +193,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
oReq.addEventListener('loadend', function() {
|
||||
if (this.status == 200 || this.status == 202) {
|
||||
const response = JSON.parse(this.responseText);
|
||||
if (response.mediaType === 'application/vnd.docker.distribution.manifest.list.v2+json') {
|
||||
self.trigger('list', response);
|
||||
const manifest = response.manifests[0];
|
||||
const image = new registryUI.DockerImage(self.name, manifest.digest)
|
||||
registryUI.eventTransfer(image, self)
|
||||
image.fillInfo()
|
||||
self.variants = [image];
|
||||
return;
|
||||
}
|
||||
self.size = response.layers.reduce(function(acc, e) {
|
||||
return acc + e.size;
|
||||
}, 0);
|
||||
@@ -181,6 +209,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
self.layers = response.layers;
|
||||
self.trigger('size', self.size);
|
||||
self.trigger('sha256', self.sha256);
|
||||
oReq.getContentDigest(function (digest) {
|
||||
self.digest = digest;
|
||||
self.trigger('content-digest', digest);
|
||||
if (!digest) {
|
||||
registryUI.showErrorCanNotReadContentDigest();
|
||||
}
|
||||
});
|
||||
self.getBlobs(response.config.digest)
|
||||
} else if (this.status == 404) {
|
||||
registryUI.errorSnackbar('Manifest for ' + self.name + ':' + self.tag + ' not found');
|
||||
@@ -189,7 +224,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
}
|
||||
});
|
||||
oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/manifests/' + self.tag);
|
||||
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
|
||||
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json'
|
||||
+ (self.list ? ', application/vnd.docker.distribution.manifest.list.v2+json' : ''));
|
||||
oReq.send();
|
||||
};
|
||||
|
||||
@@ -217,7 +253,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
}
|
||||
});
|
||||
oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/blobs/' + blob);
|
||||
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
|
||||
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json');
|
||||
oReq.send();
|
||||
};
|
||||
|
||||
@@ -225,6 +261,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
route('taglist/' + image);
|
||||
};
|
||||
|
||||
registryUI.getPageQueryParam = function() {
|
||||
var qs = route.query();
|
||||
try {
|
||||
return qs.page !== undefined ? parseInt(qs.page.replace(/#.*/, '')) : 1;
|
||||
} catch(e) { return 1; }
|
||||
}
|
||||
|
||||
registryUI.getQueryParams = function(update) {
|
||||
var qs = route.query();
|
||||
update = update || {};
|
||||
for (var key in qs) {
|
||||
if (qs[key] !== undefined) {
|
||||
qs[key] = qs[key].replace(/#!.*/, '');
|
||||
} else {
|
||||
delete qs[key];
|
||||
}
|
||||
}
|
||||
for (var key in update) {
|
||||
if (update[key] !== undefined) {
|
||||
qs[key] = update[key];
|
||||
}
|
||||
}
|
||||
return qs;
|
||||
}
|
||||
route.start(true);
|
||||
</script>
|
||||
</app>
|
||||
@@ -16,19 +16,21 @@ 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>
|
||||
<div class="content">
|
||||
<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 if="{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 if="{typeof opts.item !== "string"}" class="animated {hide: !expanded, expanding: expanding}" each="{item in item.images}" />
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
this.on('mount', function() {
|
||||
const self = this;
|
||||
const card = this.tags['material-card'];
|
||||
@@ -16,19 +16,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<catalog>
|
||||
<!-- Begin of tag -->
|
||||
<material-card ref="catalog-tag" class="catalog">
|
||||
<material-card ref="catalog-tag" class="catalog header">
|
||||
<div class="material-card-title-action">
|
||||
<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>
|
||||
</material-card>
|
||||
<div hide="{ registryUI.catalog.loadend }" class="spinner-wrapper">
|
||||
<material-spinner></material-spinner>
|
||||
</div>
|
||||
<catalog-element each="{ item in registryUI.catalog.repositories }" />
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
registryUI.catalog.instance = this;
|
||||
registryUI.catalog.display = function() {
|
||||
registryUI.catalog.repositories = [];
|
||||
@@ -36,9 +36,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
oReq.addEventListener('load', function() {
|
||||
registryUI.catalog.repositories = [];
|
||||
if (this.status == 200) {
|
||||
if (!registryUI.url()) {
|
||||
registryUI._url = window.location.origin + window.location.pathname.replace(/\/+$/, '')
|
||||
}
|
||||
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) {
|
||||
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) + '/';
|
||||
@@ -65,7 +69,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
registryUI.catalog.loadend = true;
|
||||
registryUI.catalog.instance.update();
|
||||
});
|
||||
oReq.open('GET', registryUI.url() + '/v2/_catalog?n=100000');
|
||||
oReq.open('GET', registryUI.url() + '/v2/_catalog?n=' + registryUI.catalogElementsLimit);
|
||||
oReq.send();
|
||||
};
|
||||
registryUI.catalog.display();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user