Compare commits

...

126 Commits
2.3.2 ... main

Author SHA1 Message Date
Joxit
82f6240dab build: release 2.6.0 🚀 2026-01-20 00:28:17 +01:00
Joxit
21f33f5d4b fix(tags): use Material Symbols instead of Icons 2025-11-11 14:31:03 +01:00
Lukas Engelter
83c33e9d05 feat: add DOCKER_REGISTRY_UI_TITLE and ENABLE_VERSION_NOTIFICATION (#369)
* feature: link title to main view

this way it is possible to go back to the main view, even if two layers deep, i.e. in the history view.

* feature: allow the customization of the title shown in the logo section

we already have REGISTRY_TITLE, but it is used as a reference to where the images come from: "Repositories of …". The UI however does not need to be used in a 1:1 context of the registry. Therefore, the project would benefit from this feature:

We therefore introduce "DOCKER_REGISTRY_UI_TITLE" as a new option, that optionally allows to change the title for more advanced customization and to enable more use cases for this software.

If it is not set, everything will stay the same as prior to this commit and the title will remain "Docker Registry UI"

Note, that the footer will always contain a reference to this project. I think it doesn't need to referenced twice, and makes room for some more customization.

* feature: allow to optionally disable the version check

Note: The default remains to have the version check enabled. It is though sometimes useful to disable it, in the context of customers or open source interests of your software not to bother with version updates to Docker Registry UI.

For example, if you use something like watchtower anyway, there is no need to bother with update notifications to anyone else other then the admin of the server.

* fix: wrongly named environment variable in error template

The environment variables named in the CATALOG_BRANCHING_CONFIGURATION error were wrong.

During that find, i found out that one needs to set both min and max to 0 to disable branching. I tried to make this clear in the README.md file.

* feat: update title and version notification options

Version check done every week instead of every days

---------

Co-authored-by: Joxit <joxit972@gmail.com>
2025-11-08 01:22:51 +01:00
Dmitriy Pertsev
433a2aa17a fix: update for new unprivileged nginx images (#433)
fix #432

---------

Co-authored-by: Joxit <joxit972@gmail.com>
2025-11-07 23:58:31 +01:00
Joxit
3381fb428d feat: add /version.json endpoint 2025-07-20 18:17:43 +02:00
Joxit
7a5e55a2f6 docs: update CORS section 2025-04-04 01:16:35 +02:00
Joxit
06147a8ff0 chore: update dependencies and announce distribution v3 2025-04-03 20:37:35 +02:00
Joxit
a03dd97442 chore: update dependencies 2025-01-18 15:45:39 +01:00
Joxit
3c7429b732 revert: "fix(tag-table): icons for ascending & descending are not shown correctly (#406)"
This reverts commit 9960afe909.
2025-01-16 13:52:26 +01:00
Joxit
68313a1bae docs: update FAQ and default example 2025-01-12 14:11:51 +01:00
Joxit
9960afe909 fix(tag-table): icons for ascending & descending are not shown correctly (#406)
fixes #406
2025-01-11 11:17:25 +01:00
Joxit
22960a2547 chore(confirm-delete): add help icon to link FAQ 2025-01-07 20:32:40 +01:00
Julien-Delavisse
cb776739c2 docs(examples): inconsistency of htpasswd file names with docker compose.yml (read-only-auth example) (#402) 2024-11-28 11:58:30 +01:00
Joxit
b7f732a606 feat(oci): add support to helm exports history and use material symbols 2024-09-13 20:06:14 +02:00
Joxit
079f35976f fix: avoid exceptions and display date when using OCI images (#372)
fixes #372
2024-09-13 20:06:14 +02:00
silverwind
a36e3aac57 docs: improve examples/read-only-auth (#371)
* Improve `examples/read-only-auth`

* Update examples/read-only-auth/nginx.conf
2024-04-05 21:59:24 +02:00
Lukas Engelter
7025df687c feat: add SHOW_TAG_HISTORY option to hide tag history button (#362) 2024-03-14 09:28:54 +01:00
Lukas Engelter
cfbc6e76a8 feat(theme): contrast changes for light and dark themes (#361) 2024-03-12 22:35:45 +01:00
toinux
dc9bdcbedd fix(nginx): too big request header when cookie is defined (#356) 2024-03-10 11:09:21 +01:00
Joxit
6c3c27e215 fix: should notify new version only once a day even when an error occurs (#353)
Add more messages in console

fixes #353
2024-02-08 04:25:14 +01:00
Jones Magloire
6318ccfdf5 docs: delete custom issue template 2023-11-24 18:37:43 +01:00
Christian
686b1709b2 docs: fix a small typo (#345) 2023-10-27 19:51:50 +02:00
Joxit
e79a20a5e5 fix(tag-list): missing argument tagsPerPage for getNumPages function 2023-10-18 23:57:59 +02:00
Joxit
7991442fce fix: DNS name resolving not working in kubernetes (#339)
fixes #339
2023-10-17 02:39:38 +02:00
Joxit
de6d09c98c fix(taglist): refreshing page set all digets to sha256:e3b0c44298... due to missing seesion cache (#337)
fix #337
2023-10-07 12:18:32 +02:00
Joxit
1f2913248e fix(docker): NGINX_PROXY_PASS_URL use long cache DNS resolution (#333)
fix #333
2023-09-23 00:25:28 +02:00
Joxit
3414d7b517 feat(token-auth): check the presence of www-authenticate header before the status code 2023-08-02 23:57:59 +02:00
AJWavio
cd99f6e231 docs: fix registry server configuration example in README (#328, #329) 2023-07-28 22:10:23 +02:00
Joxit
f015187b14 fix: scrollbar always displayed on safari (#327)
fixes #327
2023-07-23 19:22:49 +02:00
Joxit
07713f1425 docs: add information on how to run development environment (#314)
fixes #314
2023-07-23 00:02:31 +02:00
Joxit
f560025e70 docs(templates): update bug report issue template 2023-07-20 18:35:24 +02:00
Joxit
bf9c6c82e7 fix: shift-click for multi-delete-in-a-row selects wrong images (#323)
fixes #323
2023-07-18 00:11:09 +02:00
Joxit
c74a9aeaa3 chore(version-notification): update latest version management 2023-07-17 18:16:42 +02:00
cui fliter
42bcec50df docs: fix some typo (#324)
Signed-off-by: cui fliter <imcusg@gmail.com>
2023-07-14 07:51:54 +02:00
Joxit
18dd5ca129 docs: update project GIF for the new UI 2023-06-19 06:35:19 +02:00
Joxit
84b31f2cfb docs: fix typo in README 2023-06-18 19:49:27 +02:00
Joxit
16d01d4dbf fix(demo): add taglist-order to fix taglist page 2023-06-17 23:55:34 +02:00
Joxit
834a0ea40a ci: use nginx:alpine-slim docker image 2023-06-16 23:32:03 +02:00
Joxit
c8383a9c80 build: release 2.5.0 🚀 2023-06-09 06:53:00 +02:00
Joxit
ae9591c79a fix(taglist): add missing props and state for architectures 2023-06-08 08:28:23 +02:00
Joxit
b88dc4567d feat: check for new versions of Docker Registry UI at start up and notify the user 2023-06-07 00:24:11 +02:00
Joxit
ffb6d14baf feat: add option REGISTRY_SECURED for registries with Basic Auth 2023-06-06 06:06:24 +02:00
Joxit
684f82f24e perf: add http cache for blobs and manifests sha256 2023-06-06 06:04:07 +02:00
Artur Mostowski
9cfb6791f8 feat(taglist): add new option TAGLIST_PAGE_SIZE (#318) 2023-06-05 22:16:40 +02:00
Joxit
4091baa341 feat(taglist): improve visibility of multi-architecture images (#271)
closes #271
2023-06-03 15:33:25 +02:00
Jones Magloire
affb0572c9 feat(catalog): add multi level branching in catalog (#319)
Multi level  branching with `CATALOG_MIN_BRANCHES` and `CATALOG_MAX_BRANCHES`
2023-06-02 19:10:46 +02:00
Joxit
dbfc9fe587 docs(branching): update readme for the new option 2023-06-02 19:08:15 +02:00
Artur Mostowski
d2e6cdcab1 chore(tag-table): fix typo slected -> selected (#317) 2023-05-31 20:18:16 +02:00
Joxit
1031034bc4 feat(branching): add configuration for catalog arborescence 2023-05-30 07:49:18 +02:00
Joxit
398fa65fa1 test(branching): add new test for custom branching 2023-05-29 15:38:38 +02:00
Artur Mostowski
b6604421bb docs: fix typo in readme (#312) 2023-05-26 06:26:44 +02:00
Joxit
03157d841e test(branching): start new configurable branching repository system 2023-05-25 07:01:50 +02:00
Joxit
e7e762d6d9 ci: generate custom version name for dev + main tags 2023-05-24 23:38:06 +02:00
Joxit
8e98c1c63b ci: add test step and create PR workflow 2023-05-23 05:19:51 +02:00
Joxit
aca633720a fix: docker registry >=2.8.2 sets catalog max entries to 1000 (#306)
fixes #306
2023-05-22 21:35:04 +02:00
Joxit
5a340291c2 feat(catalog): add CATALOG_DEFAULT_EXPANDED to expand repositories by default (#302)
closes #302
2023-05-21 10:13:59 +02:00
Joxit
9ebbbc3518 fix: buildx multiarch image date and history not shown (#309)
Support images created with buildx and `--provenance true`

fixes #309
2023-05-20 02:04:22 +02:00
Jones Magloire
d2222bef05 feat(taglist-order): add new option TAGLIST_ORDER (#307)
Create new option named `TAGLIST_ORDER` that will order tags using user defined configuration.

Available values are:
| value | description |
| --- | --- |
| `num-asc;alpha-asc` | Numbers come first in **ascending** order then alphabet in **ascending** order |
| `num-desc;alpha-asc` | Numbers come first in **descending** order then alphabet in **ascending** order |
| `num-asc;alpha-desc` | Numbers come first in **ascending** order then alphabet in **descending** order |
| `num-desc;alpha-desc` | Numbers come first in **descending** order then alphabet in **descending** order |
| `alpha-asc;num-asc` | Alphabet come first in **ascending** order then numbers in **ascending** order |
| `alpha-asc;num-desc`  (default) | Alphabet come first in **ascending** order then numbers in **descending** order |
| `alpha-desc;num-asc` | Alphabet come first in **descending** order then numbers in **ascending** order |
| `alpha-desc;num-desc` | Alphabet come first in **descending** order then numbers in **descending** order |

Some examples in [test/taglist-order.test.js](8bbfc5c390/test/taglist-order.test.js)


closes #294
2023-05-19 12:46:54 +02:00
Joxit
c6dee14d79 fix(taglist-order): improve error handler when the order does not exists 2023-05-18 07:28:07 +02:00
Joxit
92584fc3da docs(taglist-order): update entrypoint and add documentation for TAGLIST_ORDER 2023-05-17 20:32:34 +02:00
Joxit
78606e07f1 feat(taglist-order): change default value to alpha-asc;num-desc 2023-05-16 06:28:35 +02:00
Alexander Wolz
bc80050a44 docs: added missing CORS headers for regular keycloak access via AJAX (#304) 2023-05-13 10:02:56 +02:00
Joxit
8bbfc5c390 feat(taglist-order): add new option taglist-order 2023-05-12 07:23:59 +02:00
Joxit
fbab517a17 feat(utils): add support to alpha first order 2023-05-11 22:15:06 +02:00
Joxit
34d1ed90ad feat(utils): add getTagComparator that generate comparator, only numFirst supported 2023-05-10 05:16:57 +02:00
Joxit
a135c00866 feat(utils): add splitTagToArray that will transform a tag into an array alpha and num splited 2023-05-09 22:34:59 +02:00
Joxit
ba322e076f docs(token-auth-keycloak): update nginx configuration (#303) 2023-05-08 14:42:32 +02:00
Joxit
a77103a2d4 fix(token-auth): always send withCredentials in token auth request 2023-05-07 10:42:32 +02:00
Joxit
b0ea4e5fb8 fix(utils): rename talgistOrderParser to taglistOrderParser 🤦 2023-05-06 22:55:33 +02:00
Joxit
edb5aa97e8 feat(utils): add talgistOrderParser will parse the order string into object 2023-05-05 00:07:29 +02:00
xcaliburinhand
ca7202d1f5 feat: support for the docker spec oauth2 token (#300) 2023-05-03 02:54:07 +02:00
Joxit
a3e987482e fix(utils): taglistOrderVariants improved format 2023-05-02 01:26:11 +02:00
Joxit
2b63fb725c feat(utils): add taglistOrderVariants function to format taglist order 2023-05-01 23:22:48 +02:00
Joxit
b0811086fd fix: hide empty repositories when SHOW_CATALOG_NB_TAGS is active (#299)
fixes #299
2023-04-30 23:06:42 +02:00
Joxit
43a242312c fix(taglist): align creation date to the left
fixes #297
2023-04-29 20:05:30 +02:00
silverwind
bfc901eb0b docs: fix typo in README (#298) 2023-04-27 19:43:29 +02:00
Joxit
f984633bc3 docs: rename home title 2023-04-17 19:47:45 +02:00
Joxit
ea508e6a1d chore: add some code format 2023-04-16 18:33:22 +02:00
Joxit
4f452207c4 docs: add links on badges in README 2023-04-15 22:18:27 +02:00
Joxit
b2590115a7 fix: upgrade riot-mui dependency firefox switch fix 2023-03-20 00:10:01 +01:00
Joxit
e2f2850f41 build: release 2.4.0 🚀 2023-03-18 15:13:17 +01:00
Joxit
a8227d9cba feat(theme): should have auto behaviour when theme option is empty 2023-03-17 07:58:45 +01:00
Steffen Butzer
6a8d984315 fix(tag-table): sorting of images w/o date. (#288)
E.g. OCI build-cache doesn't have a created date available, which leads to an error due to `creationDate` being undefined.
2023-03-16 20:54:22 +01:00
Joxit
564bc4b0b4 docs: refactoring main README and change license dates
Change Features to Hidden Features to highlight only features you may not see
Update Available Options and add linked PR/Issue and the version added
2023-03-15 23:02:31 +01:00
Jones Magloire
5db01a9fbb feat: add switch to manually change theme from light to dark (#291)
This requires `auto` default theme
2023-03-14 06:04:53 +01:00
Joxit
a9fd1a2a23 feat(theme): use localstorage to save the switch state at refresh 2023-03-14 06:01:48 +01:00
Joxit
8b377aee79 feat(theme): add icons to the switch (sun and moon) and use accent-text color 2023-03-10 04:02:38 +01:00
Joxit
8c3189b57f feat(theme): add switch to select light/dark mode when the default theme is auto 2023-03-08 22:14:51 +01:00
Jones Magloire
2dce587840 feat(delete): add support to shift + click for multi delete in a row (#287) 2023-02-06 06:50:06 +01:00
Joxit
dc64f72483 feat(shift-key): add support to shift + click for multi delete in a row 2023-02-04 21:56:32 +01:00
Joxit
959af86333 docs: add name in contributors list 2023-02-02 21:09:04 +01:00
Joxit
6314e8b11e feat(error-page): add a full page for specific errors
This may help people to save their issues

fixes #230
2023-02-01 21:54:37 +01:00
Jones Magloire
b9a157c943 feat(dockerfile): show dockerfile in history page (#286) 2023-01-29 09:30:20 +01:00
Joxit
c3fa2c91d7 feat(dockerfile): create dockerfile dialog with styled content 2023-01-26 00:13:08 +01:00
Joxit
b3278511fb feat(dockerfile): add button for Dockerfile dialog 2023-01-25 22:00:02 +01:00
Jones Magloire
347e201f79 feat(theme): add support for Dark Mode and Custom Theme (#283) 2023-01-24 00:20:50 +01:00
Joxit
c9b2415d1e docs: add documentation for theme options 2023-01-23 23:38:15 +01:00
Joxit
0a6d08bfdd ci: disable proxy request buffering (#282)
fixes #282
2023-01-22 20:53:12 +01:00
Joxit
1bfbcbf59f fix: styling for small screens 2023-01-21 02:37:50 +01:00
Joxit
e4369f4ec9 feat(theme): add support to auto theme whith media query 2023-01-20 19:45:18 +01:00
Joxit
8c402442c2 feat(theme): add theme colors to paginaion with some cleanup 2023-01-19 20:06:36 +01:00
Joxit
ef240ccf1d feat: add support to THEME* options 2023-01-18 21:22:18 +01:00
Joxit
3693662d98 fix: update docker logo to support custom fill colors 2023-01-16 18:51:34 +01:00
Joxit
89ae4f64c8 feat(theme): add import SVG for docker image 2023-01-15 15:51:57 +01:00
Joxit
2feff56619 feat(theme): add support for header and footer 2023-01-08 20:53:01 +01:00
Joxit
441def4855 feat(theme): add support for accent-text 2023-01-07 20:30:24 +01:00
Joxit
9b5b935637 feat(theme): add support for dialogs 2023-01-06 19:10:13 +01:00
Joxit
367ca0380c feat(theme): add support for custom themes 2023-01-05 22:29:37 +01:00
Joxit
5983935f84 ci: fix npm dependencies for node v16 2022-12-31 10:49:24 +01:00
Jones Magloire
a0dcc84ca6 feat(riot-mui): upgrade riot mui and all dependencies (#279)
I [forked riot-mui](https://github.com/Joxit/riot-5-mui) in 2021 to be compatible with [riot.js 5+](https://riot.js.org/) because they change a lot of stuff. 

This was bundled in 2.0.0 of docker-registry-ui (see https://github.com/Joxit/docker-registry-ui/pull/176)

Now im improving riot-mui's DX to be more component oriented and add new features in the library.

Major changes:
* CTRL + click on buttons (catalog <=> taglist; taglist <=> tag-history)
* Fix history multi-arch tabs
* Fix tag list pagination (creation date missing) 

This is still a work in progress but I'm integrating this in Docker-Registry-UI. It will help to have new features like dark mode or custom UI.

Stay tuned!
2022-12-31 10:15:14 +01:00
Joxit
1e08561b3a feat(riot-mui): set material-icon size to 24px globally 2022-12-31 10:13:30 +01:00
Joxit
228762c641 feat(riot-mui): now we can CTRL + click on catalog elements 2022-12-29 23:50:47 +01:00
Joxit
696aa39012 fix(riot-mui): creation date from tag list was not updating on page change 2022-12-27 00:05:29 +01:00
Joxit
d13f81c9af feat(riot-mui): use new material-dropdown component 2022-12-26 23:22:36 +01:00
Joxit
ba2107f765 fix(riot-ui): upgrade tag dialogs 2022-12-08 23:35:12 +01:00
Joxit
f779f43173 fix(riot-ui): upgrade tag history 2022-12-07 09:00:33 +01:00
Joxit
dd251b55a0 fix(riot-ui): upgrade and fix dialog confirm popup 2022-12-06 23:08:23 +01:00
Joxit
fe724c4d1f feat(riot-mui): upgrade tag history components 2022-12-05 19:46:41 +01:00
Joxit
68d19991ef feat(riot-mui): upgrade and fix taglist pagination 2022-12-04 00:21:00 +01:00
Joxit
54a954f8c3 feat(riot-mui): upgrade tag-list components (table, list, remove image and tag history button) 2022-12-03 15:07:58 +01:00
Joxit
c7368a3104 chore: updated dependencies (riot v7 & rollup v3) 2022-12-02 22:14:19 +01:00
Joxit
d11fd42418 feat(riot-mui): upgrade home page with navbar and footer 2022-11-30 22:06:46 +01:00
Joxit
017f6620f0 feat(taglist): show size with a decimal for images between 1 and 10
fixes #276
2022-11-26 22:41:29 +01:00
Joxit
ee93d5bba8 chore: add contributing.md 2022-11-01 09:53:37 +01:00
Joxit
ee1f173355 docs(FAQ): new section for how to fix CORS issue on s3 bucket (#193)
fixes #193
2022-10-21 21:41:12 +02:00
Joxit
71df95524d docs(FAQ): add section for docker hub mirror (#155)
fixes #155
2022-10-20 23:09:31 +02:00
91 changed files with 3791 additions and 686 deletions

View File

@@ -42,19 +42,17 @@ 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 -->
- OS: [e.g. Debian, Windows, Mac OS, Android 9...]
- Browser:
- Name: [e.g. Chrome, Firefox...]
- Version: [e.g. 22]
- Name: [e.g. Chrome, Firefox...]
- Version: [e.g. 114.0.5735.198, 102.11.0...]
- Docker registry UI:
- Version: [e.g. 1.4.0]
- Version: [e.g. 2.5.0, 2.6.0-84b31f2cfb...]
- 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...]
- Docker version: [e.g. 24.0.4...]
- Docker registry ui tag: [latest, main, 2.5.0, 2...]
- OS/Arch: [e.g. linux/amd64]
- Tools: [e.g. docker-compose, kubernets..]
- Tools: [e.g. docker-compose, kubernetes, electron..]
## Additional context

View File

@@ -1,10 +0,0 @@
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---

View File

@@ -11,8 +11,12 @@ jobs:
- uses: actions/checkout@v2
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: Build the interface
run: npm run build
env:
DEVELOPMENT_BUILD: ${{ github.sha }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx

20
.github/workflows/pull_request.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Run tests on PRs and branches
on:
pull_request:
push:
branches-ignore: [ main, master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: Build the interface
run: npm run build
env:
DEVELOPMENT_BUILD: ${{ github.event.pull_request.head.sha }}

View File

@@ -2,7 +2,7 @@ name: Release
on:
push:
tags: '*'
tags: ['*']
jobs:
build:
@@ -13,6 +13,8 @@ jobs:
fetch-depth: 0
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: Build the interface
run: npm run build
- name: Major tag

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ _site
*.orig
.serve/
demo/v2
.version.json

51
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,51 @@
# How to contribute to Docke Registry UI
I ([Jones Magloire](https://joxit.dev/)) created the Docker Registry UI from scratch, but I can't succeed without contributions from community members like you! Contributions come in many different shapes and sizes. In this file we provide guidance around two of the most common types of contributions: opening issues and opening pull requests.
Please read also the [Code Of Conduct](https://github.com/Joxit/docker-registry-ui/blob/main/CODE_OF_CONDUCT.md).
## Submitting Issues
### Did you find a bug?
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/joxit/docker-registry-ui/issues).
* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/joxit/docker-registry-ui/issues/new). Be sure to
* Use the [**Bug Report Template**](https://github.com/Joxit/docker-registry-ui/issues/new?assignees=&labels=&template=bug_report.md&title=)
* Include a **title and clear description**
* Write as much relevant information as possible
* Add your **full configuration** (Docker Registry UI **AND** Docker Server) or a **screenshots** demonstrating the expected behavior that is not occurring
## Submitting Pull Request
### Do you intend to add a new feature or change an existing one?
* Suggest your change in a new issue using the [**Feature Request Template**](https://github.com/Joxit/docker-registry-ui/issues/new?assignees=&labels=&template=feature_request.md&title=) and start writing code.
* Run the interface on your computer first with `npm start`.
### Did you write a patch that fixes a bug?
* Open a new [GitHub pull request](https://github.com/Joxit/docker-registry-ui/compare) with the patch.
* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
* Before submitting, please read the [Coding conventions](#coding-conventions) first.
### Did you fix whitespace, format code, or make a purely cosmetic patch?
Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Docker Registry UI will generally not be accepted.
## Coding conventions
* I use prettier with custom preset, use `npm format` before any PR
* I use [semver](https://semver.org/) for package versioning
* I use Github Actions for publishing docker images and releases
* I indent with two spaces
Thank you for your help! :heart:
[Joxit](https://joxit.dev/)

View File

@@ -15,6 +15,7 @@
- Murad [@muradheydarov](https://github.com/muradheydarov)
- Giacomo Mazzamuto [@gmazzamuto](https://github.com/gmazzamuto)
- Joe Bureau [@jabstone](https://github.com/jabstone)
- Artur Mostowski [@Vulwsztyn](https://github.com/Vulwsztyn)
## Because committers are not the only contributors
@@ -39,4 +40,16 @@
- Jason Tackaberry [@jtackaberry](https://github.com/jtackaberry)
- Maxime Loliée [@loliee](https://github.com/loliee)
- Enrico [@Enrico204](https://github.com/Enrico204)
- [@clyvari](https://github.com/clyvari)
- [@clyvari](https://github.com/clyvari)
- Laszlo Boros [@Semmu](https://github.com/Semmu)
- [@JKDingwall](https://github.com/JKDingwall)
- Martin Herren [@MartinHerren](https://github.com/MartinHerren)
- John Daktylidis [@Greek64](https://github.com/Greek64)
- Philipp Staiger [@lippl](https://github.com/lippl)
- [@mexaniksmirnov](https://github.com/mexaniksmirnov)
- [@HighOnMikey](https://github.com/HighOnMikey)
- [@logopk](https://github.com/logopk)
- Gustaf Järgren [@GoryMoon](https://github.com/GoryMoon)
- [@ArwynFr](https://github.com/ArwynFr)
- Nikita Matushkin [@yourh3ro](https://github.com/yourh3ro)
- Michael Grote [@quotengrote](https://github.com/quotengrote)

19
Developing.md Normal file
View File

@@ -0,0 +1,19 @@
# How to build Docker Registry UI
This file contains tips to help you take (and understand) your first steps in Docker Registry UI development.
## Clone and install the repository
```bash
git clone https://github.com/Joxit/docker-registry-ui.git
cd docker-registry-ui
npm install
```
## Run the local server
```bash
npm start
```
Open your browser <http://localhost:8000> you can configure your options by updating the `src/index.html` file.

View File

@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
FROM nginx:alpine
FROM nginx:alpine-slim
LABEL maintainer="Jones MAGLOIRE @Joxit"
@@ -27,4 +27,4 @@ COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh
COPY dist/ /usr/share/nginx/html/
COPY favicon.ico /usr/share/nginx/html/
RUN chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx
RUN chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx /var/log/nginx

219
README.md
View File

@@ -1,61 +1,53 @@
---
title: Docker Registry User Interface
---
# Docker Registry User Interface
# Docker Registry UI
![Stars](https://img.shields.io/github/stars/joxit/docker-registry-ui.svg?logo=github&maxAge=86400)
![Pulls](https://img.shields.io/docker/pulls/joxit/docker-registry-ui.svg?maxAge=86400)
[![Stars](https://img.shields.io/github/stars/joxit/docker-registry-ui.svg?logo=github&maxAge=86400)](https://github.com/Joxit/docker-registry-ui/stargazers)
[![Pulls](https://img.shields.io/docker/pulls/joxit/docker-registry-ui.svg?maxAge=86400)](https://hub.docker.com/r/joxit/docker-registry-ui)
[![Sponsor](https://joxit.dev/images/sponsor.svg)](https://github.com/sponsors/Joxit)
[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/joxit)](https://artifacthub.io/packages/search?repo=joxit)
[![Version](https://img.shields.io/github/release/joxit/docker-registry-ui?display_name=tag&sort=semver)](https://github.com/Joxit/docker-registry-ui/releases)
## Overview
This project aims to provide a simple and complete user interface for your private docker registry. You can customize the interface with various options. The major option is `SINGLE_REGISTRY` which allows you to disable the dynamic selection of docker registeries (same behavior as the old **static** tag).
This project aims to provide a simple and complete user interface for your private docker registry. You can customize the interface with various options. The major option is `SINGLE_REGISTRY` which allows you to disable the dynamic selection of docker registries (same behavior as the old **static** tag).
You may need the [migration guide from 1.x to 2.x](https://github.com/Joxit/docker-registry-ui/wiki/Migrating-from-1.x-to-2.x) or [the 1.x readme](https://github.com/Joxit/docker-registry-ui/blob/8fe3adf12540d1316cb57628ebe86a392a703d90/README.md)
You may need the [migration guide from 1.x to 2.x](https://github.com/Joxit/docker-registry-ui/wiki/Migrating-from-1.x-to-2.x) or [the 1.x readme](https://github.com/Joxit/docker-registry-ui/blob/8fe3adf12540d1316cb57628ebe86a392a703d90/README.md). The project support both [docker registry v2](https://github.com/distribution/distribution/releases/tag/v2.0.0) and [docker registry v3](https://github.com/distribution/distribution/releases/tag/v3.0.0).
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.
If you like my work and want to support it, don't hesitate to [sponsor me](https://github.com/sponsors/Joxit).
## Supported Docker tags
* `latest`: image with the latest release of Docker Registry UI based on `nginx:alpine`
* `latest-debian`: image with the latest release of Docker Registry UI based on `nginx:debian`
* `main`, `master`: image with the beta version of Docker Registry UI based on `nginx:alpine`
* `main-debian`, `master-debian`: image with the beta version of Docker Registry UI based on `nginx:debian`
* `2`: image with the latest release of Docker Registry UI v2 (includes latest minor and patch version)
* `2.x`: image with the latest release of Docker Registry UI v2.x (includes latest patch version)
* `2.x.y`: image with the specific release of Docker Registry UI v2.x.y
## [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/main/examples), [Helm Chart](https://helm.joxit.dev/)
![preview](https://raw.github.com/Joxit/docker-registry-ui/main/docker-registry-ui.gif "Preview of Docker Registry UI")
If you like my work and want to support it, don't hesitate to [sponsor me](https://github.com/sponsors/Joxit).
## Hidden Features
## Features
- Many ways to delete multiple images at once
- Select multiple tags to delete with checkboxes (see [#29](https://github.com/Joxit/docker-registry-ui/issues/29) and [#79](https://github.com/Joxit/docker-registry-ui/pull/79)). Since 1.2.0
- Select all tags of the page with `ALT + Click` on the indeterminate checkbox (see [#80](https://github.com/Joxit/docker-registry-ui/issues/80) and [#81](https://github.com/Joxit/docker-registry-ui/pull/81)). Since 1.2.1
- Select all contigous tags between two tags with `Shift + Click` on the first tag then `Shift + Click` on the second tag (see [#287](https://github.com/Joxit/docker-registry-ui/pull/287)). Since 2.4.0
- Show sha256 for specific tag (hover image tag).
- Sort the tag list with number compatibility (see [#45](https://github.com/Joxit/docker-registry-ui/pull/45) and [#46](https://github.com/Joxit/docker-registry-ui/pull/46)). Since 0.4.0
- Share your docker registry UI without installation or when you are deploying a UI with `SINGLE_REGISTRY=false`.
- Use the public demo and the query parameter `url` (e.g. `https://joxit.dev/docker-registry-ui/demo?url=https://registry.example.com`). If you need credentials on your private registry, you must set the `Access-Control-Allow-Origin` to `https://joxit.dev`.
- You can use a single interface with many registry, add them in the menu in the top right of the page.
- Filter images and tags with the search bar.
- You can select the search bar with the shortcut `CRTL + F` or `F3`. When the search bar is already focused, the shortcut will fallback to the default behavior (see [#213](https://github.com/Joxit/docker-registry-ui/issues/213)). Since 2.1.0
- 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)). Since 1.5.0
- Show the content of the dockerfile (see [#286](https://github.com/Joxit/docker-registry-ui/pull/286)). Since 2.4.0
- The UI will cache requests from your registry, such as blobs and some manifests (URL with `sha256:`).
- List all your repositories/images.
- List all tags for a image.
- Sort the tag list with number compatibility (see [#46](https://github.com/Joxit/docker-registry-ui/pull/46)).
- Use a secured docker registry.
- Display image size (see [#30](https://github.com/Joxit/docker-registry-ui/issues/30)).
- Multi arch supports, Alpine and Debian based images with supports for arm32v7 and arm64v8.
- Copy `docker pull` command to clipboard (see [#42](https://github.com/Joxit/docker-registry-ui/issues/42)).
- Show sha256 for specific tag (hover image tag).
- Display image creation date (see [#49](https://github.com/Joxit/docker-registry-ui/issues/49))
- Display image history (see [#58](https://github.com/Joxit/docker-registry-ui/pull/58) & [#61](https://github.com/Joxit/docker-registry-ui/pull/61)).
- Image aggregation (see [#56](https://github.com/Joxit/docker-registry-ui/issues/56)).
- Display image/tag count (see [#56 issue comment](https://github.com/Joxit/docker-registry-ui/issues/56#issuecomment-449246524)).
- Select multiple tags to delete (see [#29](https://github.com/Joxit/docker-registry-ui/issues/29)).
- Select all tags with ALT + Click to delete (see [#80](https://github.com/Joxit/docker-registry-ui/issues/80)).
- One interface for many registries (when `SINGLE_REGISTRY=false`).
- Share your docker registry with query parameter `url` (e.g. `https://joxit.dev/docker-registry-ui/demo?url=https://registry.example.com`) (when `SINGLE_REGISTRY=false`).
- Use the UI as reverse proxy (with `NGINX_PROXY_PASS_URL` environment variable) to your docker registry (This will avoid CORS).
- Add Title when using `REGISTRY_TITLE` (see [#28](https://github.com/Joxit/docker-registry-ui/issues/28)).
- Customise docker pull command on static registry UI (see [#71](https://github.com/Joxit/docker-registry-ui/issues/71)).
- Add custom header via environment variable and file via `NGINX_PROXY_HEADER_*` (see [#89](https://github.com/Joxit/docker-registry-ui/pull/89))
- Show/Hide content digest in taglist via `SHOW_CONTENT_DIGEST` (values are: [`true`, `false`], default: `false`) (see [#126](https://github.com/Joxit/docker-registry-ui/issues/126)).
- Limit the number of elements in the image list via `CATALOG_ELEMENTS_LIMIT` (see [#127](https://github.com/Joxit/docker-registry-ui/pull/127)).
- 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))
- Set a list of default registries with `DEFAULT_REGISTRIES` (see [#219](https://github.com/Joxit/docker-registry-ui/pull/219)).
- Desactivate add and remove regisitries with `READ_ONLY_REGISTRIES` (see [#219](https://github.com/Joxit/docker-registry-ui/pull/219)).
- Filter images and tags with a search bar. You can select the search bar with the shortcut `CRTL` + `F` or `F3`. When the search bar is already focused, the shortcut will fallback to the default behavior (see [#213](https://github.com/Joxit/docker-registry-ui/issues/213)).
- **Forward** custom header to your backend registry via environment variable and file via `NGINX_PROXY_PASS_HEADER_*` (see [#206](https://github.com/Joxit/docker-registry-ui/pull/206)).
- Run the container with user nginx instead of root via `--user nginx` and listend on custom port via `NGINX_LISTEN_PORT` (see [#224](https://github.com/Joxit/docker-registry-ui/issues/224)).
- Show number of tags per images on catalog page. This will produce + nb images requests, not recommended on large registries via `SHOW_CATALOG_NB_TAGS` (default: `false`) (see [#161](https://github.com/Joxit/docker-registry-ui/issues/161) and [#239](https://github.com/Joxit/docker-registry-ui/pull/239)).
- Expose custom labels in history page, custom labels will be processed like maintainer label via `HISTORY_CUSTOM_LABELS` (see [#160](https://github.com/Joxit/docker-registry-ui/issues/160) and [#240](https://github.com/Joxit/docker-registry-ui/pull/240)).
- Access to the official Helm Chart: https://helm.joxit.dev/
Checkout all options in [Available options](#available-options) section.
## FAQ
@@ -75,17 +67,24 @@ If you like my work and want to support it, don't hesitate to [sponsor me](https
- 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 OPTIONS (aka preflight requests) and DELETE fails with 401 status code (using Basic Auth) ?
- This is caused by a bug in docker registry, it returns 401 status requests on preflight requests, this breaks [W3C preflight-request specification](https://www.w3.org/TR/cors/#preflight-request). I suggest to have your UI on the same domain than your registry e.g. registry.example.com/ui/ **or** use `NGINX_PROXY_PASS_URL` **or** configure a nginx/apache/haproxy in front of your registry that returns 200 on each OPTIONS requests. (see [#104](https://github.com/Joxit/docker-registry-ui/issues/104), [#204](https://github.com/Joxit/docker-registry-ui/issues/204), [#207](https://github.com/Joxit/docker-registry-ui/issues/207), [#214](https://github.com/Joxit/docker-registry-ui/issues/214), [#266](https://github.com/Joxit/docker-registry-ui/issues/266)).
- Why OPTIONS (aka preflight requests) and DELETE fails with 401 status code (using Basic Auth) or why the UI says to check my `Access-Control-Allow-Origin` ?
- This is caused by a bug in docker registry, it returns 401 status requests on preflight requests, this breaks [W3C preflight-request specification](https://www.w3.org/TR/cors/#preflight-request). I contacted docker registry maintainers and this will never be fixed ([distribution/distribution#4458](https://github.com/distribution/distribution/issues/4458)). I suggest to have your UI on the same domain than your registry e.g. registry.example.com/ui/ **or** use `NGINX_PROXY_PASS_URL` **or** configure a nginx/apache/haproxy in front of your registry that returns 200 on each OPTIONS requests. (see [#104](https://github.com/Joxit/docker-registry-ui/issues/104), [#204](https://github.com/Joxit/docker-registry-ui/issues/204), [#207](https://github.com/Joxit/docker-registry-ui/issues/207), [#214](https://github.com/Joxit/docker-registry-ui/issues/214), [#266](https://github.com/Joxit/docker-registry-ui/issues/266), [#278](https://github.com/Joxit/docker-registry-ui/issues/278)).
- 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/main/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))
- 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.
- Can I run the container with an unprivileged user ?
- Yes you can run the container with the `nginx` user, (see [#224](https://github.com/Joxit/docker-registry-ui/issues/224)).
- Yes you can run the container with the `nginx` user with the option `--user nginx`, this will also update the listen port to `8080` (see [#224](https://github.com/Joxit/docker-registry-ui/issues/224) and [#234](https://github.com/Joxit/docker-registry-ui/pull/234)).
- Can I use the UI with a docker hub mirror and show `library/*` images ?
- Yes but it is at your own risk using two regstry servers, check the comment [#155](https://github.com/Joxit/docker-registry-ui/issues/155#issuecomment-1286052124).
- How to fix CORS issue on s3 bucket ?
- You should add a CORS Policy on your bucket, check the issue [#193](https://github.com/Joxit/docker-registry-ui/issues/193).
- Why my docker registry server is returning an error `pagination number invalid` ?
- Since docker registry server 2.8.2 there is default limit of 1000 images in catalog. If you need more images update the configuration `REGISTRY_CATALOG_MAXENTRIES` with your max value and check the issue [#306](https://github.com/Joxit/docker-registry-ui/issues/306).
- I'm using `NGINX_PROXY_PASS_URL`, my registry server has been recreated and the UI cannot connect with the message `[error] 176#176: *2 connect() failed (111: Connection refused) while connecting to upstream`, what can I do?
- Nginx get the IP of all addresses only once at runtime, since your container has been recreated, its IP changed too. To prevent this kind of issue, you may use the option `NGINX_RESOLVER` and set to `127.0.0.11`.
Need more informations ? Try my [examples](https://github.com/Joxit/docker-registry-ui/tree/main/examples) or open an issue.
@@ -93,41 +92,120 @@ Need more informations ? Try my [examples](https://github.com/Joxit/docker-regis
You can run the container with the unprivileged user `nginx`, see the discussion [#224](https://github.com/Joxit/docker-registry-ui/issues/224).
Some env options are available for use this interface for **only one server**.
Some env options are available for use this interface for **only one server** (when `SINGLE_REGISTRY=true`).
- `REGISTRY_URL`: The default url of your docker registry. You may need CORS configuration on your registry. This is usually the domain name or IP of your registry reachable by your computer (e.g `http://registry.example.com`). (default: derived from the hostname of your UI).
- `REGISTRY_TITLE`: Set a custom title for your user interface. (default: value derived from `REGISTRY_URL`).
- `PULL_URL`: Set a custom url when you copy the `docker pull` command. (default: value derived from `REGISTRY_URL`).
- `REGISTRY_URL`: The default url of your docker registry. You **may need CORS configuration** on your registry. This is usually the domain name or IP of your registry reachable by your computer (e.g `http://registry.example.com`). (default: derived from the hostname of your UI).
- `REGISTRY_TITLE`: Set a custom title for your user interface. (default: value derived from `REGISTRY_URL`) (see [#28](https://github.com/Joxit/docker-registry-ui/issues/28) and [#32](https://github.com/Joxit/docker-registry-ui/issues/32)). Since 0.3.4
- `PULL_URL`: Set a custom url when you copy the `docker pull` command (see [#71](https://github.com/Joxit/docker-registry-ui/issues/71)). (default: value derived from `REGISTRY_URL`). Since 1.1.0
- `DELETE_IMAGES`: Set if we can delete images from the UI. (default: `false`)
- `SHOW_CONTENT_DIGEST`: Show content digest in docker tag list. (default: `false`)
- `CATALOG_ELEMENTS_LIMIT`: Limit the number of elements in the catalog page. (default: `100000`).
- `SINGLE_REGISTRY`: Remove the menu that show the dialogs to add, remove and change the endpoint of your docker registry. (default: `false`).
- `NGINX_PROXY_PASS_URL`: Update the default Nginx configuration and set the **proxy_pass** to your backend docker registry (this avoid CORS configuration). This is usually the name of your registry container in the form `http://registry:5000`.
- `NGINX_PROXY_HEADER_*`: Update the default Nginx configuration and **set custom headers** for your backend docker registry. Only when `NGINX_PROXY_PASS_URL` is used.
- `NGINX_PROXY_PASS_HEADER_*`: Update the default Nginx configuration and **forward custom headers** to your backend docker registry. Only when `NGINX_PROXY_PASS_URL` is used.
- `NGINX_LISTEN_PORT`: Listen on a port other than 80. (default: `80` when the user is root, `8080` otherwise).
- `DEFAULT_REGISTRIES`: List of comma separated registry URLs (e.g `http://registry.example.com,http://registry:5000`), available only when `SINGLE_REGISTRY=false`. (default: ` `).
- `READ_ONLY_REGISTRIES`: Desactivate dialog for remove and add new registries, available only when `SINGLE_REGISTRY=false`. (default: `false`).
- `SHOW_CATALOG_NB_TAGS`: Show number of tags per images on catalog page. This will produce + nb images requests, not recommended on large registries. (default: `false`).
- `HISTORY_CUSTOM_LABELS`: Expose custom labels in history page, custom labels will be processed like maintainer label.
- `USE_CONTROL_CACHE_HEADER`: Use `Control-Cache` header and set to `no-store, no-cache`. This will avoid some issues on multi-arch images (see [#260](https://github.com/Joxit/docker-registry-ui/issues/260)). This option requires registry configuration: `Access-Control-Allow-Headers` with `Cache-Control`. (default: `false`).
- `SHOW_CONTENT_DIGEST`: Show/Hide content digest in docker tag list (see [#126](https://github.com/Joxit/docker-registry-ui/issues/126) and [#131](https://github.com/Joxit/docker-registry-ui/pull/131)). (default: `false`). Since 1.4.9
- `CATALOG_ELEMENTS_LIMIT`: Limit the number of elements in the catalog page (see [#39](https://github.com/Joxit/docker-registry-ui/issues/39), [#127](https://github.com/Joxit/docker-registry-ui/pull/127), [#132](https://github.com/Joxit/docker-registry-ui/pull/132)) and [#306](https://github.com/Joxit/docker-registry-ui/issues/306). (default: `1000`). Since 1.4.9
- `SINGLE_REGISTRY`: Remove the menu that show the dialogs to add, remove and change the endpoint of your docker registry. (default: `false`). Since 2.0.0
- `NGINX_PROXY_PASS_URL`: Update the default Nginx configuration and set the **proxy_pass** to your backend docker registry (this avoid CORS configuration). This is usually the name of your registry container in the form `http://registry:5000`. Since 2.0.0
- `NGINX_PROXY_HEADER_*`: Update the default Nginx configuration and **set custom headers** for your backend docker registry via environment variable and file (`/etc/nginx/.env`). Only when `NGINX_PROXY_PASS_URL` is used (see [#89](https://github.com/Joxit/docker-registry-ui/pull/89)). Since 1.2.3
- `NGINX_PROXY_PASS_HEADER_*`: Update the default Nginx configuration and **forward custom headers** to your backend docker registry via environment variable and file (`/etc/nginx/.env`). Only when `NGINX_PROXY_PASS_URL` is used (see [#206](https://github.com/Joxit/docker-registry-ui/issues/206)). Since 2.1.0
- `NGINX_LISTEN_PORT`: Listen on a port other than 80, you can also change the default user and set to nginx `--user nginx` (see [#224](https://github.com/Joxit/docker-registry-ui/issues/224) and [#234](https://github.com/Joxit/docker-registry-ui/pull/234)). (default: `80` when the user is root, `8080` otherwise). Since 2.2.0
- `NGINX_RESOLVER`: Add [`resolver`](http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive to the nginx configuration for dynamic dns resolving. The value when you are using a docker network is `127.0.0.11`, you can set a custom DNS server too with a valid time. This is not needed when you are using kubernetes. (see [#333](https://github.com/Joxit/docker-registry-ui/issues/333) and [#339](https://github.com/Joxit/docker-registry-ui/issues/339)). (default: ``). Since 2.5.5
- `DEFAULT_REGISTRIES`: List of comma separated registry URLs (e.g `http://registry.example.com,http://registry:5000`), available only when `SINGLE_REGISTRY=false` (see [#219](https://github.com/Joxit/docker-registry-ui/pull/219)). (default: ` `). Since 2.1.0
- `READ_ONLY_REGISTRIES`: Deactivate dialog for remove and add new registries, available only when `SINGLE_REGISTRY=false` (see [#219](https://github.com/Joxit/docker-registry-ui/pull/219)). (default: `false`). Since 2.1.0
- `SHOW_CATALOG_NB_TAGS`: Show number of tags per images on catalog page and hide images with 0 tags. This will produce + nb images requests, **not recommended on large registries** (see [#161](https://github.com/Joxit/docker-registry-ui/issues/161) and [#239](https://github.com/Joxit/docker-registry-ui/pull/239)). (default: `false`). Since 2.2.0
- `HISTORY_CUSTOM_LABELS`: Expose custom labels in history page, custom labels will be processed like maintainer label (see [#160](https://github.com/Joxit/docker-registry-ui/issues/160) and [#240](https://github.com/Joxit/docker-registry-ui/pull/240)). Since 2.2.0
- `USE_CONTROL_CACHE_HEADER`: Use `Control-Cache` header and set to `no-store, no-cache`. This will avoid some issues on multi-arch images (see [#260](https://github.com/Joxit/docker-registry-ui/issues/260) and [#265](https://github.com/Joxit/docker-registry-ui/pull/265)). This option requires registry configuration: `Access-Control-Allow-Headers` with `Cache-Control`. (default: `false`). Since 2.3.0
- `THEME`: Chose your default theme, could be `dark`, `light` or `auto` (see [#283](https://github.com/Joxit/docker-registry-ui/pull/283)). When auto is selected, you will have a switch to manually change from light to dark and vice-versa (see [#291](https://github.com/Joxit/docker-registry-ui/pull/291)). (default: `auto`). Since 2.4.0
- `THEME_*`: See table in [Theme options](#theme-options) section (see [#283](https://github.com/Joxit/docker-registry-ui/pull/283)). Since 2.4.0
- `TAGLIST_ORDER`: Set the default order for the taglist page, could be `num-asc;alpha-asc`, `num-desc;alpha-asc`, `num-asc;alpha-desc`, `num-desc;alpha-desc`, `alpha-asc;num-asc`, `alpha-asc;num-desc`, `alpha-desc;num-asc` or `alpha-desc;num-desc` (see [#307](https://github.com/Joxit/docker-registry-ui/pull/307)). (default: `alpha-asc;num-desc`). Since 2.5.0
- `CATALOG_DEFAULT_EXPANDED`: Expand by default all repositories in catalog (see [#302](https://github.com/Joxit/docker-registry-ui/issues/302)). (default: `false`). Since 2.5.0
- `CATALOG_MIN_BRANCHES`: Set the minimum repository/namespace to expand (e.g. `joxit/docker-registry-ui` `joxit/` is the repository/namespace). Branching can be disabled if min and max are set to 0. (see [#319](https://github.com/Joxit/docker-registry-ui/pull/319)). (default: `1`). Since 2.5.0
- `CATALOG_MAX_BRANCHES`: Set the maximum repository/namespace to expand (e.g. `joxit/docker-registry-ui` `joxit/` is the repository/namespace). Branching can be disabled if min and max are set to 0. (see [#319](https://github.com/Joxit/docker-registry-ui/pull/319)). (default: `1`). Since 2.5.0
- `TAGLIST_PAGE_SIZE`: Set the number of tags to display in one page. (default: `100`). Since 2.5.0
- `REGISTRY_SECURED`: By default, the UI will check on every requests if your registry is secured or not (you will see `401` responses in your console). Set to `true` if your registry uses Basic Authentication and divide by two the number of call to your registry. (default `false`). Since 2.5.0
- `SHOW_TAG_HISTORY`: Whether to show the tag history feature or not. Allows to simplify the user interface by hiding it form the tag list if set to `false`. (default: `true`).
There are some examples with [docker-compose](https://docs.docker.com/compose/) and docker-registry-ui as proxy [here](https://github.com/Joxit/docker-registry-ui/tree/main/examples/ui-as-proxy/) or docker-registry-ui as standalone [here](https://github.com/Joxit/docker-registry-ui/tree/main/examples/ui-as-standalone/).
- `DOCKER_REGISTRY_UI_TITLE`: Set a custom title displayed in the header bar. (default: `Docker Registry UI`).
- `ENABLE_VERSION_NOTIFICATION`: Display notification when a new version of Docker Registry UI is available. This is a weekly check. (default: `true`).
### Theme options
This featureswas added to version 2.4.0. See more about this in [#283](https://github.com/Joxit/docker-registry-ui/pull/283).
| Environment variable | light theme value | dark theme value |
| --- | --- | --- |
| `THEME_PRIMARY_TEXT` | `#25313b` | `#98a8bd` |
| `THEME_NEUTRAL_TEXT` | `#777777` | `#6d7fab` |
| `THEME_BACKGROUND` | `#ffffff` | `#22272e` |
| `THEME_HOVER_BACKGROUND` | `#eeeeee` | `#343a4b` |
| `THEME_ACCENT_TEXT` | `#5f7796` | `#5c88ff` |
| `THEME_HEADER_TEXT` | `#ffffff` | `#ffffff` |
| `THEME_HEADER_ACCENT_TEXT` | `#7b9ac2` | `#7ea1ff` |
| `THEME_HEADER_BACKGROUND` | `#25313b` | `#333a45` |
| `THEME_FOOTER_TEXT` | `#ffffff` | `#ffffff` |
| `THEME_FOOTER_NEUTRAL_TEXT` | `#adbacd` | `#98afcf` |
| `THEME_FOOTER_BACKGROUND` | `#344251` | `#344251` |
## Recommended Docker Registry Usage
Here is a simple usage of Docker Registry UI with Docker Registry Server using docker-compose. This example should work for most of your use case and your UI will be on the same domain as you registry.
```yml
version: '3.8'
services:
registry-ui:
image: joxit/docker-registry-ui:main
restart: always
ports:
- 80:80
environment:
- SINGLE_REGISTRY=true
- REGISTRY_TITLE=Docker Registry UI
- DELETE_IMAGES=true
- SHOW_CONTENT_DIGEST=true
- NGINX_PROXY_PASS_URL=http://registry-server:5000
- SHOW_CATALOG_NB_TAGS=true
- CATALOG_MIN_BRANCHES=1
- CATALOG_MAX_BRANCHES=1
- TAGLIST_PAGE_SIZE=100
- REGISTRY_SECURED=false
- CATALOG_ELEMENTS_LIMIT=1000
container_name: registry-ui
registry-server:
image: registry:2.8.2
restart: always
environment:
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin: '[http://registry-ui.example.com]'
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods: '[HEAD,GET,OPTIONS,DELETE]'
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Credentials: '[true]'
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: '[Authorization,Accept,Cache-Control]'
REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers: '[Docker-Content-Digest]'
REGISTRY_STORAGE_DELETE_ENABLED: 'true'
volumes:
- ./registry/data:/var/lib/registry
container_name: registry-server
```
## Using CORS
Your server should be configured to accept CORS.
:warning: Before posting issues about CORS, please read the and all created issues.
If your docker registry does not need credentials, you will need to send this HEADER:
:warning: If you **are using credentials** and your registry is on a different host than your UI, please read the [FAQ about OPTIONS](https://github.com/Joxit/docker-registry-ui#:~:text=Why%20OPTIONS%20(aka%20preflight%20requests)), all the linked issues and [distribution/distribution#4458](https://github.com/distribution/distribution/issues/4458) first. The best way for the UI to work is using `NGINX_PROXY_PASS_URL` or configure your own proxy (nginx, haproxy...) that will be on top of your **docker registry** (and not the UI!) to override OPTIONS requests.
If your docker registry **does not need credentials**, you will need to send this HEADER:
```yml
http:
headers:
Access-Control-Allow-Origin: ['*']
Access-Control-Allow-Headers: ['Accept', 'Cache-Control']
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS'] # Optional
```
If your docker registry need credentials, you will need to send these HEADERS (you must add the protocol `http`/`https` and the port when not default `80`/`443`):
```yml
http:
headers:
Access-Control-Allow-Origin: ['http://registry.example.com']
Access-Control-Allow-Origin: ['http://registry-ui.example.com']
Access-Control-Allow-Credentials: [true]
Access-Control-Allow-Headers: ['Authorization', 'Accept', 'Cache-Control']
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS'] # Optional
@@ -137,7 +215,7 @@ An alternative for CORS issues is a plugin on your browser, more info [here](htt
## Using delete
For deleting images, you need to activate the delete feature in your registry:
For deleting images, you need to activate the delete feature in the UI with `DELETE_IMAGES=true` and in your registry:
```yml
storage:
@@ -155,8 +233,6 @@ 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:
@@ -190,8 +266,9 @@ auth:
```
## 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.
check out the [Electron](examples/electron/README.md) standalone application (not updated).
## All examples

View File

@@ -27,4 +27,4 @@ COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh
COPY dist/ /usr/share/nginx/html/
COPY favicon.ico /usr/share/nginx/html/
RUN chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx
RUN chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx /var/log/nginx

View File

@@ -27,4 +27,4 @@ COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh
COPY dist/ /usr/share/nginx/html/
COPY favicon.ico /usr/share/nginx/html/
RUN chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx
RUN chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx /var/log/nginx

View File

@@ -1,16 +1,29 @@
#!/bin/sh
sed -i "s~\${DOCKER_REGISTRY_UI_TITLE}~${DOCKER_REGISTRY_UI_TITLE}~" index.html
sed -i "s~\${REGISTRY_URL}~${REGISTRY_URL}~" index.html
sed -i "s~\${REGISTRY_TITLE}~${REGISTRY_TITLE}~" index.html
sed -i "s~\${PULL_URL}~${PULL_URL}~" index.html
sed -i "s~\${SINGLE_REGISTRY}~${SINGLE_REGISTRY}~" index.html
sed -i "s~\${CATALOG_ELEMENTS_LIMIT}~${CATALOG_ELEMENTS_LIMIT}~" index.html
sed -i "s~\${SHOW_CONTENT_DIGEST}~${SHOW_CONTENT_DIGEST}~" index.html
sed -i "s~\${SHOW_TAG_HISTORY}~${SHOW_TAG_HISTORY}~" index.html
sed -i "s~\${DEFAULT_REGISTRIES}~${DEFAULT_REGISTRIES}~" index.html
sed -i "s~\${READ_ONLY_REGISTRIES}~${READ_ONLY_REGISTRIES}~" index.html
sed -i "s~\${SHOW_CATALOG_NB_TAGS}~${SHOW_CATALOG_NB_TAGS}~" index.html
sed -i "s~\${HISTORY_CUSTOM_LABELS}~${HISTORY_CUSTOM_LABELS}~" index.html
sed -i "s~\${USE_CONTROL_CACHE_HEADER}~${USE_CONTROL_CACHE_HEADER}~" index.html
sed -i "s~\${TAGLIST_ORDER}~${TAGLIST_ORDER}~" index.html
sed -i "s~\${CATALOG_DEFAULT_EXPANDED}~${CATALOG_DEFAULT_EXPANDED}~" index.html
sed -i "s~\${CATALOG_MIN_BRANCHES}~${CATALOG_MIN_BRANCHES}~" index.html
sed -i "s~\${CATALOG_MAX_BRANCHES}~${CATALOG_MAX_BRANCHES}~" index.html
sed -i "s~\${TAGLIST_PAGE_SIZE}~${TAGLIST_PAGE_SIZE}~" index.html
sed -i "s~\${REGISTRY_SECURED}~${REGISTRY_SECURED}~" index.html
sed -i "s~\${ENABLE_VERSION_NOTIFICATION}~${ENABLE_VERSION_NOTIFICATION}~" index.html
grep -o 'THEME[A-Z_]*' index.html | while read e; do
sed -i "s~\${$e}~$(printenv $e)~" index.html
done
if [ -z "${DELETE_IMAGES}" ] || [ "${DELETE_IMAGES}" = false ] ; then
sed -i "s/\${DELETE_IMAGES}/false/" index.html
@@ -55,7 +68,13 @@ if [ -n "${NGINX_PROXY_PASS_URL}" ] ; then
sed -i "s,\${NGINX_PROXY_PASS_URL},${NGINX_PROXY_PASS_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^\${NGINX_PROXY_PASS_HEADERS}^$(get_nginx_proxy_pass_headers)^" /etc/nginx/conf.d/default.conf
sed -i "s,#!,," /etc/nginx/conf.d/default.conf
sed -i "s,#! , ," /etc/nginx/conf.d/default.conf # The space is important here, to not interfer with #!r
if [ -n "${NGINX_RESOLVER}" ]; then
sed -i "s,\${NGINX_RESOLVER},${NGINX_RESOLVER}," /etc/nginx/conf.d/default.conf
sed -i "s,#r,," /etc/nginx/conf.d/default.conf
else
sed -i "s,#!r, ," /etc/nginx/conf.d/default.conf # The space is for cosmetic here
fi
fi
if [ "$(whoami)" != "root" ]; then
@@ -63,7 +82,7 @@ if [ "$(whoami)" != "root" ]; then
NGINX_LISTEN_PORT="8080"
fi
sed -i "/user nginx;/d" /etc/nginx/nginx.conf
sed -i "s,/var/run/nginx.pid,/tmp/nginx.pid," /etc/nginx/nginx.conf
sed -i "s,/run/nginx.pid,/tmp/nginx.pid," /etc/nginx/nginx.conf
fi
if [ "$NGINX_LISTEN_PORT" != "80" ]; then

View File

@@ -12,10 +12,10 @@ for i in arm64v8 arm32v7 master latest debian main; do
docker push 127.0.0.1:5000/joxit/docker-registry-ui:$i
done
for v in 1.1 1.2 1.3 1.4 1.5 2.0 ; do
for v in 1.1 1.2 1.3 1.4 1.5 2.0 2 2.0.0 2.1 2.1.0 2.2 2.2.0 2.3 2.3.0 2.4 2.4.0 2.5.0; do
for type in "-debian" ""; do
docker pull joxit/docker-registry-ui:$v$type
docker tag joxit/docker-registry-ui:$v$type 127.0.0.1:5000/joxit/docker-registry-ui:$v$type
docker push 127.0.0.1:5000/joxit/docker-registry-ui:$v$type
done
done
done

View File

@@ -27,4 +27,4 @@ COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh
COPY dist/ /usr/share/nginx/html/
COPY favicon.ico /usr/share/nginx/html/
RUN chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx
RUN chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx /var/log/nginx

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -16,55 +16,73 @@
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="../dist/docker-registry-ui.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"
/>
<meta
property="og:description"
content="This is the live demo for Docker Registry User Interface. Try it now! Sources : https://github.com/Joxit/docker-registry-ui"
/>
<link rel="canonical" href="https://joxit.dev/docker-registry-ui/demo/" />
<meta property="og:url" content="https://joxit.dev/docker-registry-ui/demo/" />
<meta property="og:site_name" content="Live Demo | Docker Registry User Interface" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@Joxit" />
<meta name="twitter:creator" content="@Jones Magloire" />
<title>Live Demo | Docker Registry User Interface</title>
</head>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../dist/docker-registry-ui.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" />
<meta property="og:description"
content="This is the live demo for Docker Registry User Interface. Try it now! Sources : https://github.com/Joxit/docker-registry-ui" />
<link rel="canonical" href="https://joxit.dev/docker-registry-ui/demo/" />
<meta property="og:url" content="https://joxit.dev/docker-registry-ui/demo/" />
<meta property="og:site_name" content="Live Demo | Docker Registry User Interface" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@Joxit" />
<meta name="twitter:creator" content="@Jones Magloire" />
<title>Live Demo | Docker Registry User Interface</title>
</head>
<body>
<docker-registry-ui registry-url="" name="Demo Docker Registry UI" pull-url="" show-content-digest="true"
is-image-remove-activated="true" catalog-elements-limit="1000" single-registry="false" default-registries="https://joxit.dev/docker-registry-demo">
<body>
<docker-registry-ui
registry-url=""
name="Demo Docker Registry UI"
pull-url=""
show-content-digest="true"
is-image-remove-activated="true"
catalog-elements-limit="1000"
single-registry="false"
default-registries="https://joxit.dev/docker-registry-demo"
show-catalog-nb-tags="true"
taglist-order=""
theme="auto"
/>
<script>
if (localStorage.getItem('registryServer')) {
localStorage.setItem('registryServer',
localStorage.getItem('registryServer').replace(
'https://raw.githubusercontent.com/Joxit/docker-registry-ui/master/demo',
'https://joxit.dev/docker-registry-demo'
)
)
localStorage.setItem(
'registryServer',
localStorage
.getItem('registryServer')
.replace(
'https://raw.githubusercontent.com/Joxit/docker-registry-ui/master/demo',
'https://joxit.dev/docker-registry-demo'
)
);
}
</script>
<script src="../dist/docker-registry-ui.js"></script>
<script>
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
<script>
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
(i[r] =
i[r] ||
function () {
(i[r].q = i[r].q || []).push(arguments);
}),
(i[r].l = 1 * new Date());
(a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga('create', 'G-T158HYBVZ2', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>
ga('create', 'G-T158HYBVZ2', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
dist/fonts/material-symbols-rounded.ttf vendored Normal file

Binary file not shown.

BIN
dist/fonts/material-symbols-rounded.woff vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg2" x="0px" y="0px" viewBox="-5.724 -43.601 730 600" fill="#777"> <path d="m595.942,422.343 c -7.543,0.119 -13.562,6.331 -13.443,13.875 0.119,7.544 6.332,13.562 13.875,13.443 7.495,-0.118 13.494,-6.254 13.445,-13.75 -0.085,-7.578 -6.297,-13.652 -13.875,-13.568 0,0 -10e-4,0 0,0 m 0,24.398 c -5.975,0.272 -11.039,-4.352 -11.311,-10.326 -0.271,-5.976 4.352,-11.04 10.327,-11.312 5.975,-0.271 11.039,4.352 11.311,10.327 0.01,0.19 0.013,0.382 0.011,0.573 0.204,5.723 -4.27,10.527 -9.992,10.731 -0.115,0.005 -0.23,0.007 -0.346,0.007"/> <path d="m599.081,436.342 v -0.185 c 1.512,-0.292 2.65,-1.544 2.8,-3.076 0.057,-1.175 -0.432,-2.311 -1.323,-3.077 -1.445,-0.765 -3.076,-1.106 -4.707,-0.984 -1.743,-0.024 -3.484,0.12 -5.2,0.431 v 13.538 h 3.077 v -5.446 h 1.477 c 1.754,0 2.554,0.646 2.83,2.154 0.184,1.143 0.536,2.252 1.047,3.292 h 3.415 c -0.53,-1.062 -0.873,-2.207 -1.016,-3.385 -0.138,-1.473 -1.088,-2.744 -2.462,-3.292 m -3.723,-0.985 h -1.508 v -3.908 c 0.583,-0.069 1.172,-0.069 1.754,0 1.97,0 2.893,0.831 2.893,2.062 0,1.231 -1.415,2 -3.076,2"/> <path d="M707.494,193.557c-1.938-1.539-20.029-15.199-58.181-15.199c-10.074,0.044-20.127,0.908-30.061,2.584 c-7.384-50.612-49.228-75.288-51.104-76.395l-10.245-5.908l-6.738,9.723c-8.438,13.061-14.598,27.459-18.214,42.582 c-6.831,28.891-2.677,56.027,11.999,79.226c-17.722,9.876-46.151,12.307-51.904,12.522H22.367 c-12.294,0.017-22.27,9.952-22.337,22.245c-0.549,41.234,6.437,82.222,20.614,120.946c16.214,42.521,40.336,73.842,71.719,93.01 c35.167,21.537,92.302,33.844,157.067,33.844c29.258,0.092,58.461-2.556,87.226-7.907c39.986-7.342,78.463-21.318,113.839-41.352 c29.149-16.88,55.383-38.354,77.688-63.596c37.29-42.213,59.505-89.226,76.026-131.007c2.215,0,4.431,0,6.584,0 c40.828,0,65.935-16.338,79.78-30.029c9.201-8.732,16.384-19.369,21.045-31.167l2.923-8.553L707.494,193.557z"/> <path d="M65.995,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.476-5.538 c-0.01,0-0.021,0-0.031,0H65.995c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C60.488,226.443,62.953,228.909,65.995,228.909L65.995,228.909"/> <path d="M152.913,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.059,0-5.538,2.479-5.538,5.538v56.181C147.392,226.448,149.866,228.909,152.913,228.909"/> <path d="M241.153,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C235.646,226.443,238.112,228.909,241.153,228.909L241.153,228.909"/> <path d="M328.348,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C322.841,226.443,325.307,228.909,328.348,228.909L328.348,228.909"/> <path d="M152.913,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.046,0-5.521,2.46-5.538,5.507v56.181C147.392,145.597,149.861,148.066,152.913,148.083"/> <path d="M241.153,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C235.646,145.591,238.107,148.066,241.153,148.083"/> <path d="M328.348,148.083h63.073c3.052-0.017,5.521-2.486,5.538-5.538V86.364c-0.017-3.047-2.491-5.507-5.538-5.507h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C322.841,145.591,325.302,148.066,328.348,148.083"/> <path d="M328.348,67.227h63.073c3.047,0,5.521-2.461,5.538-5.507V5.507C396.942,2.46,394.468,0,391.421,0h-63.073 c-3.042,0-5.507,2.465-5.507,5.507l0,0v56.212C322.841,64.761,325.307,67.227,328.348,67.227"/> <path d="M416.312,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.041,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C410.805,226.443,413.271,228.909,416.312,228.909"/> </svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 730 730" fill="var(--neutral-text)"> <path d="M603.672 530.973c-7.543.12-13.562 6.331-13.443 13.875.119 7.544 6.332 13.562 13.875 13.443 7.495-.118 13.494-6.254 13.445-13.75-.085-7.578-6.297-13.652-13.875-13.568 0 0-.001 0 0 0m0 24.398c-5.975.272-11.04-4.352-11.311-10.326-.271-5.976 4.352-11.04 10.327-11.312 5.975-.27 11.039 4.352 11.31 10.327.01.19.014.382.012.573.204 5.723-4.27 10.527-9.992 10.731-.115.005-.23.007-.346.007"/> <path d="M606.81 544.972v-.185a3.476 3.476 0 002.8-3.076 3.815 3.815 0 00-1.322-3.077 8.675 8.675 0 00-4.707-.984 27.066 27.066 0 00-5.2.431v13.538h3.077v-5.446h1.477c1.754 0 2.554.646 2.83 2.154a11.667 11.667 0 001.047 3.292h3.415a10.381 10.381 0 01-1.016-3.385 3.938 3.938 0 00-2.462-3.292m-3.723-.985h-1.508v-3.908a7.457 7.457 0 011.754 0c1.97 0 2.893.831 2.893 2.062 0 1.231-1.415 2-3.076 2m112.135-241.924c-1.938-1.539-20.03-15.199-58.181-15.199a185.562 185.562 0 00-30.061 2.584c-7.384-50.612-49.228-75.288-51.104-76.395l-10.245-5.908-6.738 9.723a137.318 137.318 0 00-18.214 42.582c-6.831 28.891-2.677 56.027 11.999 79.226-17.722 9.876-46.151 12.307-51.904 12.522H30.096c-12.293.017-22.27 9.952-22.336 22.245a338.69 338.69 0 0020.614 120.946c16.214 42.521 40.336 73.842 71.719 93.01 35.167 21.537 92.302 33.844 157.067 33.844a468.88 468.88 0 0087.226-7.907 364.649 364.649 0 00113.839-41.352 312.969 312.969 0 0077.688-63.596c37.29-42.213 59.505-89.226 76.026-131.007h6.584c40.828 0 65.935-16.338 79.78-30.029a87.08 87.08 0 0021.045-31.167l2.923-8.553z"/> <path d="M73.725 337.54h63.073a5.507 5.507 0 005.507-5.508V275.85a5.507 5.507 0 00-5.476-5.538H73.725a5.507 5.507 0 00-5.507 5.507v56.212a5.507 5.507 0 005.507 5.508m86.918.001h63.073a5.507 5.507 0 005.507-5.508V275.85a5.507 5.507 0 00-5.477-5.538h-63.104a5.537 5.537 0 00-5.538 5.538v56.181a5.54 5.54 0 005.539 5.508m88.24.001h63.073a5.507 5.507 0 005.507-5.508V275.85a5.507 5.507 0 00-5.477-5.538h-63.104a5.507 5.507 0 00-5.507 5.507v56.212a5.51 5.51 0 005.508 5.508m87.195.001h63.073a5.537 5.537 0 005.538-5.508V275.85a5.537 5.537 0 00-5.538-5.538h-63.073a5.507 5.507 0 00-5.507 5.507v56.212a5.508 5.508 0 005.507 5.508m-175.435-80.826h63.073a5.539 5.539 0 005.507-5.538v-56.18a5.507 5.507 0 00-5.507-5.508h-63.073a5.538 5.538 0 00-5.538 5.507v56.181a5.568 5.568 0 005.538 5.538m88.24 0h63.073a5.539 5.539 0 005.507-5.538v-56.18a5.507 5.507 0 00-5.507-5.508h-63.073a5.507 5.507 0 00-5.507 5.507v56.181a5.539 5.539 0 005.507 5.538m87.195 0h63.073a5.568 5.568 0 005.538-5.538v-56.18a5.537 5.537 0 00-5.538-5.508h-63.073a5.507 5.507 0 00-5.507 5.507v56.181a5.539 5.539 0 005.507 5.538m0-80.856h63.073a5.538 5.538 0 005.538-5.507v-56.213a5.537 5.537 0 00-5.538-5.507h-63.073a5.506 5.506 0 00-5.507 5.507v56.212a5.508 5.508 0 005.507 5.508m87.964 161.683h63.073a5.537 5.537 0 005.538-5.508V275.85a5.537 5.537 0 00-5.538-5.538h-63.073a5.508 5.508 0 00-5.507 5.507v56.212a5.508 5.508 0 005.507 5.508"/> </svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

1
dist/images/rocket.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 128 128"> <path fill="#ca2c31" d="M3.77 71.73l16.34-16.1 27.82-4.93-2.75 14.56L7.57 76.82l-2.43-1.05z"/> <path fill="#a02422" d="M22.94 59.76L5.2 75.88l13.05 6.36 19.81-10.11v-4.77l4.05-10.92zm41.98 28.39l-8.57 3.72-8.09 17.15s7.12 15.77 7.44 15.77c.32 0 4.37.32 4.37.32l14.4-16.1 3.64-27.5-13.19 6.64z"/> <path d="M56.5 100.84s4.77-.97 8.17-2.59c3.4-1.62 7.6-4.04 7.6-4.04l-1.54 13.43-15.05 17.13s-.59-.73-3.09-6.17c-1.99-4.34-2.68-5.89-2.68-5.89l6.59-11.87z" fill="#ca2c31"/> <path d="M31.58 80.66s-5.74-.48-12.03 7.47c-5.74 7.26-8.43 19.08-9.47 22.12s-3.53 3.66-2.7 5.05 4.42 1.31 8.85.76 8.23-1.94 8.23-1.94-.19.48-.83 1.52c-.23.37-1.03.9-.97 1.45.14 1.31 11.36 1.34 20.32-7.88 9.68-9.95 4.98-18.11 4.98-18.11L31.58 80.66z" fill="#f7d74d"/> <path d="M33.31 85.29s-6.19.33-11.31 8.28-7.5 17.16-7.01 17.78c.48.62 10.02-2.83 12.31-2.14 1.57.48.76 2.07 1.18 2.49.35.35 4.49.94 11.19-6.32 6.71-7.26 5.12-17.46 5.12-17.46l-11.48-2.63z" fill="#fbf0b4"/> <path d="M36.35 74.44s-3.11 2.77-4.22 4.36c-1.11 1.59-1.11 1.73-1.04 2.21.07.48 1.22 5.75 6.01 10.37 5.88 5.67 11.13 6.43 11.89 6.43.76 0 5.81-5.67 5.81-5.67l-18.45-17.7z" fill="#858585"/> <path d="M50.1 91.24s5.04 3.31 13.49.47c11.55-3.88 20.02-12.56 30.51-23.52 10.12-10.58 18.61-23.71 18.61-23.71l-5.95-19.93L50.1 91.24z" fill="#437687"/> <path d="M67.99 80.33l1.39-4.32 3.48.49s2.65 1.25 4.6 2.16c1.95.91 4.46 1.6 4.46 1.6l-4.95 4.18s-2.7-1.02-4.67-1.88c-2.22-.97-4.31-2.23-4.31-2.23z" fill="#3f545f"/> <path d="M84.32 16.14s-9.62 5.58-23.41 18.63c-12.43 11.76-21.64 22.4-23.87 31.45-1.86 7.58-.87 12.18 3.36 17.15 4.47 5.26 9.71 7.87 9.71 7.87s3.94.06 20.38-12.59C91 62.86 107.43 36.42 107.43 36.42L84.32 16.14z" fill="#8dafbf"/> <path d="M104.18 41.84s-8.37-3.57-14.34-11.9c-5.93-8.27-5.46-13.86-5.46-13.86s4.96-3.89 16.11-8.34c7.5-2.99 17.71-4.52 21.07-2.03s-2.3 14.98-2.3 14.98l-10.31 19.96-4.77 1.19z" fill="#d83f22"/> <path d="M68.17 80.4s-7.23-3.69-11.83-8.94c-8.7-9.91-10.5-20.79-10.5-20.79l4.37-5.13S51.3 57.1 60.63 67.09c6.08 6.51 12.43 9.49 12.43 9.49s-1.27 1.07-2.63 2.11c-.87.67-2.26 1.71-2.26 1.71z" fill="#6896a5"/> <path d="M112.71 44.48s4.34-5.23 8.45-17.02c5.74-16.44.74-21.42.74-21.42s-1.69 7.82-7.56 18.69c-4.71 8.71-10.41 17-10.41 17s3.14 1.41 4.84 1.9c2.14.62 3.94.85 3.94.85z" fill="#a02422"/> <path d="M39.81 69.66c1.3 1.24 3.27-.06 4.56-3.1 1.3-3.04 1.28-4.74.28-5.46-1.24-.9-3.32 1.07-4.23 2.82-1 1.94-1.59 4.8-.61 5.74zm45.14-49.53s-7.61 5.47-15.73 12.91c-7.45 6.83-12.39 12.17-13.07 13.41-.72 1.33-.73 3.21-.17 4.17s1.8 1.46 2.93.62c1.13-.85 9.18-9.75 16.45-16.11 6.65-5.82 11.78-9.51 11.78-9.51s2.08-3.68 1.74-4.52c-.34-.85-3.93-.97-3.93-.97z" fill="#b3e1ee"/> <path d="M84.95 20.13s5.62-4.31 11.74-7.34c5.69-2.82 11.35-5.17 12.37-3.13.97 1.94-5.37 4.58-10.95 8.14-5.58 3.56-10.95 7.81-10.95 7.81s-.82-1.5-1.35-2.89a23.7 23.7 0 01-.86-2.59z" fill="#ed6a65"/> <path d="M89.59 39.25c-5.57-5.13-13.32-3.75-17.14.81-3.92 4.7-3.63 11.88 1 16.2 4.21 3.92 12.04 4.81 16.76-.69 4.2-4.88 3.94-12.13-.62-16.32z" fill="#e1e1e1"/> <path d="M75.33 41.87c-3.31 3.25-3.13 9.69.81 12.63 3.44 2.57 8.32 2.44 11.38-.69 3.06-3.13 3.06-8.82.19-11.76-3.3-3.37-8.59-3.9-12.38-.18z" fill="#3f545f"/> <path d="M50 76.89s6.19-6.28 6.87-5.6c.68.68.59 4.49-2.37 8.73-2.97 4.24-9.5 11.79-14.67 16.88-5.1 5.01-12.29 10.74-12.97 10.64-.53-.08-2.68-1.15-3.54-2.19-.84-1.03 1.67-5.9 2.68-7.51 1.02-1.61 24-20.95 24-20.95z" fill="#a02524"/> <path d="M21.23 101.85c-.08 1.44 2.12 3.54 2.12 3.54L56.87 71.3s-1.57-1.77-6.19 1.1c-4.66 2.9-8.74 6.38-14.76 12.21-8.39 8.14-14.61 15.8-14.69 17.24z" fill="#ca2c31"/> </svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

23
dist/index.html vendored
View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -14,10 +14,12 @@
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 href="docker-registry-ui.css" 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><docker-registry-ui
docker-registry-ui-title="${DOCKER_REGISTRY_UI_TITLE}"
registry-url="${REGISTRY_URL}"
name="${REGISTRY_TITLE}"
pull-url="${PULL_URL}"
show-content-digest="${SHOW_CONTENT_DIGEST}"
show-tag-history="${SHOW_TAG_HISTORY}"
is-image-remove-activated="${DELETE_IMAGES}"
catalog-elements-limit="${CATALOG_ELEMENTS_LIMIT}"
single-registry="${SINGLE_REGISTRY}"
@@ -26,4 +28,23 @@
show-catalog-nb-tags="${SHOW_CATALOG_NB_TAGS}"
history-custom-labels="${HISTORY_CUSTOM_LABELS}"
use-control-cache-header="${USE_CONTROL_CACHE_HEADER}"
taglist-order="${TAGLIST_ORDER}"
catalog-default-expanded="${CATALOG_DEFAULT_EXPANDED}"
catalog-min-branches="${CATALOG_MIN_BRANCHES}"
catalog-max-branches="${CATALOG_MAX_BRANCHES}"
is-registry-secured="${REGISTRY_SECURED}"
theme="${THEME}"
theme-primary-text="${THEME_PRIMARY_TEXT}"
theme-neutral-text="${THEME_NEUTRAL_TEXT}"
theme-background="${THEME_BACKGROUND}"
theme-hover-background="${THEME_HOVER_BACKGROUND}"
theme-accent-text="${THEME_ACCENT_TEXT}"
theme-header-accent-text="${THEME_HEADER_ACCENT_TEXT}"
theme-header-text="${THEME_HEADER_TEXT}"
theme-header-background="${THEME_HEADER_BACKGROUND}"
theme-footer-text="${THEME_FOOTER_TEXT}"
theme-footer-neutral-text="${THEME_FOOTER_NEUTRAL_TEXT}"
theme-footer-background="${THEME_FOOTER_BACKGROUND}"
tags-per-page="${TAGLIST_PAGE_SIZE}"
enable-version-notification="${ENABLE_VERSION_NOTIFICATION}"
></docker-registry-ui><script src="docker-registry-ui.js"></script></body></html>

1
dist/version.json vendored Normal file
View File

@@ -0,0 +1 @@
{"version":"2.6.0","latest":"2.6.0"}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 3.6 MiB

View File

@@ -2,9 +2,12 @@
This example will override the original nginx conf with read only access to the registry. You will need to rewrite all the project configuration (replaces `proxy_pass` with your own value, in this example `http://registry:5000` is fine).
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.
There are two htpasswd files:
All users in `read-only.htpasswd` should be in `read-write.htpasswd`.
- `write.htpasswd` for write access
- `read.htpasswd` for read access
Read only user: login: `read` password: `regisrty`.
Read and write user: login: `write` password: `regisrty`.
All users in `write.htpasswd` should also be in `read.htpasswd` so that they can read and write.
Read only user: login: `read` password: `registry`.
Read and write user: login: `write` password: `registry`.

View File

@@ -17,11 +17,11 @@ services:
- SINGLE_REGISTRY=true
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
- ./write.htpasswd:/etc/nginx/auth/write.htpasswd:ro
- ./read.htpasswd:/etc/nginx/auth/read.htpasswd:ro
depends_on:
- registry
networks:
- registry-ui-net
networks:
registry-ui-net:
registry-ui-net:

View File

@@ -28,10 +28,10 @@ server {
}
# 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;
auth_basic_user_file /etc/nginx/auth/read.htpasswd;
# For requests that *aren't* a GET, HEAD or OPTIONS use the write file instead
limit_except GET HEAD OPTIONS {
auth_basic_user_file /etc/nginx/auth/write.htpasswd;
}
proxy_pass http://registry:5000;

View File

@@ -32,8 +32,23 @@ I will highlight required configuration for Basic Access Authentication Protocol
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
if ($request_method = "OPTIONS") {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods "OPTIONS, GET" always;
add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization" always;
add_header Access-Control-Allow-Credentials true always;
add_header Content-Type "text/plain charset=UTF-8";
add_header Content-Length 0;
return 204;
}
# By default, keycloak returns 400 instead of 401, we need to change that
if ($http_authorization = "") {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods "OPTIONS, GET" always;
add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization" always;
add_header Access-Control-Allow-Credentials true always;
add_header WWW-Authenticate 'Basic realm="Keycloak login"' always;
return 401;
}

View File

@@ -7,7 +7,6 @@ server {
set $registry "http://registry:5000";
set $ui "http://ui";
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
@@ -44,14 +43,35 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
if ($request_method = "OPTIONS") {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods "OPTIONS, GET" always;
add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization" always;
add_header Access-Control-Allow-Credentials true always;
add_header Content-Type "text/plain charset=UTF-8";
add_header Content-Length 0;
return 204;
}
if ($http_authorization = "") {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods "OPTIONS, GET" always;
add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization" always;
add_header Access-Control-Allow-Credentials true always;
add_header WWW-Authenticate 'Basic realm="Keycloak login"' always;
return 401;
}
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods "OPTIONS, GET" always;
add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization" always;
add_header Access-Control-Allow-Credentials true always;
proxy_pass $keycloak;
}
location /ui {
rewrite ^/ui/(.*) /$1 break;
proxy_pass $ui;
}

View File

@@ -12,7 +12,7 @@ bash run-swarm.sh
## Authentication
The registry is protected via __Basic authentication__ but feel free to use wathever you like.
The registry is protected via __Basic authentication__ but feel free to use whatever you like.
In this sample, credentials are: **admin / admin**.
To generate a new password for basic auth, run the command: `htpasswd -nb username password`.

View File

@@ -1,18 +1,23 @@
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;
#r resolver ${NGINX_RESOLVER}; # 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
# disable any limits to avoid HTTP 413 for large image uploads and 400 on large headers (eg: cookie)
client_max_body_size 0;
client_body_buffer_size 32k;
client_header_buffer_size 8k;
large_client_header_buffers 8 64k;
# 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;
# Fix push and pull of large images: see Issue #282 (https://github.com/Joxit/docker-registry-ui/issues/282)
proxy_request_buffering off;
proxy_ignore_headers "X-Accel-Buffering";
location / {
@@ -29,10 +34,12 @@ server {
#! proxy_http_version 1.1;
#! ${NGINX_PROXY_HEADERS}
#! ${NGINX_PROXY_PASS_HEADERS}
#! proxy_pass ${NGINX_PROXY_PASS_URL};
#r set $registry_server "${NGINX_PROXY_PASS_URL}";
#r proxy_pass $registry_server;
#!r proxy_pass ${NGINX_PROXY_PASS_URL};
#! }
#error_page 404 /404.html;
# error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
@@ -40,11 +47,4 @@ server {
location = /50x.html {
root /usr/share/nginx/html;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}

View File

@@ -1,6 +1,7 @@
{
"name": "docker-registry-ui",
"version": "2.3.2",
"version": "2.6.0",
"type": "module",
"scripts": {
"format": "npm run format-html && npm run format-js && npm run format-riot",
"format-html": "find src rollup rollup.config.js -name '*.html' -exec prettier --config .prettierrc -w --parser html {} \\;",
@@ -8,7 +9,8 @@
"format-riot": "find src rollup rollup.config.js -name '*.riot' -exec prettier --config .prettierrc -w --parser html {} \\;",
"start": "rollup -c -w --environment ROLLUP_SERVE:true",
"build": "rollup -c",
"build:electron": "npm run build && cd examples/electron && npm install && npm run dist"
"build:electron": "npm run build && cd examples/electron && npm install && npm run dist",
"test": "mocha"
},
"repository": {
"type": "git",
@@ -18,29 +20,28 @@
"license": "AGPL-3.0",
"description": "A web UI for private docker registry",
"devDependencies": {
"@babel/core": "^7.17.9",
"@babel/preset-env": "^7.16.0",
"@riotjs/compiler": "^6.1.3",
"@babel/core": "^7.20.7",
"@babel/preset-env": "^7.20.2",
"@riotjs/compiler": "^9.4.1",
"@riotjs/observable": "^4.1.1",
"@riotjs/route": "^8.0.1",
"@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-commonjs": "^21.1.0",
"@rollup/plugin-html": "^0.2.4",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.2.1",
"core-js": "^3.22.0",
"node-sass": "^7.0.1",
"prettier": "^2.6.2",
"riot": "^6.1.2",
"riot-mui": "github:joxit/riot-5-mui#4d68d7f",
"rollup": "^2.70.2",
"@riotjs/route": "^9.2.1",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-html": "^2.0.0",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-terser": "^0.4.4",
"core-js": "^3.27.1",
"mocha": "^11.2.0",
"prettier": "^3.4.2",
"riot": "^9.4.4",
"riot-mui": "github:joxit/riot-5-mui#a477acc",
"rollup": "^4.30.1",
"rollup-plugin-app-utils": "^1.0.6",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-riot": "^6.0.0",
"rollup-plugin-scss": "^3.0.0",
"rollup-plugin-serve": "^1.1.0",
"rollup-plugin-styles": "^4.0.0",
"rollup-plugin-terser": "^7.0.2"
"rollup-plugin-riot": "^9.0.2",
"rollup-plugin-scss": "^4.0.0",
"rollup-plugin-serve": "^3.0.0",
"sass": "^1.86.2"
}
}

View File

@@ -1,32 +1,49 @@
import riot from 'rollup-plugin-riot';
import nodeResolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import terser from '@rollup/plugin-terser';
import { emptyDirectories } from 'rollup-plugin-app-utils';
import { babel } from '@rollup/plugin-babel';
import scss from 'rollup-plugin-scss';
import serve from 'rollup-plugin-serve';
import html from '@rollup/plugin-html';
import htmlUseref from './rollup/html-useref';
import htmlUseref from './rollup/html-useref.js';
import json from '@rollup/plugin-json';
import copy from 'rollup-plugin-copy';
import copyTransform from './rollup/copy-transform';
import license from './rollup/license';
import checkOutput from './rollup/check-output';
import copyTransform from './rollup/copy-transform.js';
import license from './rollup/license.js';
import checkOutput from './rollup/check-output.js';
import importSVG from './rollup/import-svg.js';
import fs from 'fs';
const version = JSON.parse(fs.readFileSync('./package.json', 'utf-8')).version;
const useServe = process.env.ROLLUP_SERVE === 'true';
const output = useServe ? '.serve' : 'dist';
const getVersion = (version) => {
const parts = version.split('.').map((e) => parseInt(e));
if (useServe || process.env.DEVELOPMENT_BUILD) {
parts[1]++;
parts[2] = 0;
return parts.join('.') + (useServe ? '-dev' : `-${process.env.DEVELOPMENT_BUILD.slice(0, 10)}`);
}
return version;
};
fs.writeFileSync('.version.json', JSON.stringify({ version: getVersion(version), latest: version }));
const plugins = [
riot(),
json(),
importSVG(),
nodeResolve(),
commonjs(),
scss({ output: `./${output}/docker-registry-ui.css`, outputStyle: 'compressed' }),
scss({ fileName: `docker-registry-ui.css`, outputStyle: 'compressed' }),
babel({ babelHelpers: 'bundled', presets: [['@babel/env', { useBuiltIns: 'usage', corejs: { version: '2' } }]] }),
copy({
targets: [
{ src: 'src/fonts', dest: `${output}` },
{ src: '.version.json', dest: `${output}`, rename: 'version.json' },
{ src: 'src/images/*', dest: `${output}/images`, transform: copyTransform },
],
}),

28
rollup/import-svg.js Normal file
View File

@@ -0,0 +1,28 @@
import { extname } from 'path';
const injectNode = (svg) => `
export default function() {
return (new DOMParser().parseFromString(${svg}, 'image/svg+xml'));
};
`;
/**
* @param options
* @param options.include
* @param options.exclude
* @param options.stringify - if true returns String, otherwise returns DOM Node
*/
export default function () {
return {
name: 'import-svg',
transform: (code, id) => {
if (extname(id) !== '.svg') return null;
const content = JSON.stringify(code);
return {
code: injectNode(content),
map: { mappings: '' },
};
},
};
}

View File

@@ -1,5 +1,5 @@
export default `/*
* Copyright (C) 2016-2021 Jones Magloire @Joxit
* Copyright (C) 2016-2023 Jones Magloire @Joxit
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -20,8 +20,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
class="content"
if="{!props.filterResults || state.nImages > 0 || matchSearch(props.filterResults, state.image)}"
>
<material-card class="list highlight" expanded="{state.expanded}" onclick="{ onClick }">
<material-waves center="true" color="#ddd"></material-waves>
<material-card
class="list highlight"
expanded="{state.expanded}"
onclick="{ onClick }"
if="{ !props.showCatalogNbTags || state.nbTags !== 0 }"
>
<a if="{ state.image }" href="{ router.taglist(state.image) }">
<material-waves center="true" color="#ddd"></material-waves>
</a>
<material-waves if="{ state.images }" center="true" color="#ddd"></material-waves>
<span>
<i class="material-icons">send</i>
{ state.image || state.repo }
@@ -42,8 +50,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
on-notify="{ props.onnNotify }"
on-authentication="{ props.onAuthentication }"
show-catalog-nb-tags="{ props.showCatalogNbTags }"
is-registry-secured="{ props.isRegistrySecured }"
class="animated {!state.expanded && !props.filterResults ? 'hide' : ''} {state.expanding ? 'expanding' : ''}"
each="{item in state.images}"
z-index="{ props.zIndex - 1 }"
item="{ item }"
></catalog-element>
</div>
@@ -62,11 +72,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
state.images = props.item.images;
state.repo = props.item.repo;
state.nImages = props.item.images.length;
state.expanded = props.catalogDefaultExpanded;
}
if (props.showCatalogNbTags && state.image) {
this.getNbTags(props, state);
}
},
onMounted(props, state) {
const materialCard = this.$('material-card');
if (materialCard) {
materialCard.style['z-index'] = props.zIndex;
}
},
onBeforeUpdate(props, state) {
if (props.filterResults && state.images) {
state.nImages = state.images.filter((image) => matchSearch(props.filterResults, image)).length;
@@ -76,9 +93,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
},
onClick() {
const state = this.state;
if (!state.repo) {
router.taglist(state.image);
} else {
if (state.repo) {
this.update({
expanded: !this.state.expanded,
expanding: true,
@@ -94,8 +109,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
const self = this;
const oReq = new Http({
onAuthentication: props.onAuthentication,
withCredentials: props.isRegistrySecured,
});
oReq.addEventListener('load', function () {
oReq.addEventListener('loadend', function () {
if (this.status === 200) {
const nbTags = (JSON.parse(this.responseText).tags || []).length;
self.update({ nbTags });
@@ -112,6 +128,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
oReq.send();
},
matchSearch,
router,
};
</script>
<!-- End of tag -->

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -34,10 +34,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
on-notify="{ props.onNotify }"
on-authentication="{ props.onAuthentication }"
show-catalog-nb-tags="{ props.showCatalogNbTags }"
catalog-default-expanded="{ props.catalogDefaultExpanded || state.nRepositories === 1 }"
z-index="{ props.catalogMaxBranches - props.catalogMinBranches + 2 }"
is-registry-secured="{ props.isRegistrySecured }"
></catalog-element>
<script>
import CatalogElement from './catalog-element.riot';
import { Http } from '../../scripts/http';
import { getBranching } from '../../scripts/repositories';
import { getRegistryServers } from '../../scripts/utils';
export default {
@@ -55,6 +59,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
onBeforeMount(props) {
this.state.registryName = props.registryName;
this.state.catalogElementsLimit = props.catalogElementsLimit;
try {
this.state.branching = getBranching(props.catalogMinBranches, props.catalogMaxBranches);
} catch (e) {
props.onNotify(e);
}
},
onMounted(props, state) {
this.display(props, state);
@@ -68,32 +77,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
}
state.registryUrl = props.registryUrl;
let repositories = [];
let nImages = 0;
const self = this;
const catalogUrl = `${props.registryUrl}/v2/_catalog?n=${state.catalogElementsLimit}`;
const oReq = new Http({
onAuthentication: this.props.onAuthentication,
withCredentials: props.isRegistrySecured,
});
oReq.addEventListener('load', function () {
if (this.status === 200) {
repositories = JSON.parse(this.responseText).repositories || [];
repositories.sort();
repositories = repositories.reduce(function (acc, e) {
const slash = e.indexOf('/');
if (slash > 0) {
const repoName = e.substring(0, slash) + '/';
if (acc.length === 0 || acc[acc.length - 1].repo != repoName) {
acc.push({
repo: repoName,
images: [],
});
}
acc[acc.length - 1].images.push(e);
return acc;
}
acc.push(e);
return acc;
}, []);
nImages = repositories.length;
if (typeof state.branching === 'function') {
repositories = state.branching(repositories);
}
} else if (this.status === 404) {
self.props.onNotify('Server not found', true);
self.props.onNotify({ code: 'CATALOG_NOT_FOUND', url: catalogUrl }, true);
} else if (this.status === 400) {
let response;
try {
response = JSON.parse(this.responseText);
} catch (e) {}
if (!response || !Array.isArray(response.errors)) {
return self.props.onNotify(this.responseText, true);
}
self.props.onNotify({ ...response, url: catalogUrl }, true);
} else {
self.props.onNotify(this.responseText);
}
@@ -105,13 +114,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
self.update({
repositories,
nRepositories: repositories.length,
nImages: repositories.reduce((acc, e) => acc + ((e.images && e.images.length) || 1), 0),
nImages,
loadend: true,
});
});
oReq.open('GET', `${props.registryUrl}/v2/_catalog?n=${state.catalogElementsLimit}`);
oReq.open('GET', catalogUrl);
oReq.send();
},
};
</script>
<style>
catalog {
display: block;
margin: auto;
}
catalog > material-card {
width: 100%;
}
</style>
</catalog>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -16,16 +16,35 @@
-->
<add-registry-url>
<material-popup opened="{ props.opened }" onClick="{ props.onClose }">
<div slot="title">Add your Server ?</div>
<div slot="content">
<material-input onkeyup="{ onKeyUp }" placeholder="Server URL"></material-input>
<div class="material-popup-title">Add your Server ?</div>
<div class="material-popup-content">
<material-input
onkeyup="{ onKeyUp }"
label="Server URL"
text-color="var(--primary-text)"
label-color="var(--neutral-text)"
color="var(--accent-text)"
valid="{ registryUrlValidator }"
></material-input>
<span>Write your URL without /v2</span>
</div>
<div slot="action">
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ add }">
<div class="material-popup-action">
<material-button
class="dialog-button"
waves-color="var(--hover-background)"
onClick="{ add }"
color="inherit"
text-color="var(--primary-text)"
>
Add
</material-button>
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ props.onClose }">
<material-button
class="dialog-button"
waves-color="var(--hover-background)"
onClick="{ props.onClose }"
color="inherit"
text-color="var(--primary-text)"
>
Cancel
</material-button>
</div>
@@ -55,6 +74,9 @@
this.props.onClose();
setTimeout(() => router.updateUrlQueryParam(url), 100);
},
registryUrlValidator(input) {
return /^https?:\/\//.test(input) && !/\/v2\/?$/.test(input);
},
};
</script>
</add-registry-url>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -15,18 +15,30 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<change-registry-url>
<material-popup opened="{ props.opened }" onClick="{ props.onClick }">
<div slot="title">Change your Server ?</div>
<div slot="content">
<material-popup opened="{ props.opened }" onClick="{ props.onClose }">
<div class="material-popup-title">Change your Server ?</div>
<div class="material-popup-content">
<select>
<option each="{ url in getRegistryServers() }" value="{ url }">{ url }</option>
</select>
</div>
<div slot="action">
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ change }">
<div class="material-popup-action">
<material-button
class="dialog-button"
waves-color="var(--hover-background)"
onClick="{ change }"
color="inherit"
text-color="var(--primary-text)"
>
Change
</material-button>
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ props.onClose }">
<material-button
class="dialog-button"
waves-color="var(--hover-background)"
onClick="{ props.onClose }"
color="inherit"
text-color="var(--primary-text)"
>
Cancel
</material-button>
</div>
@@ -62,13 +74,18 @@
background: 0 0;
border: none;
font-weight: 400;
font-size: 1em;
line-height: 24px;
height: 24px;
border-bottom: 1px solid #2f6975;
border-bottom: 1px solid var(--accent-text);
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
margin: 1.5em 0;
color: var(--primary-text);
}
:host select option {
background-color: var(--background);
}
</style>
</change-registry-url>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -16,17 +16,40 @@
-->
<confirm-delete-image>
<material-popup opened="{ props.opened }" onClick="{ props.onClick }">
<div slot="title">These images will be deleted</div>
<div slot="content">
<div class="material-popup-title">
These images will be deleted
<material-button
color="inherit"
text-color="var(--accent-text)"
target="_blank"
waves-color="var(--hover-background)"
href="https://joxit.dev/docker-registry-ui/#:~:text=Why%20when%20I%20delete%20one%20tag,%20all%20tags%20with%20the%20same%20SHA%20are%20deleted%20"
icon
><i class="material-icons">help</i>
</material-button>
</div>
<div class="material-popup-content">
<ul>
<li each="{ image in displayImagesToDelete(props.toDelete, props.tags) }">{ image.name }:{ image.tag }</li>
</ul>
</div>
<div slot="action">
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ deleteImages }">
<div class="material-popup-action">
<material-button
class="dialog-button"
waves-color="var(--hover-background)"
onClick="{ deleteImages }"
color="inherit"
text-color="var(--primary-text)"
>
Delete
</material-button>
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ props.onClick }">
<material-button
class="dialog-button"
waves-color="var(--hover-background)"
onClick="{ props.onClick }"
color="inherit"
text-color="var(--primary-text)"
>
Cancel
</material-button>
</div>
@@ -46,10 +69,11 @@
},
deleteImages() {
this.props.toDelete.forEach((image) => this.getContentDigestThenDelete(image, this.props));
this.props.onImageDeleted();
},
getContentDigestThenDelete({ name, tag }, opts) {
const { registryUrl, onNotify, onAuthentication } = opts;
const oReq = new Http({ onAuthentication });
const { registryUrl, onNotify, onAuthentication, isRegistrySecured } = opts;
const oReq = new Http({ onAuthentication, withCredentials: isRegistrySecured });
const self = this;
oReq.addEventListener('loadend', function () {
if (this.status === 200 || this.status === 202) {
@@ -74,8 +98,8 @@
oReq.send();
},
deleteImage({ name, tag, contentDigest }, opts) {
const { registryUrl, ignoreError, onNotify, onAuthentication, onClick } = opts;
const oReq = new Http({ onAuthentication });
const { registryUrl, ignoreError, onNotify, onAuthentication, onClick, isRegistrySecured } = opts;
const oReq = new Http({ onAuthentication, withCredentials: isRegistrySecured });
oReq.addEventListener('loadend', function () {
if (this.status === 200 || this.status === 202) {
router.taglist(name);
@@ -97,9 +121,13 @@
'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json'
);
oReq.addEventListener('error', function () {
const credMsg = this.withCredentials
? ' When you use credentials on a different hostname, the registry server may fail preflight requests. Check FAQ and issue #104.'
: '';
onNotify({
message:
"An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: ['DELETE'].",
"An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: ['DELETE']." +
credMsg,
isError: true,
});
});

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -35,15 +35,23 @@
on-notify="{ props.onNotify }"
on-server-change="{ props.onServerChange }"
></remove-registry-url>
<div class="container">
<material-button onClick="{ onClick }" waves-center="true" rounded="true" waves-opacity="0.6" waves-duration="600">
<div class="material-dropdown-wrapper">
<material-button
onClick="{ onClick }"
waves-center="true"
waves-opacity="0.6"
waves-duration="600"
color="var(--header-background)"
text-color="var(--header-text)"
icon
>
<i class="material-icons">more_vert</i>
</material-button>
<material-dropdown-list
<material-dropdown
items="{ dropdownItems.filter(item => item.ro || !props.readOnlyRegistries) }"
onSelect="{ onDropdownSelect }"
on-click="{ onDropdownSelect }"
opened="{ state.isDropdownOpened }"
/>
></material-dropdown>
</div>
<div class="overlay" onclick="{ onClick }" if="{ state.isDropdownOpened }"></div>
<script>
@@ -74,9 +82,9 @@
ro: false,
},
],
onDropdownSelect(key, item) {
onDropdownSelect(event) {
this.update({
[item.name]: true,
[event.target.item]: true,
isDropdownOpened: false,
});
},
@@ -96,14 +104,25 @@
};
</script>
<style>
:host > .container {
position: absolute;
top: 0px;
right: 16px;
color: #000;
:host > .material-dropdown-wrapper {
color: var(--primary-text);
list-style-type: disc;
margin-block-start: 0.7em;
}
material-dropdown .material-dropdown-container,
material-dropdown .material-dropdown-container .material-dropdown-item {
background-color: var(--background);
color: var(--primary-text);
}
material-dropdown .material-dropdown-container .material-dropdown-item:hover {
background-color: rgba(0, 0, 0, 0.12);
}
:host .material-dropdown-wrapper material-dropdown .material-dropdown-container {
right: 0;
top: 2em;
}
:host .overlay {
position: fixed;
@@ -114,33 +133,15 @@
z-index: 10;
}
:host material-button {
background: rgba(255, 255, 255, 0);
:host material-button button {
float: right;
z-index: 2;
}
:host material-button .content i.material-icons {
color: #fff;
font-size: 24px;
}
:host material-dropdown-list {
display: inline-block;
position: relative;
}
:host material-dropdown-list ul.dropdown-content {
min-width: 156px;
padding: 8px 0;
margin: 0;
}
:host material-dropdown-list ul.dropdown-content li span {
font-size: 1rem;
line-height: 1.2em;
}
:host material-popup * {
line-height: 1em;
}

View File

@@ -0,0 +1,68 @@
<!--
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<dockerfile>
<material-popup opened="{ props.opened }" onClick="{ props.onClose }">
<div class="material-popup-title">Dockerfile</div>
<div class="material-popup-content">
<template each="{ (_, idx) in props.elements }">
<template
each="{ element in props.elements[props.elements.length - 1 - idx].filter(e => e.key === 'created_by') }"
>
<div class="instruction">
<span class="keyword">{ element.value }</span>
<span>{ ' ' + element.content }</span>
</div>
</template>
</template>
</div>
<div class="material-popup-action">
<material-button
class="dialog-button"
waves-color="rgba(158,158,158,.4)"
onClick="{ props.onClose }"
color="inherit"
text-color="var(--primary-text)"
>
Close
</material-button>
</div>
</material-popup>
<style>
:host material-popup .popup material-button {
margin-right: 1em;
}
:host material-popup .popup > .content {
max-width: 75em;
width: 80%;
}
:host .material-popup-content {
background-color: var(--hover-background);
}
@media screen and (max-width: 800px) {
:host material-popup .popup > .content {
width: 100%;
}
}
:host .instruction {
font-family: 'Roboto Mono', monospace !important;
margin: 0.75em 0;
}
:host .instruction .keyword {
color: var(--accent-text);
}
</style>
</dockerfile>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -16,17 +16,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<remove-registry-url>
<material-popup opened="{ props.opened }" onClick="{ props.onClose }">
<div slot="title">Remove your Registry Server ?</div>
<div slot="content">
<div class="material-popup-title">Remove your Registry Server ?</div>
<div class="material-popup-content">
<ul class="list">
<li each="{ url in getRegistryServers() }">
<span>
<material-button
onClick="{ remove }"
onClick="{ remove(url) }"
url="{ url }"
rounded="true"
waves-color="rgba(158,158,158,.4)"
text-color="var(--neutral-text)"
color="inherit"
waves-color="var(--hover-background)"
waves-center="true"
icon
>
<i class="material-icons">delete</i>
</material-button>
@@ -35,8 +37,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</li>
</ul>
</div>
<div slot="action">
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ props.onClose }">
<div class="material-popup-action">
<material-button
class="dialog-button"
waves-color="rgba(158,158,158,.4)"
onClick="{ props.onClose }"
color="inherit"
text-color="var(--primary-text)"
>
Close
</material-button>
</div>
@@ -44,10 +52,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<script>
import { getRegistryServers, removeRegistryServers } from '../../scripts/utils';
export default {
remove(event) {
const url = event.currentTarget.attributes.url && event.currentTarget.attributes.url.value;
removeRegistryServers(url);
setTimeout(() => this.update(), 100);
remove(url) {
return (event) => {
removeRegistryServers(url);
setTimeout(() => this.update(), 100);
};
},
getRegistryServers,
};
@@ -56,9 +65,5 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:host material-popup .popup material-button {
margin-right: 1em;
}
:host material-popup .popup material-button .content i.material-icons {
color: #777;
}
</style>
</remove-registry-url>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -17,19 +17,43 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<docker-registry-ui>
<header>
<material-navbar>
<div class="logo">Docker Registry UI</div>
<search-bar on-search="{ onSearch }"></search-bar>
<dialogs-menu
if="{props.singleRegistry !== 'true'}"
on-notify="{ notifySnackbar }"
on-server-change="{ onServerChange }"
default-registries="{ props.defaultRegistries }"
read-only-registries="{ truthy(props.readOnlyRegistries) }"
></dialogs-menu>
<span class="logo">
<span><a href="{ router.home() }">{ props.dockerRegistryUiTitle.trim() || "Docker Registry UI" }</a></span>
<version-notification
if="{ falsy(props.enableVersionNotification) }"
version="{ latest }"
on-notify="{ notifySnackbar }"
></version-notification>
</span>
<div class="menu">
<search-bar on-search="{ onSearch }"></search-bar>
<dialogs-menu
if="{props.singleRegistry !== 'true'}"
on-notify="{ notifySnackbar }"
on-server-change="{ onServerChange }"
default-registries="{ props.defaultRegistries }"
read-only-registries="{ truthy(props.readOnlyRegistries) }"
></dialogs-menu>
</div>
</material-navbar>
</header>
<main>
<router base="#!">
<error-page
if="{ state.pageError && !Array.isArray(state.pageError.errors) }"
code="{ state.pageError.code }"
status="{ state.pageError.status }"
message="{ state.pageError.message }"
url="{ state.pageError.url }"
></error-page>
<error-page
if="{ state.pageError && Array.isArray(state.pageError.errors) }"
each="{ error in (state.pageError && state.pageError.errors) }"
code="{ error.code }"
detail="{ error.detail }"
message="{ error.message }"
url="{ state.pageError.url }"
></error-page>
<router>
<route path="{baseRoute}">
<catalog
registry-url="{ state.registryUrl }"
@@ -39,7 +63,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
filter-results="{ state.filter }"
on-authentication="{ onAuthentication }"
show-catalog-nb-tags="{ truthy(props.showCatalogNbTags) }"
/>
catalog-default-expanded="{ truthy(props.catalogDefaultExpanded) }"
catalog-min-branches="{ props.catalogMinBranches }"
catalog-max-branches="{ props.catalogMaxBranches }"
is-registry-secured="{ truthy(props.isRegistrySecured) }"
></catalog>
</route>
<route path="{baseRoute}taglist/(.*)">
<tag-list
@@ -48,11 +76,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
pull-url="{ state.pullUrl }"
image="{ router.getTagListImage() }"
show-content-digest="{ truthy(props.showContentDigest) }"
show-tag-history="{ falsy(props.showTagHistory) }"
is-image-remove-activated="{ truthy(props.isImageRemoveActivated) }"
on-notify="{ notifySnackbar }"
filter-results="{ state.filter }"
on-authentication="{ onAuthentication }"
use-control-cache-header="{ truthy(props.useControlCacheHeader) }"
taglist-order="{ props.taglistOrder }"
tags-per-page="{ props.tagsPerPage }"
is-registry-secured="{ truthy(props.isRegistrySecured) }"
></tag-list>
</route>
<route path="{baseRoute}taghistory/(.*)">
@@ -67,6 +99,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
on-authentication="{ onAuthentication }"
history-custom-labels="{ stringToArray(props.historyCustomLabels) }"
use-control-cache-header="{ truthy(props.useControlCacheHeader) }"
is-registry-secured="{ truthy(props.isRegistrySecured) }"
></tag-history>
</route>
</router>
@@ -81,28 +114,46 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<material-snackbar message="{ state.snackbarMessage }" is-error="{ state.snackbarIsError }"></material-snackbar>
</main>
<footer>
<material-footer>
<a slot="logo" href="https://joxit.github.io/docker-registry-ui/">Docker Registry UI { version }</a>
<ul slot="link-list">
<material-footer mini="true">
<a class="material-footer-logo" href="https://joxit.github.io/docker-registry-ui/">
Docker Registry UI { version }
</a>
<ul>
<li>
<a href="https://github.com/Joxit/docker-registry-ui">Contribute on GitHub</a>
</li>
<li>
<a href="https://github.com/Joxit/docker-registry-ui/blob/main/LICENSE">License AGPL-3.0</a>
</li>
<li if="{ props.theme === 'auto' || props.theme === '' }">
<material-switch
on-change="{ onThemeChange }"
checked="{ state.themeSwitch }"
track-selected-color="var(--accent-text)"
outline-selected-color="var(--accent-text)"
>
<i slot="thumb-icon" class="material-icons" style="color: white; font-size: 0.75em">wb_sunny</i>
<i slot="thumb-selected-icon" class="material-icons" style="color: #79747e; font-size: 0.75em">
brightness_2
</i>
</material-switch>
</li>
</ul>
</material-footer>
</footer>
<script>
import { version } from '../../package.json';
import { version, latest } from '../../.version.json';
import { Router, Route } from '@riotjs/route';
import Catalog from './catalog/catalog.riot';
import TagList from './tag-list/tag-list.riot';
import TagHistory from './tag-history/tag-history.riot';
import DialogsMenu from './dialogs/dialogs-menu.riot';
import SearchBar from './search-bar.riot';
import { stripHttps, getRegistryServers, setRegistryServers, truthy, stringToArray } from '../scripts/utils';
import ErrorPage from './error-page.riot';
import VersionNotification from './version-notification.riot';
import { stripHttps, getRegistryServers, setRegistryServers, truthy, falsy, stringToArray } from '../scripts/utils';
import router from '../scripts/router';
import { loadTheme } from '../scripts/theme';
export default {
components: {
@@ -113,6 +164,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
SearchBar,
Router,
Route,
ErrorPage,
VersionNotification,
},
onUpdated(props, state) {
state.snackbarIsError = false;
@@ -126,6 +179,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
setRegistryServers(props.defaultRegistries);
}
window.onselectstart = (e) => {
if (e.target && e.target.className && typeof e.target.className.indexOf === 'function') {
return !['checkbox', 'checkmark', 'remove-tag'].find((elt) => e.target.className.indexOf(elt) >= 0);
}
};
// props.singleRegistry === 'true' means old static version
const registryUrl =
props.registryUrl ||
@@ -133,9 +192,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
window.location.origin + window.location.pathname.replace(/\/+$/, '');
this.state.registryUrl = registryUrl.replace(/\/$/, '').replace(/index(\.html?)?$/, '');
this.state.name = props.name || stripHttps(props.registryUrl);
this.state.catalogElementsLimit = props.catalogElementsLimit || 100000;
this.state.catalogElementsLimit = props.catalogElementsLimit || 1000;
this.state.pullUrl = this.pullUrl(this.state.registryUrl, props.pullUrl);
this.state.useControlCacheHeader = props.useControlCacheHeader;
const theme = loadTheme(props, this.root.parentNode.style);
this.state.themeSwitch = theme === 'dark';
},
onServerChange(registryUrl) {
this.update({
@@ -157,6 +218,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
this.notifySnackbar(`Failed to log in: ${e.message}`, true);
}
});
req.withCredentials = true;
req.open('GET', `${realm}?service=${service}&scope=${scope}`);
req.send();
} else {
@@ -178,6 +240,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
snackbarMessage: message,
snackbarIsError: isError || false,
});
} else if (message && (message.code || message.errors)) {
this.update({
pageError: message,
});
setTimeout(() => delete this.state['pageError'], 1000);
} else if (message && message.message) {
this.update({
snackbarMessage: message.message,
@@ -190,11 +257,67 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
filter: value,
});
},
onThemeChange(e) {
const theme = e.target.checked ? 'dark' : 'light';
loadTheme({ ...this.props, theme }, this.root.parentNode.style);
this.update({ themeSwitch: e.target.checked });
},
baseRoute: '([^#]*?)/(\\?[^#]*?)?(#!)?(/?)',
router,
version,
latest,
truthy,
falsy,
stringToArray,
};
</script>
<style>
:host {
display: flex;
flex-direction: column;
min-height: 100vh;
}
:host main {
flex-grow: 1;
margin: 0.5em 0;
}
material-navbar {
height: 64px;
color: var(--header-text);
background-color: var(--header-background);
}
material-navbar .menu {
display: flex;
}
material-navbar .nav-wrapper .menu {
flex-shrink: 1;
}
material-navbar .nav-wrapper .logo {
display: flex;
align-items: center;
flex-direction: row;
}
material-navbar .nav-wrapper .logo a {
text-decoration: none;
}
material-navbar .nav-wrapper .logo a:hover {
text-decoration: underline;
}
material-footer {
color: var(--footer-neutral-text);
background-color: var(--footer-background);
}
material-footer .material-footer-logo {
color: var(--footer-text);
}
material-switch i {
user-select: none;
}
</style>
</docker-registry-ui>

View File

@@ -0,0 +1,123 @@
<error-page>
<div class="content">
<h1 if="{ getStatusCode() }">{ getStatusCode() }</h1>
<h2>{ props.code }</h2>
<template if="{ props.code === 'CATALOG_NOT_FOUND' }">
<p>We received a 404 status code from your registry.</p>
<p>The contact point was <a href="{ props.url }">{ props.url }</a></p>
<p>
This may be caused by a misconfiguration of Docker Registry UI. Check the
<a href="https://joxit.dev/docker-registry-ui/#faq">FAQ</a> and
<a href="https://joxit.dev/docker-registry-ui/#available-options">Available options</a>
</p>
</template>
<template if="{ props.code === 'MIXED_CONTENT' }">
<p>
<span>Mixed Content</span>: The page at `<a href="{ window.location.origin }">{ window.location.origin }</a>`
was loaded over HTTPS, but requested an insecure server endpoint `<a href="{ new URL(props.url).origin }"
>{ new URL(props.url).origin }</a
>`.
</p>
<p>This request <span>may</span> has been blocked; the content must be served over HTTPS.</p>
<p>
You may unset the option `<span>REGISTRY_URL</span>` and set the registry server container URL in
`<span>NGINX_PROXY_PASS_URL</span>`. It's usually the name of your container, and it should be on the same
network as the UI.
</p>
<p>You can check the issue <a href="https://github.com/Joxit/docker-registry-ui/issues/277">#277</a>.</p>
</template>
<template if="{ props.code === 'INCORRECT_URL' }">
<p>`{ props.url }` does not seems to be a correct URL, should starts with http:// or https://.</p>
</template>
<template if="{ props.code === 'PAGINATION_NUMBER_INVALID' }">
<p>
A default limit of <code>1000</code> images in catalog has been added in docker registry server
<a href="https://github.com/distribution/distribution/releases/tag/v2.8.2">v2.8.2</a> (May 11, 2023) and we
cannot exceed this value without configuration.
</p>
<p>
The new default value for the UI is <code>1000</code> since
<a href="https://github.com/Joxit/docker-registry-ui/milestone/6">2.5.0</a> and was <code>100000</code> from
<a href="https://github.com/Joxit/docker-registry-ui/issues/39">0.3.6</a>.
</p>
<h3>Possible fixes</h3>
<p>
You can update the environment variable
<code>REGISTRY_CATALOG_MAXENTRIES={ props.detail?.n || 100000 }</code> of your
<span>docker registry server</span> or you can update your
<code>/etc/docker/registry/config.yml</code> configuration of your <span>docker registry server</span> and add
those lines:
</p>
<pre>
<span class="keyword">catalog</span>:
<span class="keyword">maxentries</span>: <span>{props.detail?.n || 100000}</span>
</pre>
<p>
If you don't need that many images, you can reduce the number of elements fetch by the
<span>docker registry UI</span> with the environment variable <code>CATALOG_ELEMENTS_LIMIT=1000</code>.
</p>
<p>
More about this issue:
<a href="https://github.com/Joxit/docker-registry-ui/issues/306">Joxit/docker-registry-ui#306</a>.
</p>
</template>
<template if="{ props.code === 'CATALOG_BRANCHING_CONFIGURATION' }">
<p>Wrong configuration for the branching feature: { props.message }</p>
<p>Configuration environment variables are : CATALOG_MIN_BRANCHES and CATALOG_MAX_BRANCHES</p>
</template>
</div>
<script>
export default {
getStatusCode() {
const { props } = this;
switch (props.code) {
case 'CATALOG_NOT_FOUND':
return '404';
case 'PAGINATION_NUMBER_INVALID':
return '400';
}
},
URL: window.URL,
};
</script>
<style>
:host {
display: flex;
flex-direction: row;
margin: 20px 3em;
}
:host .content {
margin: auto;
text-align: center;
}
:host .content a {
color: var(--accent-text);
}
:host .content a:visited {
color: var(--accent-text);
}
:host .content p span {
color: var(--accent-text);
font-weight: 700;
}
:host .content h2 {
font-weight: 700;
}
:host pre,
code {
background-color: var(--hover-background);
font-family: 'Roboto Mono', monospace !important;
text-align: left;
border-radius: 5px;
}
code {
padding: 0 0.5em;
}
:host pre {
padding: 0.5em;
}
:host pre .keyword {
color: var(--accent-text);
}
</style>
</error-page>

View File

@@ -1,5 +1,10 @@
<search-bar>
<material-input placeholder="Search in page"></material-input>
<material-input
label="Search in page"
text-color="var(--header-text)"
label-color="var(--header-accent-text)"
color="var(--header-accent-text)"
></material-input>
<script>
import { router } from '@riotjs/route';
@@ -39,10 +44,8 @@
</script>
<style>
:host material-input {
position: absolute;
top: 0em;
right: 64px;
max-width: 20%;
line-height: initial;
min-width: 5em;
}
@media screen and (max-width: 400px) {
@@ -50,9 +53,5 @@
display: none;
}
}
:host material-input input {
color: #fff;
}
</style>
</search-bar>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -16,8 +16,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<tag-history-element class="{ state.key }">
<div class="headline">
<i class="material-icons">{ state.icon }</i>
<p>{ state.name }</p>
<i if="{ state.key !== 'docker_version' }" class="material-icons">{ state.icon }</i>
<span if="{ state.key === 'docker_version' }" id="docker_verion"></span>
<span>{ state.name }</span>
</div>
<div class="content">
<div class="value" if="{ state.value }">{ state.value }</div>
@@ -25,6 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</div>
<script>
import { getHistoryIcon } from '../../scripts/utils';
import dockerVersionIcon from '../../images/docker-logo.svg';
export default {
onBeforeStart(props, state) {
state.key = props.entry.key;
@@ -42,6 +44,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
onBeforeUpdate(props, state) {
this.onBeforeStart(props, state);
},
onMounted(props, state) {
const dockerVersion = this.$('#docker_verion');
if (dockerVersion) {
dockerVersion.innerHTML = dockerVersionIcon().firstElementChild.outerHTML;
}
},
cleanName(name) {
if (name === 'id') {
return name;
@@ -70,10 +78,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
padding: 0px;
}
:host.docker_version .headline .material-icons {
background-size: 24px auto;
background-image: url('images/docker-logo.svg');
background-repeat: no-repeat;
:host.docker_version .headline .material-icons,
:host.docker_version .headline #docker_verion {
height: 24px;
}
:host {
@@ -93,12 +100,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
overflow-x: initial;
}
:host .headline p {
:host .headline {
height: 24px;
display: flex;
flex-direction: row;
align-items: center;
font-weight: bold;
line-height: 20px;
position: relative;
display: inline;
top: -4px;
}
:host .headline * {
margin-right: 6px;
}
:host.id div.value {

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -15,26 +15,55 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<tag-history>
<material-card ref="tag-history-tag" class="tag-history header">
<material-card>
<div class="material-card-title-action">
<material-button waves-center="true" rounded="true" waves-color="#ddd" onClick="{ toTaglist }">
<material-button
text-color="var(--neutral-text)"
color="inherit"
waves-color="var(--hover-background)"
waves-center="true"
rounded="true"
href="{ toTaglist() }"
icon
>
<i class="material-icons">arrow_back</i>
</material-button>
<h2>History of { props.image }:{ props.tag } <i class="material-icons">history</i></h2>
<material-button
text-color="var(--accent-text)"
color="inherit"
waves-color="var(--hover-background)"
waves-center="true"
rounded="true"
outlined
onClick="{ showDockerfile }"
>
Dockerfile
</material-button>
</div>
</material-card>
<div if="{ !state.loadend }" class="spinner-wrapper">
<material-spinner></material-spinner>
</div>
<dockerfile
opened="{ state.showDockerfile }"
on-close="{ onDockerfileClose }"
elements="{ state.elements }"
></dockerfile>
<material-tabs
if="{ state.archs && state.loadend }"
color="var(--background)"
text-color="var(--primary-text)"
text-selected-color="var(--accent-text)"
line-color="var(--background)"
line-selected-color="var(--accent-text)"
useLine="{ true }"
tabs="{ state.archs }"
onTabChanged="{ onTabChanged }"
></material-tabs>
<material-card each="{ element in state.elements }" class="tag-history-element">
<material-card each="{ element in state.elements }">
<tag-history-element
each="{ entry in element }"
if="{ entry.value && entry.value.length > 0}"
@@ -44,11 +73,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<script>
import { DockerImage } from '../../scripts/docker-image';
import { bytesToSize } from '../../scripts/utils';
import Dockerfile from '../dialogs/dockerfile.riot';
import router from '../../scripts/router';
import TagHistoryElement from './tag-history-element.riot';
export default {
components: {
TagHistoryElement,
Dockerfile,
},
onBeforeMount(props, state) {
state.elements = [];
@@ -58,6 +89,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
onNotify: props.onNotify,
onAuthentication: props.onAuthentication,
useControlCacheHeader: props.useControlCacheHeader,
isRegistrySecured: props.isRegistrySecured,
});
state.image.fillInfo();
},
@@ -92,10 +124,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
for (var attribute in elt) {
if (elt.hasOwnProperty(attribute) && attribute != 'empty_layer') {
const value = elt[attribute];
const guiElement = {
'key': attribute,
'value': modifySpecificAttributeTypes(attribute, value),
};
const guiElement = modifySpecificAttributeTypes(attribute, value);
guiElements.push(guiElement);
}
}
@@ -128,7 +157,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
});
},
toTaglist() {
router.taglist(this.props.image);
return router.taglist(this.props.image);
},
showDockerfile() {
this.update({ showDockerfile: true });
},
onDockerfileClose() {
this.update({ showDockerfile: false });
},
};
const eltIdx = function (e) {
@@ -158,27 +193,47 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
return eltIdx(e1.key) - eltIdx(e2.key);
};
const modifySpecificAttributeTypes = function (attribute, value) {
switch (attribute) {
const parseCreatedBy = (value) => {
if (value.startsWith('COPY')) {
return {
value: 'COPY',
content: value.replace(/^COPY /, ''),
};
}
let cmd = value.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+) (.*)/);
return {
value: (cmd && cmd[1]) || 'RUN',
content: (cmd && cmd[2]) || value.replace(/^\/bin\/sh *-c *(#\(nop\))?/, ''),
};
};
const modifySpecificAttributeTypes = function (key, value) {
switch (key) {
case 'created':
return new Date(value).toLocaleString();
return { key, value: new Date(value).toLocaleString() };
case 'created_by':
const cmd = value.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+)/);
return (cmd && cmd[1]) || 'RUN';
const cmd = value.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+) (.*)/);
return {
key,
...parseCreatedBy(value),
};
case 'size':
return bytesToSize(value);
return { key, value: bytesToSize(value) };
case 'Entrypoint':
case 'Cmd':
return (value || []).join(' ');
return { key, value: (value || []).join(' ') };
case 'Labels':
return Object.keys(value || {}).map(function (elt) {
return value[elt] ? elt + '=' + value[elt] : '';
});
return {
key,
value: Object.keys(value || {}).map(function (elt) {
return value[elt] ? elt + '=' + value[elt] : '';
}),
};
case 'Volumes':
case 'ExposedPorts':
return Object.keys(value);
return { key, value: Object.keys(value) };
}
return value || '';
return { key, value: value || '' };
};
const getConfig = function (blobs, { historyCustomLabels }) {
@@ -198,8 +253,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
'author',
'id',
'ExposedPorts',
'name',
'appVersion',
'kubeVersion',
'keywords',
'home',
'sources'
].reduce(function (acc, e) {
const value = blobs[e] || blobs.config[e];
const value = blobs[e] || (blobs.config && blobs.config[e]);
if (value && e === 'architecture' && blobs.variant) {
acc[e] = value + blobs.variant;
} else if (value) {
@@ -225,4 +286,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
return res;
};
</script>
<style>
h2 {
flex-grow: 1;
display: flex;
flex-direction: row;
align-items: center;
}
h2 .material-icons {
margin-left: .25em;
}
</style>
</tag-history>

View File

@@ -0,0 +1,54 @@
<architectures>
<div class="architecture" each="{ architecture in state.architectures }">{ architecture }</div>
<script>
import { platformToString } from '../../scripts/docker-image';
export default {
onMounted(props, state) {
this.onLoad(props, state);
},
onUpdated(props, state) {
if (props.image !== state.image) {
this.onLoad(props, state);
}
},
onLoad(props, state) {
if (props.image.manifests) {
return this.onList(props.image.manifests, props, state);
} else if (props.image.blobs) {
return this.onBlobs(props.image.blobs, props, state);
}
props.image.on('blobs', (blobs) => this.onBlobs(blobs, props, state));
props.image.on('list', (list) => this.onList(list, props, state));
},
onBlobs(blobs, props, state) {
const architectures = [platformToString(blobs)];
if (!props.image.manifests) {
this.update({ architectures, image: props.image });
}
},
onList(list, props, state) {
const architectures = list.map(({ platform }) => platformToString(platform));
this.update({ architectures, image: props.image });
},
};
</script>
<style>
:host architectures {
display: flex;
flex-direction: column;
}
architectures .architecture {
background-color: var(--hover-background);
padding: 2px 4px;
border-radius: 12px;
margin: 4px 0;
text-align: center;
}
architectures .architecture:first-child {
margin-top: 0;
}
architectures .architecture:last-child {
margin-bottom: 0;
}
</style>
</architectures>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -18,11 +18,13 @@
<div class="copy-to-clipboard">
<input style="display: none; width: 1px; height: 1px" value="{ getDockerCmd(props) }" />
<material-button
text-color="var(--neutral-text)"
color="inherit"
waves-color="var(--hover-background)"
waves-center="true"
rounded="true"
waves-color="#ddd"
onClick="{ copy }"
title="Copy pull command."
icon
>
<i class="material-icons">content_copy</i>
</material-button>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -20,13 +20,21 @@
import { dateFormat } from '../../scripts/utils';
export default {
onMounted(props) {
props.image.one('creation-date', (date) => {
this.update({
date: date,
localDate: date && date.toLocaleString(),
this.loadCreationDate(props);
},
onUpdated(props) {
this.loadCreationDate(props);
},
loadCreationDate(props) {
if (!props.image.creationDate && !props.image.ociImage) {
props.image.one('creation-date', (date) => {
this.update({
date: date,
localDate: date && date.toLocaleString(),
});
});
});
props.image.trigger('get-date');
props.image.trigger('get-date');
}
},
getDate(image) {
return !image.ociImage ? `${dateFormat(image.creationDate)} ago` : 'Not Available';

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -15,19 +15,57 @@ 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/>.
-->
<pagination>
<div class="conatianer">
<div class="container">
<div class="pagination-centered">
<material-button
aria-label="page-{ p.page }"
color="{ p.current ? 'var(--accent-text)' : 'rgba(0, 0, 0, 0 )' }"
text-color="{ p.current ? 'var(--accent-text)' : 'var(--primary-text)' }"
waves-color="rgba(158,158,158,.4)"
each="{ (p, idx) in props.pages}"
class="{ p.current ? 'current' : ''} { p['space-left'] ? 'space-left' : '' } { p['space-right'] ? 'space-right' : ''}"
onClick="{() => props.onPageUpdate(idx)}"
onClick="{(e) => props.onPageUpdate(idx)}"
outlined
>
<i if="{ p.icon }" class="material-icons">{ p.icon }</i>
<div if="{ !p.icon }">{ p.page }</div>
<template if="{ !p.icon }">{ p.page }</template>
</material-button>
</div>
</div>
<script></script>
<style>
:host .container {
display: flex;
}
:host .container .pagination-centered {
margin: auto;
}
:host material-button > :first-child {
padding: 0;
min-width: 40px;
min-height: 44px;
}
:host material-button > :first-child .content {
display: flex;
align-content: center;
flex-direction: column;
font-size: 16px;
line-height: 42px;
}
:host material-button.current > :first-child.space-left {
margin-left: 85px;
}
:host material-button.current > :first-child.space-right {
margin-right: 85px;
}
:host material-button .content i.material-icons {
height: unset;
}
</style>
</pagination>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -16,13 +16,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<remove-image>
<material-button
text-color="var(--neutral-text)"
color="inherit"
waves-color="var(--hover-background)"
waves-center="true"
rounded="true"
waves-color="#ddd"
title="This will delete the image."
if="{ !props.multiDelete }"
disabled="{ !state.contentDigest }"
onClick="{ deleteImage }"
icon
>
<i class="material-icons">delete</i>
</material-button>
@@ -56,9 +58,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
deleteImage() {
this.props.handleCheckboxChange(ACTION_DELETE_IMAGE, this.props.image);
},
handleCheckboxChange(checked) {
const action = checked ? ACTION_CHECK_TO_DELETE : ACTION_UNCHECK_TO_DELETE;
this.props.handleCheckboxChange(action, this.props.image);
handleCheckboxChange(event) {
const action = event.target.checked ? ACTION_CHECK_TO_DELETE : ACTION_UNCHECK_TO_DELETE;
this.props.handleCheckboxChange(action, this.props.image, event.shiftKey);
},
};
</script>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -17,11 +17,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<tag-history-button>
<material-button
title="{ buttonTittle() }"
text-color="var(--neutral-text)"
color="inherit"
waves-color="var(--hover-background)"
waves-center="true"
rounded="true"
waves-color="#ddd"
onClick="{ routeToHistory }"
href="{ routeToHistory() }"
disabled="{ props.image.ociImage }"
icon
>
<i class="material-icons">history</i>
</material-button>
@@ -40,7 +42,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
},
routeToHistory() {
if (!this.props.image.ociImage) {
router.history(this.props.image.name, this.props.image.tag);
return router.history(this.props.image.name, this.props.image.tag);
}
},
};

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -17,7 +17,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<tag-list>
<material-card class="header">
<div class="material-card-title-action">
<material-button waves-center="true" rounded="true" waves-color="#ddd" onClick="{ router.home }">
<material-button
text-color="var(--neutral-text)"
color="inherit"
waves-color="var(--hover-background)"
waves-center="true"
href="{ router.home() }"
icon
>
<i class="material-icons">arrow_back</i>
</material-button>
<h2>
@@ -32,7 +39,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<material-spinner></material-spinner>
</div>
<pagination pages="{ getPageLabels(state.page, getNumPages(state.tags)) }" onPageUpdate="{onPageUpdate}"></pagination>
<pagination
pages="{ getPageLabels(state.page, getNumPages(state.tags, props.tagsPerPage)) }"
onPageUpdate="{onPageUpdate}"
></pagination>
<tag-table
if="{ state.loadend }"
@@ -40,6 +50,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
asc="{state.asc}"
page="{ state.page }"
show-content-digest="{props.showContentDigest}"
show-tag-history="{props.showTagHistory}"
is-image-remove-activated="{props.isImageRemoveActivated}"
onReverseOrder="{ onReverseOrder }"
registry-url="{ props.registryUrl }"
@@ -47,18 +58,26 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
on-notify="{ props.onNotify }"
filter-results="{ props.filterResults }"
on-authentication="{ props.onAuthentication }"
tags-per-page="{ props.tagsPerPage }"
is-registry-secured="{ props.isRegistrySecured }"
on-image-deleted="{ () => state.reload() }"
>
</tag-table>
<pagination pages="{ getPageLabels(state.page, getNumPages(state.tags)) }" onPageUpdate="{onPageUpdate}"></pagination>
<pagination
pages="{ getPageLabels(state.page, getNumPages(state.tags, props.tagsPerPage)) }"
onPageUpdate="{onPageUpdate}"
></pagination>
<script>
import { Http } from '../../scripts/http';
import { DockerImage, compare } from '../../scripts/docker-image';
import { DockerImage } from '../../scripts/docker-image';
import { getNumPages, getPageLabels } from '../../scripts/utils';
import Pagination from './pagination.riot';
import TagTable from './tag-table.riot';
import router from '../../scripts/router';
import { getTagComparator, taglistOrderParser } from '../../scripts/taglist-order';
export default {
components: {
Pagination,
@@ -72,6 +91,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
asc: true,
page: router.getPageQueryParam() || 1,
};
try {
this.state.taglistOrder = taglistOrderParser(props.taglistOrder);
} catch (e) {
props.onNotify(e);
}
},
onMounted(props, state) {
this.display(props, state);
@@ -79,12 +103,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// this may be run before the final document size is available, so schedule
// a correction once everything is set up.
window.requestAnimationFrame(this.onResize);
this.tagComparator = getTagComparator(props.taglistOrder);
},
display(props, state) {
state.reload = () => setTimeout(() => this.display(props, state), 1000);
state.tags = [];
const self = this;
const oReq = new Http({
onAuthentication: props.onAuthentication,
withCredentials: props.isRegistrySecured,
});
oReq.addEventListener('load', function () {
if (this.status === 200) {
@@ -97,12 +124,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
onNotify: props.onNotify,
onAuthentication: props.onAuthentication,
useControlCacheHeader: props.useControlCacheHeader,
isRegistrySecured: props.isRegistrySecured,
})
)
.sort(compare);
.sort(self.tagComparator);
window.requestAnimationFrame(self.onResize);
self.update({
page: Math.min(state.page, getNumPages(tags)),
page: Math.min(state.page, getNumPages(tags, props.tagsPerPage)),
tags,
});
} else if (this.status === 404) {
@@ -126,7 +154,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
},
onPageUpdate(idx) {
const labels = getPageLabels(this.state.page, getNumPages(this.state.tags));
const labels = getPageLabels(this.state.page, getNumPages(this.state.tags, this.props.tagsPerPage));
const page = labels[idx].page;
this.update({
page: page,
@@ -161,7 +189,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
this.state.tags.reverse();
this.state.asc = false;
} else {
this.state.tags.sort(compare);
this.state.tags.sort(this.tagComparator);
this.state.asc = true;
}
this.update();

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -23,6 +23,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
on-authentication="{ props.onAuthentication }"
tags="{ props.tags }"
to-delete="{ state.toDelete }"
is-registry-secured="{ props.isRegistrySecured }"
on-image-deleted="{ props.onImageDeleted }"
></confirm-delete-image>
<material-card class="taglist">
<table style="border: none">
@@ -49,7 +51,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
>
Tag
</th>
<th class="show-tag-history">History</th>
<th class="architectures">Arch</th>
<th class="show-tag-history" if="{ props.showTagHistory }">History</th>
<th
class="remove-tag { state.toDelete.size > 0 && !state.singleDeleteAction ? 'delete' : '' }"
if="{ props.isImageRemoveActivated }"
@@ -63,12 +66,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
>
</material-checkbox>
<material-button
if="{ state.toDelete.size > 0 && !state.singleDeleteAction }"
waves-center="true"
rounded="true"
waves-color="#ddd"
color="inherit"
text-color="var(--neutral-background)"
waves-color="var(--hover-background)"
title="This will delete selected images."
onClick="{ deleteImages }"
if="{ state.toDelete.size > 0 && !state.singleDeleteAction }"
icon
>
<i class="material-icons">delete</i>
</material-button>
@@ -78,31 +83,34 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<tbody>
<tr each="{ image in getPage(props.tags, props.page) }" if="{ matchSearch(props.filterResults, image.tag) }">
<td class="creation-date">
<image-date image="{ image }" />
<image-date image="{ image }"></image-date>
</td>
<td class="image-size">
<image-size image="{ image }" />
<image-size image="{ image }"></image-size>
</td>
<td if="{ props.showContentDigest }">
<image-content-digest image="{ image }" />
<image-content-digest image="{ image }"></image-content-digest>
<copy-to-clipboard
target="digest"
image="{ image }"
pull-url="{ props.pullUrl }"
on-notify="{ props.onNotify }"
/>
></copy-to-clipboard>
</td>
<td>
<image-tag image="{ image }" />
<image-tag image="{ image }"></image-tag>
<copy-to-clipboard
target="tag"
image="{ image }"
pull-url="{ props.pullUrl }"
on-notify="{ props.onNotify }"
/>
></copy-to-clipboard>
</td>
<td class="show-tag-history">
<tag-history-button image="{ image }" />
<td class="architectures">
<architectures image="{ image }"></architectures>
</td>
<td class="show-tag-history" if="{ props.showTagHistory }">
<tag-history-button image="{ image }"></tag-history-button>
</td>
<td if="{ props.isImageRemoveActivated }" class="remove-tag">
<remove-image
@@ -113,7 +121,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
checked="{ state.toDelete.has(image) }"
on-notify="{ props.onNotify }"
on-authentication="{ props.onAuthentication }"
/>
></remove-image>
</td>
</tr>
</tbody>
@@ -128,6 +136,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import CopyToClipboard from './copy-to-clipboard.riot';
import TagHistoryButton from './tag-history-button.riot';
import RemoveImage from './remove-image.riot';
import Architectures from './architectures.riot';
import { matchSearch } from '../search-bar.riot';
import ConfirmDeleteImage from '../dialogs/confirm-delete-image.riot';
const ACTION_CHECK_TO_DELETE = 'CHECK';
@@ -144,6 +153,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
RemoveImage,
TagHistoryButton,
ConfirmDeleteImage,
Architectures,
},
onBeforeMount(props) {
this.state = {
@@ -172,7 +182,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
confirmDeleteImage: false,
});
},
onRemoveImageHeaderChange(checked, event) {
onRemoveImageHeaderChange(event) {
if (event.altKey === true) {
const tags = getPage(this.props.tags, this.props.page);
tags
@@ -181,23 +191,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
this.update({
multiDelete: true,
toDelete: this.state.toDelete,
selectedImage: undefined,
});
} else {
this.update({
multiDelete: checked,
multiDelete: event.target.checked,
selectedImage: undefined,
});
}
},
onRemoveImageChange(action, image) {
onRemoveImageChange(action, image, shiftKey) {
let confirmDeleteImage = false;
let singleDeleteAction = false;
let selectedImage = undefined;
switch (action) {
case ACTION_CHECK_TO_DELETE: {
this.state.toDelete.add(image);
if (shiftKey) {
selectedImage = this.supportShiftKey(image, true);
}
break;
}
case ACTION_UNCHECK_TO_DELETE: {
this.state.toDelete.delete(image);
if (shiftKey) {
selectedImage = this.supportShiftKey(image, false);
}
break;
}
case ACTION_DELETE_IMAGE: {
@@ -211,8 +230,33 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
toDelete: this.state.toDelete,
confirmDeleteImage,
singleDeleteAction,
selectedImage,
});
},
supportShiftKey(selectedImage, addOrRemove) {
if (!this.state.selectedImage) {
return selectedImage;
} else {
let shouldChange = false;
const tags = this.getPage(this.props.tags, this.props.page);
tags
.filter((image) => {
if (image == this.state.selectedImage || image == selectedImage) {
shouldChange = !shouldChange;
return true;
}
return shouldChange;
})
.forEach((image) => {
if (addOrRemove) {
this.state.toDelete.add(image);
} else {
this.state.toDelete.delete(image);
}
});
return undefined;
}
},
onReverseOrder() {
this.state.orderType = null;
this.state.desc = false;
@@ -225,12 +269,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
});
},
getPage(tags, page) {
const sortedTags = getPage(tags, page);
const sortedTags = getPage(tags, page, this.props.tagsPerPage);
if (this.state.orderType === 'date') {
sortedTags.sort((e1, e2) =>
!this.state.desc
? e2.creationDate.getTime() - e1.creationDate.getTime()
: e1.creationDate.getTime() - e2.creationDate.getTime()
? (e2.creationDate?.getTime() || 0) - (e1.creationDate?.getTime() || 0)
: (e1.creationDate?.getTime() || 0) - (e2.creationDate?.getTime() || 0)
);
} else if (this.state.orderType === 'size') {
sortedTags.sort((e1, e2) => (!this.state.desc ? e2.size - e1.size : e1.size - e2.size));
@@ -241,4 +285,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
};
export { ACTION_CHECK_TO_DELETE, ACTION_UNCHECK_TO_DELETE, ACTION_DELETE_IMAGE };
</script>
<style>
tag-table table th.architectures {
text-align: center;
}
</style>
</tag-table>

View File

@@ -0,0 +1,115 @@
<version-notification>
<span if="{ state.tag_name && !isNewestVersion(props.version, state.tag_name) }" onclick="{ onClick }"></span>
<material-popup opened="{ state.open }" onClick="{ onClose }">
<div class="material-popup-title">Check for updates</div>
<div class="material-popup-content">
<p>The version <b>{ state.tag_name }</b> of Docker Registry UI now available.</p>
<p>You can download the lastest version with docker.</p>
<code>joxit/docker-registry-ui:{ state.tag_name }</code>
</div>
<div class="material-popup-action">
<material-button
class="dialog-button release-note"
waves-color="var(--hover-background)"
href="{ state.latest && state.latest.html_url }"
color="inherit"
text-color="var(--accent-text)"
target="_blank"
>
Release Note
</material-button>
<material-button
class="dialog-button"
waves-color="var(--hover-background)"
onClick="{ onClose }"
color="inherit"
text-color="var(--primary-text)"
>
Close
</material-button>
</div>
</material-popup>
<script>
import rocketIcon from '../images/rocket.svg';
import { Http } from '../scripts/http';
import { isNewestVersion, parseJSON } from '../scripts/utils';
const LATEST = 'version-notification:latest';
const EXPIRES = 'version-notification:expiration-date';
const ONE_DAY = 24 * 60 * 60 * 1000;
export default {
onMounted(props, state) {
const latest = parseJSON(localStorage.getItem(LATEST));
const expires = parseInt(localStorage.getItem(EXPIRES));
if (latest && latest.tag_name) {
this.update({ tag_name: latest.tag_name, latest });
}
if (isNaN(expires) || new Date().getTime() > expires) {
this.checkForUpdates(props, state);
}
},
onUpdated(props, state) {
const span = this.$('span');
if (span) {
span.innerHTML = rocketIcon().firstElementChild.outerHTML;
}
},
onClose() {
this.update({ open: false });
},
onClick() {
this.update({ open: true });
},
checkForUpdates(props, state) {
const oReq = new Http();
const self = this;
oReq.addEventListener('load', function () {
localStorage.setItem(EXPIRES, new Date().getTime() + ONE_DAY * 7);
if (this.status === 200) {
const latest = parseJSON(this.responseText);
if (latest && self.tag_name !== latest.tag_name && !isNewestVersion(props.version, latest.tag_name)) {
props.onNotify('A new version of Docker Registry UI is available!');
}
localStorage.setItem(LATEST, this.responseText);
self.update({ tag_name: latest.tag_name, latest });
} else if (this.status !== 404) {
// Should not notify if the project is not found.
props.onNotify('Cannot check for new updates. Will try again in 24 hours. See the browser console.');
console.error(
`Cannot check for new Docker Registry UI updates. This is most likely a GitHub issue. You don't need to worry about it.`
);
console.error(`Got status code ${this.status} from Github API with response ${this.responseText}`);
}
});
oReq.open('GET', 'https://api.github.com/repos/joxit/docker-registry-ui/releases/latest');
oReq.send();
},
isNewestVersion,
};
</script>
<style>
:host {
display: inline;
}
:host svg {
margin-left: 10px;
cursor: pointer;
}
material-popup material-button > a:first-child {
display: flex;
align-items: center;
}
material-popup .material-popup-content code {
background-color: var(--hover-background);
padding: 0 5px;
border-radius: 4px;
line-height: 1.5em;
}
material-popup .material-popup-content b {
color: var(--accent-text);
}
</style>
</version-notification>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,37 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg2"
x="0px"
y="0px"
viewBox="-5.724 -43.601 730 600" fill="#777">
<path
d="m595.942,422.343 c -7.543,0.119 -13.562,6.331 -13.443,13.875 0.119,7.544 6.332,13.562 13.875,13.443 7.495,-0.118 13.494,-6.254 13.445,-13.75 -0.085,-7.578 -6.297,-13.652 -13.875,-13.568 0,0 -10e-4,0 0,0 m 0,24.398 c -5.975,0.272 -11.039,-4.352 -11.311,-10.326 -0.271,-5.976 4.352,-11.04 10.327,-11.312 5.975,-0.271 11.039,4.352 11.311,10.327 0.01,0.19 0.013,0.382 0.011,0.573 0.204,5.723 -4.27,10.527 -9.992,10.731 -0.115,0.005 -0.23,0.007 -0.346,0.007"/>
<path
d="m599.081,436.342 v -0.185 c 1.512,-0.292 2.65,-1.544 2.8,-3.076 0.057,-1.175 -0.432,-2.311 -1.323,-3.077 -1.445,-0.765 -3.076,-1.106 -4.707,-0.984 -1.743,-0.024 -3.484,0.12 -5.2,0.431 v 13.538 h 3.077 v -5.446 h 1.477 c 1.754,0 2.554,0.646 2.83,2.154 0.184,1.143 0.536,2.252 1.047,3.292 h 3.415 c -0.53,-1.062 -0.873,-2.207 -1.016,-3.385 -0.138,-1.473 -1.088,-2.744 -2.462,-3.292 m -3.723,-0.985 h -1.508 v -3.908 c 0.583,-0.069 1.172,-0.069 1.754,0 1.97,0 2.893,0.831 2.893,2.062 0,1.231 -1.415,2 -3.076,2"/>
<path
d="M707.494,193.557c-1.938-1.539-20.029-15.199-58.181-15.199c-10.074,0.044-20.127,0.908-30.061,2.584 c-7.384-50.612-49.228-75.288-51.104-76.395l-10.245-5.908l-6.738,9.723c-8.438,13.061-14.598,27.459-18.214,42.582 c-6.831,28.891-2.677,56.027,11.999,79.226c-17.722,9.876-46.151,12.307-51.904,12.522H22.367 c-12.294,0.017-22.27,9.952-22.337,22.245c-0.549,41.234,6.437,82.222,20.614,120.946c16.214,42.521,40.336,73.842,71.719,93.01 c35.167,21.537,92.302,33.844,157.067,33.844c29.258,0.092,58.461-2.556,87.226-7.907c39.986-7.342,78.463-21.318,113.839-41.352 c29.149-16.88,55.383-38.354,77.688-63.596c37.29-42.213,59.505-89.226,76.026-131.007c2.215,0,4.431,0,6.584,0 c40.828,0,65.935-16.338,79.78-30.029c9.201-8.732,16.384-19.369,21.045-31.167l2.923-8.553L707.494,193.557z"/>
<path
d="M65.995,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.476-5.538 c-0.01,0-0.021,0-0.031,0H65.995c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C60.488,226.443,62.953,228.909,65.995,228.909L65.995,228.909"/>
<path
d="M152.913,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.059,0-5.538,2.479-5.538,5.538v56.181C147.392,226.448,149.866,228.909,152.913,228.909"/>
<path
d="M241.153,228.909h63.073c3.042,0,5.507-2.466,5.507-5.507l0,0V167.22c0.017-3.042-2.435-5.521-5.477-5.538 c-0.01,0-0.021,0-0.031,0h-63.073c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181 C235.646,226.443,238.112,228.909,241.153,228.909L241.153,228.909"/>
<path
d="M328.348,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C322.841,226.443,325.307,228.909,328.348,228.909L328.348,228.909"/>
<path
d="M152.913,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.046,0-5.521,2.46-5.538,5.507v56.181C147.392,145.597,149.861,148.066,152.913,148.083"/>
<path
d="M241.153,148.083h63.073c3.046-0.017,5.507-2.492,5.507-5.538V86.364c0-3.042-2.466-5.507-5.507-5.507l0,0h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C235.646,145.591,238.107,148.066,241.153,148.083"/>
<path
d="M328.348,148.083h63.073c3.052-0.017,5.521-2.486,5.538-5.538V86.364c-0.017-3.047-2.491-5.507-5.538-5.507h-63.073 c-3.042,0-5.507,2.466-5.507,5.507l0,0v56.181C322.841,145.591,325.302,148.066,328.348,148.083"/>
<path
d="M328.348,67.227h63.073c3.047,0,5.521-2.461,5.538-5.507V5.507C396.942,2.46,394.468,0,391.421,0h-63.073 c-3.042,0-5.507,2.465-5.507,5.507l0,0v56.212C322.841,64.761,325.307,67.227,328.348,67.227"/>
<path
d="M416.312,228.909h63.073c3.047,0,5.521-2.46,5.538-5.507V167.22c0-3.059-2.479-5.538-5.538-5.538l0,0h-63.073 c-3.041,0-5.507,2.466-5.507,5.507c0,0.01,0,0.021,0,0.031v56.181C410.805,226.443,413.271,228.909,416.312,228.909"/>
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 730 730" fill="var(--neutral-text)">
<path d="M603.672 530.973c-7.543.12-13.562 6.331-13.443 13.875.119 7.544 6.332 13.562 13.875 13.443 7.495-.118 13.494-6.254 13.445-13.75-.085-7.578-6.297-13.652-13.875-13.568 0 0-.001 0 0 0m0 24.398c-5.975.272-11.04-4.352-11.311-10.326-.271-5.976 4.352-11.04 10.327-11.312 5.975-.27 11.039 4.352 11.31 10.327.01.19.014.382.012.573.204 5.723-4.27 10.527-9.992 10.731-.115.005-.23.007-.346.007"/>
<path d="M606.81 544.972v-.185a3.476 3.476 0 002.8-3.076 3.815 3.815 0 00-1.322-3.077 8.675 8.675 0 00-4.707-.984 27.066 27.066 0 00-5.2.431v13.538h3.077v-5.446h1.477c1.754 0 2.554.646 2.83 2.154a11.667 11.667 0 001.047 3.292h3.415a10.381 10.381 0 01-1.016-3.385 3.938 3.938 0 00-2.462-3.292m-3.723-.985h-1.508v-3.908a7.457 7.457 0 011.754 0c1.97 0 2.893.831 2.893 2.062 0 1.231-1.415 2-3.076 2m112.135-241.924c-1.938-1.539-20.03-15.199-58.181-15.199a185.562 185.562 0 00-30.061 2.584c-7.384-50.612-49.228-75.288-51.104-76.395l-10.245-5.908-6.738 9.723a137.318 137.318 0 00-18.214 42.582c-6.831 28.891-2.677 56.027 11.999 79.226-17.722 9.876-46.151 12.307-51.904 12.522H30.096c-12.293.017-22.27 9.952-22.336 22.245a338.69 338.69 0 0020.614 120.946c16.214 42.521 40.336 73.842 71.719 93.01 35.167 21.537 92.302 33.844 157.067 33.844a468.88 468.88 0 0087.226-7.907 364.649 364.649 0 00113.839-41.352 312.969 312.969 0 0077.688-63.596c37.29-42.213 59.505-89.226 76.026-131.007h6.584c40.828 0 65.935-16.338 79.78-30.029a87.08 87.08 0 0021.045-31.167l2.923-8.553z"/>
<path d="M73.725 337.54h63.073a5.507 5.507 0 005.507-5.508V275.85a5.507 5.507 0 00-5.476-5.538H73.725a5.507 5.507 0 00-5.507 5.507v56.212a5.507 5.507 0 005.507 5.508m86.918.001h63.073a5.507 5.507 0 005.507-5.508V275.85a5.507 5.507 0 00-5.477-5.538h-63.104a5.537 5.537 0 00-5.538 5.538v56.181a5.54 5.54 0 005.539 5.508m88.24.001h63.073a5.507 5.507 0 005.507-5.508V275.85a5.507 5.507 0 00-5.477-5.538h-63.104a5.507 5.507 0 00-5.507 5.507v56.212a5.51 5.51 0 005.508 5.508m87.195.001h63.073a5.537 5.537 0 005.538-5.508V275.85a5.537 5.537 0 00-5.538-5.538h-63.073a5.507 5.507 0 00-5.507 5.507v56.212a5.508 5.508 0 005.507 5.508m-175.435-80.826h63.073a5.539 5.539 0 005.507-5.538v-56.18a5.507 5.507 0 00-5.507-5.508h-63.073a5.538 5.538 0 00-5.538 5.507v56.181a5.568 5.568 0 005.538 5.538m88.24 0h63.073a5.539 5.539 0 005.507-5.538v-56.18a5.507 5.507 0 00-5.507-5.508h-63.073a5.507 5.507 0 00-5.507 5.507v56.181a5.539 5.539 0 005.507 5.538m87.195 0h63.073a5.568 5.568 0 005.538-5.538v-56.18a5.537 5.537 0 00-5.538-5.508h-63.073a5.507 5.507 0 00-5.507 5.507v56.181a5.539 5.539 0 005.507 5.538m0-80.856h63.073a5.538 5.538 0 005.538-5.507v-56.213a5.537 5.537 0 00-5.538-5.507h-63.073a5.506 5.506 0 00-5.507 5.507v56.212a5.508 5.508 0 005.507 5.508m87.964 161.683h63.073a5.537 5.537 0 005.538-5.508V275.85a5.537 5.537 0 00-5.538-5.538h-63.073a5.508 5.508 0 00-5.507 5.507v56.212a5.508 5.508 0 005.507 5.508"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

20
src/images/rocket.svg Normal file
View File

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 128 128">
<path fill="#ca2c31" d="M3.77 71.73l16.34-16.1 27.82-4.93-2.75 14.56L7.57 76.82l-2.43-1.05z"/>
<path fill="#a02422" d="M22.94 59.76L5.2 75.88l13.05 6.36 19.81-10.11v-4.77l4.05-10.92zm41.98 28.39l-8.57 3.72-8.09 17.15s7.12 15.77 7.44 15.77c.32 0 4.37.32 4.37.32l14.4-16.1 3.64-27.5-13.19 6.64z"/>
<path d="M56.5 100.84s4.77-.97 8.17-2.59c3.4-1.62 7.6-4.04 7.6-4.04l-1.54 13.43-15.05 17.13s-.59-.73-3.09-6.17c-1.99-4.34-2.68-5.89-2.68-5.89l6.59-11.87z" fill="#ca2c31"/>
<path d="M31.58 80.66s-5.74-.48-12.03 7.47c-5.74 7.26-8.43 19.08-9.47 22.12s-3.53 3.66-2.7 5.05 4.42 1.31 8.85.76 8.23-1.94 8.23-1.94-.19.48-.83 1.52c-.23.37-1.03.9-.97 1.45.14 1.31 11.36 1.34 20.32-7.88 9.68-9.95 4.98-18.11 4.98-18.11L31.58 80.66z" fill="#f7d74d"/>
<path d="M33.31 85.29s-6.19.33-11.31 8.28-7.5 17.16-7.01 17.78c.48.62 10.02-2.83 12.31-2.14 1.57.48.76 2.07 1.18 2.49.35.35 4.49.94 11.19-6.32 6.71-7.26 5.12-17.46 5.12-17.46l-11.48-2.63z" fill="#fbf0b4"/>
<path d="M36.35 74.44s-3.11 2.77-4.22 4.36c-1.11 1.59-1.11 1.73-1.04 2.21.07.48 1.22 5.75 6.01 10.37 5.88 5.67 11.13 6.43 11.89 6.43.76 0 5.81-5.67 5.81-5.67l-18.45-17.7z" fill="#858585"/>
<path d="M50.1 91.24s5.04 3.31 13.49.47c11.55-3.88 20.02-12.56 30.51-23.52 10.12-10.58 18.61-23.71 18.61-23.71l-5.95-19.93L50.1 91.24z" fill="#437687"/>
<path d="M67.99 80.33l1.39-4.32 3.48.49s2.65 1.25 4.6 2.16c1.95.91 4.46 1.6 4.46 1.6l-4.95 4.18s-2.7-1.02-4.67-1.88c-2.22-.97-4.31-2.23-4.31-2.23z" fill="#3f545f"/>
<path d="M84.32 16.14s-9.62 5.58-23.41 18.63c-12.43 11.76-21.64 22.4-23.87 31.45-1.86 7.58-.87 12.18 3.36 17.15 4.47 5.26 9.71 7.87 9.71 7.87s3.94.06 20.38-12.59C91 62.86 107.43 36.42 107.43 36.42L84.32 16.14z" fill="#8dafbf"/>
<path d="M104.18 41.84s-8.37-3.57-14.34-11.9c-5.93-8.27-5.46-13.86-5.46-13.86s4.96-3.89 16.11-8.34c7.5-2.99 17.71-4.52 21.07-2.03s-2.3 14.98-2.3 14.98l-10.31 19.96-4.77 1.19z" fill="#d83f22"/>
<path d="M68.17 80.4s-7.23-3.69-11.83-8.94c-8.7-9.91-10.5-20.79-10.5-20.79l4.37-5.13S51.3 57.1 60.63 67.09c6.08 6.51 12.43 9.49 12.43 9.49s-1.27 1.07-2.63 2.11c-.87.67-2.26 1.71-2.26 1.71z" fill="#6896a5"/>
<path d="M112.71 44.48s4.34-5.23 8.45-17.02c5.74-16.44.74-21.42.74-21.42s-1.69 7.82-7.56 18.69c-4.71 8.71-10.41 17-10.41 17s3.14 1.41 4.84 1.9c2.14.62 3.94.85 3.94.85z" fill="#a02422"/>
<path d="M39.81 69.66c1.3 1.24 3.27-.06 4.56-3.1 1.3-3.04 1.28-4.74.28-5.46-1.24-.9-3.32 1.07-4.23 2.82-1 1.94-1.59 4.8-.61 5.74zm45.14-49.53s-7.61 5.47-15.73 12.91c-7.45 6.83-12.39 12.17-13.07 13.41-.72 1.33-.73 3.21-.17 4.17s1.8 1.46 2.93.62c1.13-.85 9.18-9.75 16.45-16.11 6.65-5.82 11.78-9.51 11.78-9.51s2.08-3.68 1.74-4.52c-.34-.85-3.93-.97-3.93-.97z" fill="#b3e1ee"/>
<path d="M84.95 20.13s5.62-4.31 11.74-7.34c5.69-2.82 11.35-5.17 12.37-3.13.97 1.94-5.37 4.58-10.95 8.14-5.58 3.56-10.95 7.81-10.95 7.81s-.82-1.5-1.35-2.89a23.7 23.7 0 01-.86-2.59z" fill="#ed6a65"/>
<path d="M89.59 39.25c-5.57-5.13-13.32-3.75-17.14.81-3.92 4.7-3.63 11.88 1 16.2 4.21 3.92 12.04 4.81 16.76-.69 4.2-4.88 3.94-12.13-.62-16.32z" fill="#e1e1e1"/>
<path d="M75.33 41.87c-3.31 3.25-3.13 9.69.81 12.63 3.44 2.57 8.32 2.44 11.38-.69 3.06-3.13 3.06-8.82.19-11.76-3.3-3.37-8.59-3.9-12.38-.18z" fill="#3f545f"/>
<path d="M50 76.89s6.19-6.28 6.87-5.6c.68.68.59 4.49-2.37 8.73-2.97 4.24-9.5 11.79-14.67 16.88-5.1 5.01-12.29 10.74-12.97 10.64-.53-.08-2.68-1.15-3.54-2.19-.84-1.03 1.67-5.9 2.68-7.51 1.02-1.61 24-20.95 24-20.95z" fill="#a02524"/>
<path d="M21.23 101.85c-.08 1.44 2.12 3.54 2.12 3.54L56.87 71.3s-1.57-1.77-6.19 1.1c-4.66 2.9-8.74 6.38-14.76 12.21-8.39 8.14-14.61 15.8-14.69 17.24z" fill="#ca2c31"/>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016-2021 Jones Magloire @Joxit
Copyright (C) 2016-2023 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -35,10 +35,12 @@
<body>
<!-- build:keep production -->
<docker-registry-ui
docker-registry-ui-title="${DOCKER_REGISTRY_UI_TITLE}"
registry-url="${REGISTRY_URL}"
name="${REGISTRY_TITLE}"
pull-url="${PULL_URL}"
show-content-digest="${SHOW_CONTENT_DIGEST}"
show-tag-history="${SHOW_TAG_HISTORY}"
is-image-remove-activated="${DELETE_IMAGES}"
catalog-elements-limit="${CATALOG_ELEMENTS_LIMIT}"
single-registry="${SINGLE_REGISTRY}"
@@ -47,21 +49,61 @@
show-catalog-nb-tags="${SHOW_CATALOG_NB_TAGS}"
history-custom-labels="${HISTORY_CUSTOM_LABELS}"
use-control-cache-header="${USE_CONTROL_CACHE_HEADER}"
taglist-order="${TAGLIST_ORDER}"
catalog-default-expanded="${CATALOG_DEFAULT_EXPANDED}"
catalog-min-branches="${CATALOG_MIN_BRANCHES}"
catalog-max-branches="${CATALOG_MAX_BRANCHES}"
is-registry-secured="${REGISTRY_SECURED}"
theme="${THEME}"
theme-primary-text="${THEME_PRIMARY_TEXT}"
theme-neutral-text="${THEME_NEUTRAL_TEXT}"
theme-background="${THEME_BACKGROUND}"
theme-hover-background="${THEME_HOVER_BACKGROUND}"
theme-accent-text="${THEME_ACCENT_TEXT}"
theme-header-accent-text="${THEME_HEADER_ACCENT_TEXT}"
theme-header-text="${THEME_HEADER_TEXT}"
theme-header-background="${THEME_HEADER_BACKGROUND}"
theme-footer-text="${THEME_FOOTER_TEXT}"
theme-footer-neutral-text="${THEME_FOOTER_NEUTRAL_TEXT}"
theme-footer-background="${THEME_FOOTER_BACKGROUND}"
tags-per-page="${TAGLIST_PAGE_SIZE}"
enable-version-notification="${ENABLE_VERSION_NOTIFICATION}"
>
</docker-registry-ui>
<!-- endbuild -->
<!-- build:keep developement -->
<docker-registry-ui
docker-registry-ui-title=""
registry-url=""
name="Developement Registry"
name="Development Registry"
pull-url=""
show-content-digest="true"
show-tag-history="true"
is-image-remove-activated="true"
catalog-elements-limit="1000"
single-registry="false"
show-catalog-nb-tags="true"
history-custom-labels="first_custom_labels,second_custom_labels"
use-control-cache-header="false"
taglist-order=""
catalog-default-expanded=""
catalog-min-branches="1"
catalog-max-branches="1"
is-registry-secured="false"
theme="auto"
theme-primary-text=""
theme-neutral-text=""
theme-background=""
theme-hover-background=""
theme-accent-text=""
theme-header-accent-text=""
theme-header-text=""
theme-header-background=""
theme-footer-text=""
theme-footer-neutral-text=""
theme-footer-background=""
tags-per-page=""
enable-version-notification="true"
>
</docker-registry-ui>
<!-- endbuild -->

View File

@@ -10,9 +10,10 @@ import {
MaterialCheckbox,
MaterialTabs,
MaterialSnackbar,
MaterialDropdownList,
MaterialDropdown,
MaterialPopup,
MaterialInput,
MaterialSwitch,
} from 'riot-mui';
import DockerRegistryUI from './components/docker-registry-ui.riot';
@@ -28,9 +29,10 @@ register('material-waves', MaterialWaves);
register('material-checkbox', MaterialCheckbox);
register('material-snackbar', MaterialSnackbar);
register('material-tabs', MaterialTabs);
register('material-dropdown-list', MaterialDropdownList);
register('material-dropdown', MaterialDropdown);
register('material-popup', MaterialPopup);
register('material-input', MaterialInput);
register('material-switch', MaterialSwitch);
const createApp = component(DockerRegistryUI);
const tags = document.getElementsByTagName('docker-registry-ui');

View File

@@ -1,20 +1,18 @@
@font-face {
font-family: 'Material Icons';
font-family: 'Material Symbols Rounded';
font-style: normal;
font-weight: 400;
src: url(fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(fonts/MaterialIcons-Regular.woff2) format('woff2'),
url(fonts/MaterialIcons-Regular.woff) format('woff'),
url(fonts/MaterialIcons-Regular.ttf) format('truetype');
src: local('Material Symbols Rounded'),
url(fonts/material-symbols-rounded.woff2) format('woff2'),
url(fonts/material-symbols-rounded.woff) format('woff'),
url(fonts/material-symbols-rounded.ttf) format('truetype');
}
material-button .content i.material-icons,
material-button[rounded=true] .content i.material-icons,
i.material-icons {
font-family: 'Material Icons';
font-family: 'Material Symbols Rounded';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
@@ -38,6 +36,12 @@ i.material-icons {
/* Support for IE. */
font-feature-settings: 'liga';
user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
}
material-button .content i.material-icons,

View File

@@ -0,0 +1,36 @@
const SHA_REGEX = /(blobs|manifests)\/sha256:[a-f0-9]+$/;
const getSha256 = (method, url) => {
if (method !== 'GET') {
return;
}
const parts = SHA_REGEX.exec(url);
if (!parts || !parts[0]) {
return;
}
return parts[0];
};
export const getFromCache = (method, url) => {
const sha256 = getSha256(method, url);
if (!sha256) {
return;
}
try {
return {
responseText: sessionStorage.getItem(`${sha256}/responseText`),
dockerContentdigest: sessionStorage.getItem(`${sha256}/dockerContentdigest`),
};
} catch (e) {}
};
export const setCache = (method, url, { responseText, dockerContentdigest }) => {
const sha256 = getSha256(method, url);
if (!sha256) {
return;
}
try {
sessionStorage.setItem(`${sha256}/responseText`, responseText);
sessionStorage.setItem(`${sha256}/dockerContentdigest`, dockerContentdigest);
} catch (e) {}
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016-2021 Jones Magloire @Joxit
* Copyright (C) 2016-2023 Jones Magloire @Joxit
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -14,39 +14,35 @@
* 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/>.
*/
import { Http } from './http';
import { isDigit, eventTransfer, ERROR_CAN_NOT_READ_CONTENT_DIGEST } from './utils';
import { Http } from './http.js';
import { eventTransfer, ERROR_CAN_NOT_READ_CONTENT_DIGEST } from './utils.js';
import observable from '@riotjs/observable';
const tagReduce = (acc, e) => {
if (acc.length > 0 && isDigit(acc[acc.length - 1].charAt(0)) == isDigit(e)) {
acc[acc.length - 1] += e;
} else {
acc.push(e);
export const supportListManifest = (response) => {
if (response.mediaType === 'application/vnd.docker.distribution.manifest.list.v2+json') {
return true;
}
return acc;
if (response.mediaType === 'application/vnd.oci.image.index.v1+json' && Array.isArray(response.manifests)) {
return !response.manifests.some(({ mediaType }) => mediaType !== 'application/vnd.oci.image.manifest.v1+json');
}
return false;
};
export function compare(e1, e2) {
const tag1 = e1.tag.match(/./g).reduce(tagReduce, []);
const tag2 = e2.tag.match(/./g).reduce(tagReduce, []);
export const filterWrongManifests = (response) => {
return response.manifests.filter(
({ annotations }) => !annotations || annotations['vnd.docker.reference.type'] !== 'attestation-manifest'
);
};
for (var i = 0; i < tag1.length && i < tag2.length; i++) {
const compare = tag1[i].localeCompare(tag2[i]);
if (isDigit(tag1[i].charAt(0)) && isDigit(tag2[i].charAt(0))) {
const diff = tag1[i] - tag2[i];
if (diff != 0) {
return diff;
}
} else if (compare != 0) {
return compare;
}
export const platformToString = (platform) => {
if (!platform || !platform.architecture) {
return 'unknown';
}
return e1.tag.length - e2.tag.length;
}
return platform.architecture + (platform.variant ? platform.variant : '');
};
export class DockerImage {
constructor(name, tag, { list, registryUrl, onNotify, onAuthentication, useControlCacheHeader }) {
constructor(name, tag, { list, registryUrl, onNotify, onAuthentication, useControlCacheHeader, isRegistrySecured }) {
this.name = name;
this.tag = tag;
this.chars = 0;
@@ -56,6 +52,7 @@ export class DockerImage {
onNotify,
onAuthentication,
useControlCacheHeader,
isRegistrySecured,
};
this.ociImage = false;
observable(this);
@@ -95,13 +92,18 @@ export class DockerImage {
return;
}
this._fillInfoWaiting = true;
const oReq = new Http({ onAuthentication: this.opts.onAuthentication });
const oReq = new Http({
onAuthentication: this.opts.onAuthentication,
withCredentials: this.opts.isRegistrySecured,
});
const self = this;
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.opts.list) {
self.trigger('list', response);
if (supportListManifest(response) && self.opts.list) {
const manifests = filterWrongManifests(response);
self.trigger('list', manifests);
self.manifests = manifests;
const manifest = response.manifests[0];
const image = new DockerImage(self.name, manifest.digest, { ...self.opts, list: false });
eventTransfer(image, self);
@@ -111,6 +113,7 @@ export class DockerImage {
}
self.ociImage = response.mediaType === 'application/vnd.oci.image.index.v1+json';
self.layers = response.layers || response.manifests;
self.annotations = response.annotations;
self.size = self.layers.reduce(function (acc, e) {
return acc + e.size;
}, 0);
@@ -150,13 +153,17 @@ export class DockerImage {
oReq.send();
}
getBlobs(blob) {
const oReq = new Http({ onAuthentication: this.opts.onAuthentication });
const oReq = new Http({
onAuthentication: this.opts.onAuthentication,
withCredentials: this.opts.isRegistrySecured,
});
const self = this;
oReq.addEventListener('loadend', function () {
if (this.status === 200 || this.status === 202) {
const response = JSON.parse(this.responseText);
self.creationDate = new Date(response.created);
self.creationDate = new Date(response.created || self.annotations?.['org.opencontainers.image.created']);
self.blobs = response;
self.blobs.history = self.blobs.history || [];
self.blobs.history
.filter(function (e) {
return !e.empty_layer;

7
src/scripts/error.js Normal file
View File

@@ -0,0 +1,7 @@
export class DockerRegistryUIError extends Error {
constructor(msg, code) {
super(msg);
this.isError = true;
this.code = code;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016-2021 Jones Magloire @Joxit
* Copyright (C) 2016-2023 Jones Magloire @Joxit
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -15,6 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getFromCache, setCache } from './cache-request.js';
export class Http {
constructor(opts) {
this.oReq = new XMLHttpRequest();
@@ -27,19 +29,23 @@ export class Http {
}
getContentDigest(cb) {
if (this.oReq.hasHeader('Docker-Content-Digest')) {
if (this.cache?.dockerContentdigest) {
cb(this.cache.dockerContentdigest);
} else 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((byte) => byte.toString(16).padStart(2, '0'))
.join('')
);
});
crypto.subtle
.digest('SHA-256', new TextEncoder().encode(this.oReq.responseText || this.cache?.responseText))
.then(function (buffer) {
cb(
'sha256:' +
Array.from(new Uint8Array(buffer))
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('')
);
});
} else {
// IE and old Edge
// simply do not call the callback and skip the setup downstream
@@ -52,9 +58,9 @@ export class Http {
switch (e) {
case 'loadend': {
self.oReq.addEventListener('loadend', function () {
if (this.status === 401 && !this.withCredentials) {
const tokenAuth =
this.hasHeader('www-authenticate') && parseAuthenticateHeader(this.getResponseHeader('www-authenticate'));
const tokenAuth =
this.hasHeader('www-authenticate') && parseAuthenticateHeader(this.getResponseHeader('www-authenticate'));
if (this.status === 401 && (!this.withCredentials || tokenAuth)) {
self.onAuthentication(tokenAuth, (bearer) => {
const req = new XMLHttpRequest();
req._url = self._url;
@@ -67,15 +73,22 @@ export class Http {
}
if (bearer && bearer.token) {
req.setRequestHeader('Authorization', `Bearer ${bearer.token}`);
} else if (bearer && bearer.access_token) {
req.setRequestHeader('Authorization', `Bearer ${bearer.access_token}`);
} else {
req.withCredentials = true;
}
req.hasHeader = hasHeader;
req.getErrorMessage = Http.getErrorMessage;
req.getErrorMessage = getErrorMessage;
self.oReq = req;
req.send();
});
} else {
this.status === 200 &&
setCache(self._method, self._url, {
responseText: this.responseText,
dockerContentdigest: this.getResponseHeader('Docker-Content-Digest'),
});
f.bind(this)();
}
});
@@ -114,6 +127,11 @@ export class Http {
}
send() {
const cache = getFromCache(this._method, this._url);
if (cache && cache.responseText) {
this.cache = cache;
return this._events['loadend'].bind({ status: 200, responseText: cache.responseText })();
}
this.oReq.send();
}
}
@@ -128,15 +146,9 @@ const hasHeader = function (header) {
const getErrorMessage = function () {
if (this._url.match('^http://') && window.location.protocol === 'https:') {
return (
'Mixed Content: The page at `' +
window.location.origin +
'` was loaded over HTTPS, but requested an insecure server endpoint `' +
new URL(this._url).origin +
'`. This request has been blocked; the content must be served over HTTPS.'
);
return { code: 'MIXED_CONTENT', url: this._url };
} else if (!this._url || !this._url.match('^http')) {
return 'Incorrect server endpoint.';
return { code: 'INCORRECT_URL', url: this._url };
} else if (this.withCredentials && !this.hasHeader('Access-Control-Allow-Credentials')) {
return (
"The `Access-Control-Allow-Credentials` header in the response is missing and must be set to `true` when the request's credentials mode is on. Origin `" +

View File

@@ -0,0 +1,70 @@
import { DockerRegistryUIError } from './error.js';
const ERROR_CODE = 'CATALOG_BRANCHING_CONFIGURATION';
const getRepositoryName = (split, max) => {
let repositoryName = '';
for (let i = 0; i < Math.min(max, split.length - 1); i++) {
repositoryName += `${split[i]}/`;
}
return repositoryName;
};
const getLatestRepository = (repo, repoName) => {
if (!repo.images) {
return;
}
if (repo.repo === repoName) {
return repo;
}
for (let i = 0; i < repo.images.length; i++) {
const res = getLatestRepository(repo.images[i], repoName);
if (res) {
return res;
}
}
if (repoName.startsWith(repo.repo)) {
const newRepo = { repo: repoName, images: [] };
repo.images.push(newRepo);
return newRepo;
}
};
const cleanInt = (n) => (n === '' ? 1 : parseInt(n));
export const getBranching = (min = 1, max = 1) => {
min = cleanInt(min);
max = cleanInt(max);
if (isNaN(min) || isNaN(max)) {
throw new DockerRegistryUIError(`min and max must be integers: (min: ${min} and max: ${max}))`, ERROR_CODE);
} else if (min > max) {
throw new DockerRegistryUIError(`min must be inferior to max (min: ${min} <= max: ${max})`, ERROR_CODE);
} else if (max < 0 || min < 0) {
throw new DockerRegistryUIError(
`min and max must be greater than equals to 0 (min: ${min} >= 0 and max: ${max} >= 0)`,
ERROR_CODE
);
}
if (max == 1) {
min = 1;
}
return (repositories) =>
repositories.sort().reduce(function (acc, image) {
const split = image.split('/');
if (split.length > min && min > 0) {
const repoName = getRepositoryName(split, max);
let repo = acc.length > 0 && getLatestRepository(acc[acc.length - 1], repoName);
if (!repo) {
repo = {
repo: repoName,
images: [],
};
acc.push(repo);
}
repo.images.push(image);
return acc;
}
acc.push(image);
return acc;
}, []);
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016-2021 Jones Magloire @Joxit
* Copyright (C) 2016-2023 Jones Magloire @Joxit
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -14,7 +14,7 @@
* 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/>.
*/
import { router, getCurrentRoute } from '@riotjs/route';
import { getCurrentRoute } from '@riotjs/route';
import { encodeURI, decodeURI } from './utils';
function getQueryParams() {
@@ -59,16 +59,16 @@ function baseUrl(qs) {
export default {
home() {
router.push(baseUrl({ page: null }));
return baseUrl({ page: null });
},
taglist(image) {
router.push(`${baseUrl({ page: null })}#!/taglist/${image}`);
return `${baseUrl({ page: null })}#!/taglist/${image}`;
},
getTagListImage() {
return getCurrentRoute().replace(/^.*(#!)?\/?taglist\//, '');
},
history(image, tag) {
router.push(`${baseUrl({ page: null })}#!/taghistory/image/${image}/tag/${tag}`);
return `${baseUrl({ page: null })}#!/taghistory/image/${image}/tag/${tag}`;
},
getTagHistoryImage() {
return getCurrentRoute().replace(/^.*(#!)?\/?taghistory\/image\/(.*)\/tag\/(.*)\/?$/, '$2');

View File

@@ -0,0 +1,89 @@
import { DockerRegistryUIError } from './error.js';
import { isDigit } from './utils.js';
const TAGLIST_ORDER_REGEX = /(alpha-(asc|desc);num-(asc|desc))|(num-(asc|desc);alpha-(asc|desc))/;
export const taglistOrderVariants = (taglistOrder) => {
switch (taglistOrder) {
case 'desc':
return 'alpha-desc;num-desc';
case 'asc':
return 'num-asc;alpha-asc';
case 'alpha-desc':
case 'alpha-asc':
case 'num-desc':
case 'num-asc':
return `${taglistOrder};${taglistOrder.startsWith('num') ? 'alpha' : 'num'}-asc`;
default:
if (!taglistOrder) {
return 'alpha-asc;num-desc';
} else if (TAGLIST_ORDER_REGEX.test(taglistOrder)) {
return taglistOrder;
}
throw new DockerRegistryUIError(`The taglist order \`${taglistOrder}\` is not recognized.`);
}
};
export const taglistOrderParser = (taglistOrder) => {
const orders = taglistOrderVariants(taglistOrder)
.split(';')
.filter((e) => e)
.map((e) => e.split('-').filter((e) => e))
.reduce((acc, e, idx) => {
if (e.length > 1) {
acc[e[0] + 'Asc'] = e[1] === 'asc';
}
if (idx === 0) {
acc.numFirst = e[0] === 'num';
}
return acc;
}, {});
return orders;
};
export const tagReduce = (acc, e) => {
if (acc.length > 0 && isDigit(acc[acc.length - 1].charAt(0)) == isDigit(e)) {
acc[acc.length - 1] += e;
} else {
acc.push(e);
}
return acc;
};
export const splitTagToArray = (tag) =>
tag
.split('')
.reduce(tagReduce, [])
.map((e) => (isDigit(e.charAt(0)) ? parseInt(e) : e));
const applyOrder = (order, e1, e2) => {
if (e1 === e2) {
return 0;
}
const numFirst = order.numFirst ? 1 : -1;
if (typeof e1 === 'number') {
const factor = order.numAsc ? 1 : -1;
return typeof e2 === 'number' ? (e1 - e2) * factor : -1 * numFirst;
} else if (typeof e2 === 'number') {
return 1 * numFirst;
} else {
const factor = order.alphaAsc ? 1 : -1;
return e1.localeCompare(e2) * factor;
}
};
export const getTagComparator = (order) => {
return (e1, e2) => {
const tag1 = splitTagToArray(e1.tag || e1);
const tag2 = splitTagToArray(e2.tag || e2);
for (var i = 0; i < tag1.length && i < tag2.length; i++) {
const compare = applyOrder(order, tag1[i], tag2[i]);
if (compare != 0) {
return compare;
}
}
return (e1.tag || e1).length - (e2.tag || e2).length;
};
};

66
src/scripts/theme.js Normal file
View File

@@ -0,0 +1,66 @@
const LIGHT_THEME = {
'primary-text': '#25313b',
'neutral-text': '#777777',
'background': '#ffffff',
'hover-background': '#eeeeee',
'accent-text': '#5f7796',
'header-text': '#ffffff',
'header-accent-text': '#7b9ac2',
'header-background': '#25313b',
'footer-text': '#ffffff',
'footer-neutral-text': '#adbacd',
'footer-background': '#344251',
};
const DARK_THEME = {
'primary-text': '#98a8bd',
'neutral-text': '#6d7fab',
'background': '#22272e',
'hover-background': '#343a4b',
'accent-text': '#5c88ff',
'header-text': '#ffffff',
'header-accent-text': '#7ea1ff',
'header-background': '#333a45',
'footer-text': '#ffffff',
'footer-neutral-text': '#98afcf',
'footer-background': '#344251',
};
const LOCAL_STORAGE_THEME = 'registryUiTheme';
let THEME;
const normalizeKey = (k) =>
k
.replace(/([A-Z])/g, '-$1')
.toLowerCase()
.replace(/^theme-/, '');
const preferDarkMode = ({ theme }) => {
if (theme === 'auto' || theme === '') {
switch (localStorage.getItem(LOCAL_STORAGE_THEME)) {
case 'dark':
return true;
case 'light':
return false;
default:
if (typeof window.matchMedia === 'function') {
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
return prefersDarkScheme && prefersDarkScheme.matches;
}
}
}
return theme === 'dark';
};
export const loadTheme = (props, style) => {
const isDarkMode = preferDarkMode(props);
THEME = isDarkMode ? { ...DARK_THEME } : { ...LIGHT_THEME };
Object.entries(props)
.filter(([k, v]) => v && /^theme[A-Z]/.test(k))
.map(([k, v]) => [normalizeKey(k), v])
.forEach(([k, v]) => (THEME[k] = v));
Object.entries(THEME).forEach(([k, v]) => style.setProperty(`--${k}`, v));
const theme = isDarkMode ? 'dark' : 'light';
localStorage.setItem(LOCAL_STORAGE_THEME, theme);
return theme;
};

View File

@@ -8,7 +8,12 @@ export function bytesToSize(bytes) {
return '0 Byte';
}
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.ceil(bytes / Math.pow(1024, i)) + ' ' + sizes[i];
const number = bytes / Math.pow(1024, i);
if (number < 10) {
const decimal = (bytes - Math.floor(number) * Math.pow(1024, i)) / Math.pow(1024, i);
return `${Math.floor(number)}.${Math.floor(decimal * 10)} ${sizes[i]}`;
}
return Math.ceil(number) + ' ' + sizes[i];
}
export function dateFormat(date) {
@@ -75,6 +80,19 @@ export function getHistoryIcon(attribute) {
return 'get_app';
case 'ExposedPorts':
return 'router';
case 'comment':
return 'chat';
case 'home':
return 'home';
case 'sources':
return 'link';
case 'keywords':
return 'receipt';
case 'name':
return 'abc';
case 'kubeVersion':
case 'appVersion':
return '123';
default:
if (attribute.startsWith('custom-label-')) {
return 'label';
@@ -210,6 +228,54 @@ export function truthy(value) {
return value === true || value === 'true';
}
/**
* only is false if explicitly set to boolean false or string 'false'.
* defaults to true in any other case, e.g. if empty.
*
* @param {string|boolean} value the input value to check
* @returns {boolean} false if explicity set, true otherwise
*/
export function falsy(value) {
return value !== false && value !== 'false';
}
export function stringToArray(value) {
return value && typeof value === 'string' ? value.split(',') : [];
}
const compareNumbers = (a, b) => {
const na = parseInt(a);
const nb = parseInt(b);
if (na > nb) return 1;
if (nb > na) return -1;
if (!isNaN(na) && isNaN(nb)) return 1;
if (isNaN(na) && !isNaN(nb)) return -1;
return 0;
};
export function isNewestVersion(current = '0.0.0', release = '0.0.0') {
if (current === release) {
return true;
}
current = current.split('.');
release = release.split('.');
const isDev = current[2].indexOf('-') >= 0;
const major = compareNumbers(current[0], release[0]);
const minor = compareNumbers(current[1], release[1]);
const patch = compareNumbers(current[2], release[2]);
if (!isDev && (major > 0 || (major === 0 && minor > 0) || (major === 0 && minor === 0 && patch >= 0))) {
return true;
} else if (isDev && (major > 0 || (major === 0 && minor > 0))) {
return true;
}
return false;
}
export function parseJSON(json) {
if (!json) {
return;
}
try {
return JSON.parse(json);
} catch (e) {}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Jones Magloire @Joxit
* Copyright (C) 2016-2023 Jones Magloire @Joxit
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -14,27 +14,32 @@
* 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/>.
*/
@import 'riot-mui/src/material-elements/material-navbar/material-navbar.scss';
@import 'riot-mui/src/material-elements/material-footer/material-footer.scss';
@import 'riot-mui/src/material-elements/material-card/material-card.scss';
@import 'riot-mui/src/material-elements/material-spinner/material-spinner.scss';
@import 'riot-mui/src/material-elements/material-button/material-button.scss';
@import 'riot-mui/src/material-elements/material-waves/material-waves.scss';
@import 'riot-mui/src/material-elements/material-checkbox/material-checkbox.scss';
@import 'riot-mui/src/material-elements/material-tabs/material-tabs.scss';
@import 'riot-mui/src/material-elements/material-snackbar/material-snackbar.scss';
@import 'riot-mui/src/material-elements/material-dropdown-list/material-dropdown-list.scss';
@import 'riot-mui/src/material-elements/material-popup/material-popup.scss';
@import 'riot-mui/src/material-elements/material-input/material-input.scss';
@use 'riot-mui/src/material-elements/material-navbar/material-navbar.scss';
@use 'riot-mui/src/material-elements/material-footer/material-footer.scss';
@use 'riot-mui/src/material-elements/material-card/material-card.scss';
@use 'riot-mui/src/material-elements/material-spinner/material-spinner.scss';
@use 'riot-mui/src/material-elements/material-button/material-button.scss';
@use 'riot-mui/src/material-elements/material-waves/material-waves.scss';
@use 'riot-mui/src/material-elements/material-checkbox/material-checkbox.scss';
@use 'riot-mui/src/material-elements/material-tabs/material-tabs.scss';
@use 'riot-mui/src/material-elements/material-snackbar/material-snackbar.scss';
@use 'riot-mui/src/material-elements/material-dropdown/material-dropdown.scss';
@use 'riot-mui/src/material-elements/material-popup/material-popup.scss';
@use 'riot-mui/src/material-elements/material-input/material-input.scss';
@use 'riot-mui/src/material-elements/material-switch/material-switch.scss';
@use './roboto.scss';
@use './material-symbols.scss';
@import './roboto.scss';
@import './material-icons.scss';
html > body {
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif !important;
font-size: 16px;
background-color: var(--background);
color: var(--primary-text);
}
html, body {
html,
body {
margin: 0;
height: 100%;
}
@@ -58,55 +63,42 @@ html, body {
text-decoration: none;
font-weight: inherit;
}
material-card, material-tabs, pagination .conatianer {
max-width: 95%;
material-card {
background-color: var(--background);
}
material-card,
material-tabs,
pagination .container {
margin: auto;
margin-top: 20px;
margin-bottom: 20px;
}
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 {
material-card,
material-tabs,
pagination .container,
catalog {
max-width: 95%;
/* 1515px * 0.95 = 1440px */
@media screen and (min-width: 1515px) {
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);
-webkit-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
-ms-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
-moz-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
-o-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 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;
}
@@ -117,23 +109,21 @@ material-spinner {
flex-direction: column;
}
material-navbar {
height: 64px;
}
material-navbar nav-wrapper {
display: flex;
}
.logo {
padding: 0 16px 0 72px;
text-decoration: none;
font-size: 20px;
line-height: 1;
letter-spacing: .02em;
letter-spacing: 0.02em;
font-weight: 400;
}
@media screen and (max-width: 600px) {
.logo {
padding-left: 0;
}
}
h2 {
padding: 16px;
margin: auto;
@@ -156,7 +146,7 @@ h2 {
}
.list.highlight:hover {
background-color: #eee;
background-color: rgba(0, 0, 0, 0.12);
cursor: pointer;
}
@@ -169,18 +159,22 @@ h2 {
overflow: hidden;
}
docker-registry-ui material-button > :first-child .content i.material-icons,
docker-registry-ui material-button > :first-child .content i.material-icons.material-icons {
font-size: 24px;
}
.list > span i.material-icons,
.list > li i.material-icons {
margin-right: 32px;
height: 24px;
width: 24px;
font-size: 24px;
box-sizing: border-box;
color: #757575;
color: var(--accent-text);
}
.list > span .right i.material-icons.animated {
transition: all 350ms cubic-bezier(.4,0,.2,1);
transition: all 350ms cubic-bezier(0.4, 0, 0.2, 1);
margin-right: 10px;
}
@@ -237,11 +231,10 @@ material-card table {
width: 100%;
border: none;
position: relative;
border: 1px solid rgba(0, 0, 0, .12);
border: 1px solid rgba(0, 0, 0, 0.12);
border-collapse: collapse;
white-space: nowrap;
font-size: 13px;
background-color: #fff;
border: none;
}
@@ -250,7 +243,7 @@ material-card table th {
vertical-align: bottom;
line-height: 24px;
height: 48px;
color: rgba(0, 0, 0, .54);
color: var(--accent-text);
box-sizing: border-box;
padding: 0 18px 12px 18px;
text-align: right;
@@ -260,17 +253,15 @@ material-card table th {
text-align: left;
}
material-card material-button:hover,
material-card table tbody tr:hover,
pagination material-button:hover {
background-color: #eee;
material-card .material-card-title-action material-button:hover button,
material-card table tbody tr:hover {
background-color: rgba(0, 0, 0, 0.12) !important;
}
material-card material-button,
material-card table tbody tr,
pagination material-button {
transition-duration: .28s;
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
material-button > :first-child[inverted='true'],
material-card table tbody tr {
transition-duration: 0.28s;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-property: background-color;
}
@@ -283,8 +274,8 @@ material-card table td {
font-size: 16px;
position: relative;
height: 48px;
border-top: 1px solid rgba(0, 0, 0, .12);
border-bottom: 1px solid rgba(0, 0, 0, .12);
border-top: 1px solid rgba(0, 0, 0, 0.12);
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
padding: 12px 18px;
box-sizing: border-box;
vertical-align: middle;
@@ -292,32 +283,35 @@ material-card table td {
}
tag-history-button button:hover,
material-card table th.material-card-th-sorted-ascending:hover, material-card table th.material-card-th-sorted-descending:hover {
material-card table th.material-card-th-sorted-ascending:hover,
material-card table th.material-card-th-sorted-descending:hover {
cursor: pointer;
}
material-card table th.material-card-th-sorted-ascending:hover:before, material-card table th.material-card-th-sorted-descending:hover:before {
color: rgba(0, 0, 0, .26);
material-card table th.material-card-th-sorted-ascending:hover:before,
material-card table th.material-card-th-sorted-descending:hover:before {
color: var(--primary-text);
}
material-card table th.material-card-th-sorted-ascending:before, material-card table th.material-card-th-sorted-descending:before {
font-family: 'Material Icons';
material-card table th.material-card-th-sorted-ascending:before,
material-card table th.material-card-th-sorted-descending:before {
font-family: 'Material Symbols Rounded';
font-weight: 400;
font-style: normal;
line-height: 1;
font-size: 16px;
content: "\e5d8";
content: '\e5d8';
margin-right: 5px;
vertical-align: sub;
}
material-card table th.material-card-th-sorted-descending:before {
content: "\e5db";
content: '\e5db';
}
material-button .content i.material-icons,
.material-icons {
color: #777;
color: var(--neutral-text);
}
material-button[disabled] .content i.material-icons,
@@ -329,19 +323,11 @@ material-snackbar .toast {
height: auto;
}
material-popup material-button,
pagination material-button {
background-color: #fff;
color: #000;
}
material-popup material-button:hover material-waves {
background-color: hsla(0, 0%, 75%, .2);
}
material-popup .popup {
material-popup .popup > .content {
padding: 1em;
max-width: 450px;
top: 2em;
background-color: var(--background);
color: var(--primary-text);
}
footer {
@@ -350,12 +336,11 @@ footer {
bottom: 0;
}
main {
min-height: calc(100% - 136px);
}
material-footer {
padding: 0.5em 1em;
li {
align-self: center;
}
}
.copy-to-clipboard {
@@ -366,7 +351,8 @@ material-footer {
/* 5 + 2 + 3 + 24 + 3 + 2 + 18 */
padding-right: 57px;
}
image-tag, .copy-to-clipboard {
image-tag,
.copy-to-clipboard {
display: inline-block;
}
image-content-digest {
@@ -401,32 +387,17 @@ taglist .creation-date {
width: 10em;
}
material-card td.creation-date,
material-card th.creation-date {
text-align: left;
max-width: 10em;
}
taglist .image-size {
width: 7em;
}
catalog material-card,
tag-history material-card {
min-height: auto;
}
tag-history-button button {
background: none;
border: none;
}
material-card material-button,
pagination material-button {
max-height: 30px;
max-width: 30px;
}
material-button:hover material-waves {
background: none;
}
material-card material-button,
pagination material-button {
material-card material-button {
background-color: inherit;
}
@@ -436,7 +407,7 @@ catalog-element material-card {
}
catalog-element catalog-element material-card {
transition: all 350ms cubic-bezier(.4,0,.2,1);
transition: all 350ms cubic-bezier(0.4, 0, 0.2, 1);
z-index: 1;
position: relative;
}
@@ -451,7 +422,7 @@ catalog-element catalog-element > .content {
margin-left: 3em;
}
@media screen and (min-width: 1515px){
@media screen and (min-width: 1515px) {
catalog-element catalog-element > .content material-card {
max-width: calc(1440px - 3em);
}
@@ -483,35 +454,9 @@ material-checkbox.indeterminate .checkbox.checked .checkmark {
}
material-checkbox .checkbox {
border-color: #777;
border-color: var(--neutral-text);
}
material-checkbox .checkbox.checked {
background-color: #777;
background-color: var(--neutral-text);
}
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;
}

53
test/docker-image.test.js Normal file
View File

@@ -0,0 +1,53 @@
import { supportListManifest, filterWrongManifests, platformToString } from '../src/scripts/docker-image.js';
import { dockerManifestList } from './fixtures/docker-manifest-list.js';
import { ociImageIndexLayer } from './fixtures/oci-image-index-layer.js';
import { ociImageIndexManifest } from './fixtures/oci-image-index-manifest.js';
import assert from 'assert';
describe('docker-image', () => {
describe('supportListManifest', () => {
/**
* Manifest of an image created with:
* docker buildx build --platform amd64,arm -t joxit/docker-registry-ui:buildx --push --provenance false .
*/
it('should support mediaType `application/vnd.docker.distribution.manifest.list.v2+json`', () => {
assert.ok(supportListManifest(dockerManifestList['application/vnd.docker.distribution.manifest.list.v2+json']));
});
/**
* Index of an image created with:
* docker buildx build --platform amd64,arm -t joxit/docker-registry-ui:buildx --push --provenance true .
*/
it('should support mediaType `application/vnd.oci.image.index.v1+json`', () => {
assert.ok(supportListManifest(ociImageIndexManifest['application/vnd.oci.image.index.v1+json']));
});
/**
* Index of an image created with:
* buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --export-cache type=registry,ref=joxit/docker-registry-ui:buildkit
*/
it('should not support mediaType `application/vnd.oci.image.index.v1+json` with layers (`application/vnd.oci.image.layer.v1.tar+gzip`)', () => {
assert.ok(!supportListManifest(ociImageIndexLayer['application/vnd.oci.image.index.v1+json']));
});
});
describe('supportListManifest', () => {
it('should return all manifests for `application/vnd.docker.distribution.manifest.list.v2+json`', () => {
assert.equal(
filterWrongManifests(dockerManifestList['application/vnd.docker.distribution.manifest.list.v2+json']).length,
2
);
});
it('should return all manifests for `application/vnd.oci.image.index.v1+json`', () => {
assert.equal(filterWrongManifests(ociImageIndexManifest['application/vnd.oci.image.index.v1+json']).length, 2);
});
});
describe('platformToString', () => {
it('should return unknown when the platform is not found', () => {
assert.equal(platformToString(), 'unknown');
assert.equal(platformToString({}), 'unknown');
});
it('should format the platform', () => {
assert.equal(platformToString({ os: 'linux', architecture: 'amd64' }), 'amd64');
assert.equal(platformToString({ os: 'linux', architecture: 'arm', variant: 'v7' }), 'armv7');
assert.equal(platformToString({ architecture: 'arm', variant: 'v7' }), 'armv7');
});
});
});

542
test/fixtures/docker-manifest-list.js vendored Normal file
View File

@@ -0,0 +1,542 @@
/**
* Manifest of an image created with:
* docker buildx build --platform amd64,arm -t joxit/docker-registry-ui:buildx --push --provenance false .
*/
const manifestList = {
'mediaType': 'application/vnd.docker.distribution.manifest.list.v2+json',
'schemaVersion': 2,
'manifests': [
{
'mediaType': 'application/vnd.docker.distribution.manifest.v2+json',
'digest': 'sha256:7d9bbfa92dbd2a9c09abe924ee7cb8f164be59f25b9457fa0c593a7110dba89f',
'size': 2850,
'platform': { 'architecture': 'amd64', 'os': 'linux' },
},
{
'mediaType': 'application/vnd.docker.distribution.manifest.v2+json',
'digest': 'sha256:5ef7a7d411a524beff05c9d1a541442ff78bb1ec6b45a55434dd3e51e00292b1',
'size': 2849,
'platform': { 'architecture': 'arm', 'os': 'linux', 'variant': 'v7' },
},
],
};
const manifestAmd64 = {
'mediaType': 'application/vnd.docker.distribution.manifest.v2+json',
'schemaVersion': 2,
'config': {
'mediaType': 'application/vnd.docker.container.image.v1+json',
'digest': 'sha256:7209907f3aa39f8b259069272274f185c4e9772ea7159722728b5f648c71eaad',
'size': 13772,
},
'layers': [
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:f56be85fc22e46face30e2c3de3f7fe7c15f8fd7c4e5add29d7f64b87abdaa09',
'size': 3374563,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:2ce963c369bc5690378d31c51dc575c7035f6adfcc1e286051b5a5d9a7b0cc5c',
'size': 1799036,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:59b9d2200e632e457f800814693b3a01adf09a244c38ebe8d3beef5c476c4c55',
'size': 626,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:3e1e579c95fece6bbe0cb9c8c2949512a3f8caaf9dbe6219dc6495abb9902040',
'size': 956,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:547a97583f72a32903ca1357d48fa302e91e8f83ffa18e0c40fd87adb5c06025',
'size': 773,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:1f21f983520d9a440d410ea62eb0bda61a2b50dd79878071181b56b82efa9ef3',
'size': 1404,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:c23b4f8cf279507bb1dd3d6eb2d15ca84fac9eac215ab5b529aa8b5a060294c8',
'size': 11607291,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1',
'size': 32,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:a5a652ffc299e3af7414a7c48a8b287785c0c70d7e7712cc81da43cfd18dc677',
'size': 1023,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:ac35a311bcf31486bf05fb0a0b66dd1fb313f8ac6b631c07662f5c77a017b142',
'size': 953,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:0bbe5bab7d19c248fd83f1d7746b306b1590118dcdab99cf49b72cec4f744a15',
'size': 878128,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:b516cdaec2158585780cfe13e196fbd6ee4e2aed81d1007903da60df2a6f8d12',
'size': 18327,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:c93af1909df12d053ee3b101b005c15312dedeee3d18c2f23a3aa3776693a0f5',
'size': 900629,
},
],
};
const manifestArm = {
'mediaType': 'application/vnd.docker.distribution.manifest.v2+json',
'schemaVersion': 2,
'config': {
'mediaType': 'application/vnd.docker.container.image.v1+json',
'digest': 'sha256:322f0eb73bbb441e1a0eae5dd05dfa56d7bc78b2be4682056463d919393b7d0b',
'size': 13773,
},
'layers': [
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:fd4b2aeb476b6c2c0c3049dae919de20fe09e90deac95e3181d717055cbe6707',
'size': 2868519,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:661c223f0b9c15a114f1a1cb1cfcfeea06544eb6d74e5d81bf239e3843d5963b',
'size': 1748335,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:4edc4a7923b338722943b7ca9ab57db06e501a300e63d2694830bce5a13a8719',
'size': 625,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:d04be6d2e446962d34ac7a76f347bf8a8bf8def411d2eb646241504f3cbc4835',
'size': 956,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:c4eb1daa4076033e2233b6aed11887d3e6b77fbf7910ced0673fe7f2e0c218b7',
'size': 770,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:8bdf974e3c33c7ab91a2c116867eb8007a255b8ffe7a8ff55344f0a4110eff9c',
'size': 1403,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:b157eb398be1a483b6d89235dc72a936bc9070e13e4ce1af9e3e8101ca37f78a',
'size': 9400541,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1',
'size': 32,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:c4b7c519f7157460cf3fdb5bc3ef2a503e6c011cd1afb07f9c4c92df1d0d5c98',
'size': 1023,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:5e59186ea6d831f79fb2d3b861ef2aac0e8610146f4ea86d8d0bfaff014ba168',
'size': 954,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:4df6c8febd43820b56a4a778f4ad551395c137c42cc4c9546e642df6f7eeef9c',
'size': 878130,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:d8b5650b9cc734d84c7f567264282cc58161d77a427c63c7380bfe8b35c916ae',
'size': 18322,
},
{
'mediaType': 'application/vnd.docker.image.rootfs.diff.tar.gzip',
'digest': 'sha256:803176948dabb2a0a106f58bfb423dea1da98bbe05e713649e39ef13dde1de6f',
'size': 900626,
},
],
};
const blobAmd64 = {
'architecture': 'amd64',
'config': {
'ExposedPorts': { '80/tcp': {} },
'Env': [
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
'NGINX_VERSION=1.23.4',
'PKG_RELEASE=1',
'NJS_VERSION=0.7.11',
'NGINX_PROXY_HEADER_Host=$http_host',
'NGINX_LISTEN_PORT=80',
'SHOW_CATALOG_NB_TAGS=false',
],
'Entrypoint': ['/docker-entrypoint.sh'],
'Cmd': ['nginx', '-g', 'daemon off;'],
'WorkingDir': '/usr/share/nginx/html/',
'Labels': { 'maintainer': 'Jones MAGLOIRE @Joxit' },
'StopSignal': 'SIGQUIT',
'OnBuild': null,
},
'created': '2023-05-16T17:53:59.778774465Z',
'history': [
{
'created': '2023-03-29T18:19:24.348438709Z',
'created_by': '/bin/sh -c #(nop) ADD file:9a4f77dfaba7fd2aa78186e4ef0e7486ad55101cefc1fabbc1b385601bb38920 in / ',
},
{
'created': '2023-03-29T18:19:24.45578926Z',
'created_by': '/bin/sh -c #(nop) CMD ["/bin/sh"]',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:10.826996048Z',
'created_by': '/bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:10.902296598Z',
'created_by': '/bin/sh -c #(nop) ENV NGINX_VERSION=1.23.4',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:10.975496976Z',
'created_by': '/bin/sh -c #(nop) ENV PKG_RELEASE=1',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:16.216540441Z',
'created_by':
'/bin/sh -c set -x \u0026\u0026 addgroup -g 101 -S nginx \u0026\u0026 adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make base \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache --virtual .gettext gettext \u0026\u0026 mv /usr/bin/envsubst /tmp/ \u0026\u0026 runDeps="$( scanelf --needed --nobanner /tmp/envsubst | awk \'{ gsub(/,/, "\\nso:", $2); print "so:" $2 }\' | sort -u | xargs -r apk info --installed | sort -u )" \u0026\u0026 apk add --no-cache $runDeps \u0026\u0026 apk del .gettext \u0026\u0026 mv /tmp/envsubst /usr/local/bin/ \u0026\u0026 apk add --no-cache tzdata \u0026\u0026 ln -sf /dev/stdout /var/log/nginx/access.log \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log \u0026\u0026 mkdir /docker-entrypoint.d',
},
{
'created': '2023-03-29T22:31:16.339435018Z',
'created_by':
'/bin/sh -c #(nop) COPY file:7b307b62e82255f040c9812421a30090bf9abf3685f27b02d77fcca99f997911 in / ',
},
{
'created': '2023-03-29T22:31:16.427781358Z',
'created_by':
'/bin/sh -c #(nop) COPY file:5c18272734349488bd0c94ec8d382c872c1a0a435cca13bd4671353d6021d2cb in /docker-entrypoint.d ',
},
{
'created': '2023-03-29T22:31:16.515135125Z',
'created_by':
'/bin/sh -c #(nop) COPY file:abbcbf84dc17ee4454b6b2e3cf914be88e02cf84d344ec45a5b31235379d722a in /docker-entrypoint.d ',
},
{
'created': '2023-03-29T22:31:16.601542115Z',
'created_by':
'/bin/sh -c #(nop) COPY file:e57eef017a414ca793499729d80a7b9075790c9a804f930f1417e56d506970cf in /docker-entrypoint.d ',
},
{
'created': '2023-03-29T22:31:16.678093757Z',
'created_by': '/bin/sh -c #(nop) ENTRYPOINT ["/docker-entrypoint.sh"]',
'empty_layer': true,
},
{ 'created': '2023-03-29T22:31:16.756398749Z', 'created_by': '/bin/sh -c #(nop) EXPOSE 80', 'empty_layer': true },
{
'created': '2023-03-29T22:31:16.839607817Z',
'created_by': '/bin/sh -c #(nop) STOPSIGNAL SIGQUIT',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:16.921795894Z',
'created_by': '/bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"]',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:30.252298671Z',
'created_by': '/bin/sh -c #(nop) ENV NJS_VERSION=0.7.11',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:35.828698308Z',
'created_by':
'/bin/sh -c set -x \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers libxslt-dev gd-dev geoip-dev libedit-dev bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make module-geoip module-image-filter module-njs module-xslt \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache curl ca-certificates',
},
{
'created': '2023-05-16T17:53:59.527106164Z',
'created_by': 'LABEL maintainer=Jones MAGLOIRE @Joxit',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.527106164Z',
'created_by': 'WORKDIR /usr/share/nginx/html/',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.527106164Z',
'created_by': 'ENV NGINX_PROXY_HEADER_Host=$http_host',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.527106164Z',
'created_by': 'ENV NGINX_LISTEN_PORT=80',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.527106164Z',
'created_by': 'ENV SHOW_CATALOG_NB_TAGS=false',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.549089552Z',
'created_by': 'COPY nginx/default.conf /etc/nginx/conf.d/default.conf # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.605792395Z',
'created_by': 'COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.627224324Z',
'created_by': 'COPY dist/ /usr/share/nginx/html/ # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.644817137Z',
'created_by': 'COPY favicon.ico /usr/share/nginx/html/ # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.778774465Z',
'created_by':
'RUN /bin/sh -c chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
],
'moby.buildkit.buildinfo.v1':
'eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvbmdpbng6YWxwaW5lIiwicGluIjoic2hhMjU2OjAyZmZkNDM5YjcxZDllYTk0MDhlNDQ5YjU2OGY2NWMwYmJiYjk0YmViZDg3NTBmMWQ4MDIzMWFiNjQ5NjAwOGUifV19',
'os': 'linux',
'rootfs': {
'type': 'layers',
'diff_ids': [
'sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5',
'sha256:1003ff723696bfd596cd65592fa26554840e90780f6937e6ddccc909b8ed1443',
'sha256:1d54586a1706c0af48668c10cbd8246626acb4fec01287be54cd9b26d72df15d',
'sha256:c1cd5c8c68ef2336b2504336206d58931e9215a863a35a741f66aa3f4970b0f5',
'sha256:f0fb842dea4179a94f1b8c2ac178e72690fa2b30e25e03a7a7893794fe9520a5',
'sha256:f9cb3f1f1d3d7c591c4ab02118816fe6761a8f2f7b2500a5ec7421a42b8a5ea2',
'sha256:31531248c7cbf5b31a8d9695c20041b9b3749b8c04b9831331ad93333fcf1474',
'sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef',
'sha256:d2a2fde0ccbec9f84533eba6eae08e61456eae22cdb18cbd6770584a96de079c',
'sha256:417b58457dd524a2ab48d3b4d124910aaff0680035f71816ee6efb5fb08c784a',
'sha256:a8ecc6cb361d80fdf6d9a3149dcd7d3042cf1b26b45a6e591032033c98848de9',
'sha256:f5fe999227158f3d3649ac32585d981b74774e9d3b8f8254104395470753c751',
'sha256:3eaed6821acc2fcc0f298984d83dd9ca0317b028ed4a5b1cdcb58d4e1c6aec74',
],
},
};
const blobArm = {
'architecture': 'arm',
'config': {
'ExposedPorts': { '80/tcp': {} },
'Env': [
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
'NGINX_VERSION=1.23.4',
'PKG_RELEASE=1',
'NJS_VERSION=0.7.11',
'NGINX_PROXY_HEADER_Host=$http_host',
'NGINX_LISTEN_PORT=80',
'SHOW_CATALOG_NB_TAGS=false',
],
'Entrypoint': ['/docker-entrypoint.sh'],
'Cmd': ['nginx', '-g', 'daemon off;'],
'WorkingDir': '/usr/share/nginx/html/',
'Labels': { 'maintainer': 'Jones MAGLOIRE @Joxit' },
'StopSignal': 'SIGQUIT',
'OnBuild': null,
},
'created': '2023-05-18T06:18:36.698896035Z',
'history': [
{
'created': '2023-03-29T18:03:38.84773971Z',
'created_by': '/bin/sh -c #(nop) ADD file:959fa0ffb60c37c4fdc0d32ac31511f8dead32ef7f4bd848b11ff144a6b37012 in / ',
},
{
'created': '2023-03-29T18:03:38.9602307Z',
'created_by': '/bin/sh -c #(nop) CMD ["/bin/sh"]',
'empty_layer': true,
},
{
'created': '2023-03-29T19:27:03.894972841Z',
'created_by': '/bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e',
'empty_layer': true,
},
{
'created': '2023-03-29T19:27:03.977068216Z',
'created_by': '/bin/sh -c #(nop) ENV NGINX_VERSION=1.23.4',
'empty_layer': true,
},
{
'created': '2023-03-29T19:27:04.093650581Z',
'created_by': '/bin/sh -c #(nop) ENV PKG_RELEASE=1',
'empty_layer': true,
},
{
'created': '2023-03-29T19:27:44.532352816Z',
'created_by':
'/bin/sh -c set -x \u0026\u0026 addgroup -g 101 -S nginx \u0026\u0026 adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make base \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache --virtual .gettext gettext \u0026\u0026 mv /usr/bin/envsubst /tmp/ \u0026\u0026 runDeps="$( scanelf --needed --nobanner /tmp/envsubst | awk \'{ gsub(/,/, "\\nso:", $2); print "so:" $2 }\' | sort -u | xargs -r apk info --installed | sort -u )" \u0026\u0026 apk add --no-cache $runDeps \u0026\u0026 apk del .gettext \u0026\u0026 mv /tmp/envsubst /usr/local/bin/ \u0026\u0026 apk add --no-cache tzdata \u0026\u0026 ln -sf /dev/stdout /var/log/nginx/access.log \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log \u0026\u0026 mkdir /docker-entrypoint.d',
},
{
'created': '2023-03-29T19:27:44.660812167Z',
'created_by':
'/bin/sh -c #(nop) COPY file:7b307b62e82255f040c9812421a30090bf9abf3685f27b02d77fcca99f997911 in / ',
},
{
'created': '2023-03-29T19:27:44.758007971Z',
'created_by':
'/bin/sh -c #(nop) COPY file:5c18272734349488bd0c94ec8d382c872c1a0a435cca13bd4671353d6021d2cb in /docker-entrypoint.d ',
},
{
'created': '2023-03-29T19:27:44.849497713Z',
'created_by':
'/bin/sh -c #(nop) COPY file:abbcbf84dc17ee4454b6b2e3cf914be88e02cf84d344ec45a5b31235379d722a in /docker-entrypoint.d ',
},
{
'created': '2023-03-29T19:27:44.941848809Z',
'created_by':
'/bin/sh -c #(nop) COPY file:e57eef017a414ca793499729d80a7b9075790c9a804f930f1417e56d506970cf in /docker-entrypoint.d ',
},
{
'created': '2023-03-29T19:27:45.021738784Z',
'created_by': '/bin/sh -c #(nop) ENTRYPOINT ["/docker-entrypoint.sh"]',
'empty_layer': true,
},
{ 'created': '2023-03-29T19:27:45.104459469Z', 'created_by': '/bin/sh -c #(nop) EXPOSE 80', 'empty_layer': true },
{
'created': '2023-03-29T19:27:45.186875746Z',
'created_by': '/bin/sh -c #(nop) STOPSIGNAL SIGQUIT',
'empty_layer': true,
},
{
'created': '2023-03-29T19:27:45.267774313Z',
'created_by': '/bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"]',
'empty_layer': true,
},
{
'created': '2023-03-29T19:29:39.602231315Z',
'created_by': '/bin/sh -c #(nop) ENV NJS_VERSION=0.7.11',
'empty_layer': true,
},
{
'created': '2023-03-29T19:31:21.652098356Z',
'created_by':
'/bin/sh -c set -x \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers libxslt-dev gd-dev geoip-dev libedit-dev bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make module-geoip module-image-filter module-njs module-xslt \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache curl ca-certificates',
},
{
'created': '2023-05-18T06:18:36.4191662Z',
'created_by': 'LABEL maintainer=Jones MAGLOIRE @Joxit',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-18T06:18:36.4191662Z',
'created_by': 'WORKDIR /usr/share/nginx/html/',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-18T06:18:36.4191662Z',
'created_by': 'ENV NGINX_PROXY_HEADER_Host=$http_host',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-18T06:18:36.4191662Z',
'created_by': 'ENV NGINX_LISTEN_PORT=80',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-18T06:18:36.4191662Z',
'created_by': 'ENV SHOW_CATALOG_NB_TAGS=false',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-18T06:18:36.433173058Z',
'created_by': 'COPY nginx/default.conf /etc/nginx/conf.d/default.conf # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-18T06:18:36.446926669Z',
'created_by': 'COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-18T06:18:36.464433643Z',
'created_by': 'COPY dist/ /usr/share/nginx/html/ # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-18T06:18:36.479417246Z',
'created_by': 'COPY favicon.ico /usr/share/nginx/html/ # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-18T06:18:36.698896035Z',
'created_by':
'RUN /bin/sh -c chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
],
'moby.buildkit.buildinfo.v1':
'eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvbmdpbng6YWxwaW5lIiwicGluIjoic2hhMjU2OjAyZmZkNDM5YjcxZDllYTk0MDhlNDQ5YjU2OGY2NWMwYmJiYjk0YmViZDg3NTBmMWQ4MDIzMWFiNjQ5NjAwOGUifV19',
'os': 'linux',
'rootfs': {
'type': 'layers',
'diff_ids': [
'sha256:0b9ff86f9940609d912c3e621dd6adad477087fbfc4c31c61d654a22a0f11b61',
'sha256:f60190c8cdc262062bd7d42a07cadde6791c8449f3421dff1223bfc36a9caf82',
'sha256:e327072a6a7f45d0af7c04f57d4729a7562f5aaca9377f0ffab6d9fd120f6ec5',
'sha256:c26ebf2a190d73b49a5e7bb779f33448d5fbf1a0a36236510621b758790ae793',
'sha256:6af30f71b3afff8b63313da78bda5d7b7fee2216be594cb2fc7f19a3ce69a14a',
'sha256:2bfd4c9ec145aaf73c515d20d10f1f03e7043c191134b7bcbb75013f016c68fc',
'sha256:b1f07242859d323324ee74a7221806532d71a0cb52f1463e9c1f661d4293fabb',
'sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef',
'sha256:00fd9ea6d2874d964d55c800e9ea9f812c9b6a4e8e342990f79c317b96545495',
'sha256:90eeeac365c62a487016298dfd4dae3aef0662f243b2a6f8030c0a3cefebf0aa',
'sha256:684c2527abcba7d7b72bef753728e030093aa6ac748e0d46034c94dc50bc8780',
'sha256:2e0de70327c6edd8680cacf8213f2dfbae8da5f2e3da03765046c220c4e56b88',
'sha256:282631fc20d0d4585c4ff9b37ffe0bd7b9e34834c9902d1f7934ce07fe3264a3',
],
},
'variant': 'v7',
};
export const dockerManifestList = {
'application/vnd.docker.distribution.manifest.list.v2+json': manifestList,
manifestList,
'sha256:7d9bbfa92dbd2a9c09abe924ee7cb8f164be59f25b9457fa0c593a7110dba89f': manifestAmd64,
manifestAmd64,
'sha256:5ef7a7d411a524beff05c9d1a541442ff78bb1ec6b45a55434dd3e51e00292b1': manifestArm,
manifestArm,
'sha256:7209907f3aa39f8b259069272274f185c4e9772ea7159722728b5f648c71eaad': blobAmd64,
blobAmd64,
'sha256:322f0eb73bbb441e1a0eae5dd05dfa56d7bc78b2be4682056463d919393b7d0b': blobArm,
blobArm,
};

137
test/fixtures/oci-image-index-layer.js vendored Normal file
View File

@@ -0,0 +1,137 @@
/**
* Index of an image created with:
* buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --export-cache type=registry,ref=joxit/docker-registry-ui:buildkit
*/
const imageIndex = {
'schemaVersion': 2,
'mediaType': 'application/vnd.oci.image.index.v1+json',
'manifests': [
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:1f21f983520d9a440d410ea62eb0bda61a2b50dd79878071181b56b82efa9ef3',
'size': 1404,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:52.396770851+02:00',
'containerd.io/uncompressed': 'sha256:f9cb3f1f1d3d7c591c4ab02118816fe6761a8f2f7b2500a5ec7421a42b8a5ea2',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:232b62d5fcb5fc034d54170fd0e93e62b4f0eb357b23f99fc70edfd514e68688',
'size': 878138,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:53.358118651+02:00',
'containerd.io/uncompressed': 'sha256:da6ab5dba3f3f7a67cee1b384e5c3e76dc6daffe31c8da7d7b8944b010146deb',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:2ce963c369bc5690378d31c51dc575c7035f6adfcc1e286051b5a5d9a7b0cc5c',
'size': 1799036,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:52.374638832+02:00',
'containerd.io/uncompressed': 'sha256:1003ff723696bfd596cd65592fa26554840e90780f6937e6ddccc909b8ed1443',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:3e1e579c95fece6bbe0cb9c8c2949512a3f8caaf9dbe6219dc6495abb9902040',
'size': 956,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:52.386713549+02:00',
'containerd.io/uncompressed': 'sha256:c1cd5c8c68ef2336b2504336206d58931e9215a863a35a741f66aa3f4970b0f5',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1',
'size': 32,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:53.299566674+02:00',
'containerd.io/uncompressed': 'sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:547a97583f72a32903ca1357d48fa302e91e8f83ffa18e0c40fd87adb5c06025',
'size': 773,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:52.392007164+02:00',
'containerd.io/uncompressed': 'sha256:f0fb842dea4179a94f1b8c2ac178e72690fa2b30e25e03a7a7893794fe9520a5',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:59b9d2200e632e457f800814693b3a01adf09a244c38ebe8d3beef5c476c4c55',
'size': 626,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:52.382010266+02:00',
'containerd.io/uncompressed': 'sha256:1d54586a1706c0af48668c10cbd8246626acb4fec01287be54cd9b26d72df15d',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:91a97e9af03b8531316a2f1418c03e329d0d1e261d6e193435263f3c7506d9ea',
'size': 18328,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:53.375505685+02:00',
'containerd.io/uncompressed': 'sha256:233ce4876ce17521a29b279dfa36dc690a85faca59b64ee5b8b7aa2d1f89dfa9',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:9b5968b53397e19a4a0ba7e49f0f29fa274fe0bdb4827720a69b50a9241f7281',
'size': 900650,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:53.54931788+02:00',
'containerd.io/uncompressed': 'sha256:9cf4861c81b63e36381c165dfac7c6f44877ec30d562f401b49a625e059f34d6',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:c23b4f8cf279507bb1dd3d6eb2d15ca84fac9eac215ab5b529aa8b5a060294c8',
'size': 11607291,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:52.401595965+02:00',
'containerd.io/uncompressed': 'sha256:31531248c7cbf5b31a8d9695c20041b9b3749b8c04b9831331ad93333fcf1474',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:efc25abc67f8fa9710cbfbb82588504708f18bca00a2a8a6fcc09fc71e72f10b',
'size': 1023,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:53.324178797+02:00',
'containerd.io/uncompressed': 'sha256:2596c2188541b87815b21a34838862ca037720785f6e150fd410213555c4aab1',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:f56be85fc22e46face30e2c3de3f7fe7c15f8fd7c4e5add29d7f64b87abdaa09',
'size': 3374563,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:52.358761758+02:00',
'containerd.io/uncompressed': 'sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5',
},
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:fd6b87e430ca8d0742c0cae2e0bd472ae024851526a39e3a7fe6dd15fed45224',
'size': 954,
'annotations': {
'buildkit/createdat': '2023-05-17T22:25:53.33927092+02:00',
'containerd.io/uncompressed': 'sha256:f04b4697ffd252d7a0080dcc83c0790e9962db6cf74d3450960e03e16cf1e747',
},
},
{
'mediaType': 'application/vnd.buildkit.cacheconfig.v0',
'digest': 'sha256:b7d18f694c1dcf673bc8e56dbdc05a3441a0ae000f05dbc3f34f42ac9f060ef9',
'size': 2908,
},
],
};
export const ociImageIndexLayer = {
'application/vnd.oci.image.index.v1+json': imageIndex,
imageIndex,
};

View File

@@ -0,0 +1,561 @@
/**
* Manifest of an image created with:
* docker buildx build --platform amd64,arm -t joxit/docker-registry-ui:buildx --push --provenance true .
*/
const imageIndex = {
'mediaType': 'application/vnd.oci.image.index.v1+json',
'schemaVersion': 2,
'manifests': [
{
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
'digest': 'sha256:868d96eea2ab3b0905caa746339541cef30ed4e0864da7f89e423cb50aee7857',
'size': 2756,
'platform': { 'architecture': 'amd64', 'os': 'linux' },
},
{
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
'digest': 'sha256:ee45307ae7404ccfbe4536677095b1ad1258a261c79ecdf5640d24bec66e1257',
'size': 2756,
'platform': { 'architecture': 'arm64', 'os': 'linux' },
},
{
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
'digest': 'sha256:2d0c98dae3c1936ef04d00554f9b2557cbdd1f2aa84226758fa77e84a7326f98',
'size': 566,
'annotations': {
'vnd.docker.reference.digest': 'sha256:868d96eea2ab3b0905caa746339541cef30ed4e0864da7f89e423cb50aee7857',
'vnd.docker.reference.type': 'attestation-manifest',
},
'platform': { 'architecture': 'unknown', 'os': 'unknown' },
},
{
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
'digest': 'sha256:f239e839c36a0fc3bd9d4a6152d7a83522002bfbf09836f0a3b20613afe27d97',
'size': 566,
'annotations': {
'vnd.docker.reference.digest': 'sha256:ee45307ae7404ccfbe4536677095b1ad1258a261c79ecdf5640d24bec66e1257',
'vnd.docker.reference.type': 'attestation-manifest',
},
'platform': { 'architecture': 'unknown', 'os': 'unknown' },
},
],
};
const manifestAmd64 = {
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
'schemaVersion': 2,
'config': {
'mediaType': 'application/vnd.oci.image.config.v1+json',
'digest': 'sha256:7209907f3aa39f8b259069272274f185c4e9772ea7159722728b5f648c71eaad',
'size': 13772,
},
'layers': [
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:f56be85fc22e46face30e2c3de3f7fe7c15f8fd7c4e5add29d7f64b87abdaa09',
'size': 3374563,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:2ce963c369bc5690378d31c51dc575c7035f6adfcc1e286051b5a5d9a7b0cc5c',
'size': 1799036,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:59b9d2200e632e457f800814693b3a01adf09a244c38ebe8d3beef5c476c4c55',
'size': 626,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:3e1e579c95fece6bbe0cb9c8c2949512a3f8caaf9dbe6219dc6495abb9902040',
'size': 956,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:547a97583f72a32903ca1357d48fa302e91e8f83ffa18e0c40fd87adb5c06025',
'size': 773,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:1f21f983520d9a440d410ea62eb0bda61a2b50dd79878071181b56b82efa9ef3',
'size': 1404,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:c23b4f8cf279507bb1dd3d6eb2d15ca84fac9eac215ab5b529aa8b5a060294c8',
'size': 11607291,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1',
'size': 32,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:a5a652ffc299e3af7414a7c48a8b287785c0c70d7e7712cc81da43cfd18dc677',
'size': 1023,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:ac35a311bcf31486bf05fb0a0b66dd1fb313f8ac6b631c07662f5c77a017b142',
'size': 953,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:0bbe5bab7d19c248fd83f1d7746b306b1590118dcdab99cf49b72cec4f744a15',
'size': 878128,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:b516cdaec2158585780cfe13e196fbd6ee4e2aed81d1007903da60df2a6f8d12',
'size': 18327,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:c93af1909df12d053ee3b101b005c15312dedeee3d18c2f23a3aa3776693a0f5',
'size': 900629,
},
],
};
const manifestArm = {
'mediaType': 'application/vnd.oci.image.manifest.v1+json',
'schemaVersion': 2,
'config': {
'mediaType': 'application/vnd.oci.image.config.v1+json',
'digest': 'sha256:6d0a94a37f413ae834a226070fb042386303fd80ac79c6d4a12e986a03416710',
'size': 13771,
},
'layers': [
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:c41833b44d910632b415cd89a9cdaa4d62c9725dc56c99a7ddadafd6719960f9',
'size': 3261854,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:2c2c9b85ac58c9f389d42b1033672337110dba86c12d1b0d5c7c384a7cfe110b',
'size': 1788526,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:40f94fa3619489012a181c2b217548ea718fe485578eec4afdef4b14b3bc536e',
'size': 624,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:ae26f20697dc7e3b86701a83a1ed42b81b1755f0763130d7f6f816a39adaf388',
'size': 956,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:e4fa283fba0e8150c05ba453aed98ff4f4bdd65a6248837101fc16b489d1101e',
'size': 770,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:4c53b6cdc37bcca61cf31d3308b58fda6d7d3192ddd56559cca2f67eafcb0cc1',
'size': 1403,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:7bcac465295e8cfefa26d0ad33a638a0415ad7c4e1afba500b9633f97e277c3c',
'size': 11108102,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1',
'size': 32,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:7923ad4ef26285d800ddad2f7a82d428d035aafd6d6029c4bb23b2b8ac53f699',
'size': 1026,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:ac35a311bcf31486bf05fb0a0b66dd1fb313f8ac6b631c07662f5c77a017b142',
'size': 953,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:cb284b84c5d00c523b11f6fa3ee3886f20b146a0f6bc70160bc6e43a3a525dcc',
'size': 878125,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:ed35f24b7cbe4d56ab109eb77be92a68460b8f932b2a42ddde9296ee7805070a',
'size': 18329,
},
{
'mediaType': 'application/vnd.oci.image.layer.v1.tar+gzip',
'digest': 'sha256:ea6ae72ae0bc46efb9381c62a0c9f01b7d1ad2cf67dc3ec58ef8b4ff91a145f1',
'size': 900638,
},
],
};
const blobAmd64 = {
'architecture': 'amd64',
'config': {
'ExposedPorts': { '80/tcp': {} },
'Env': [
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
'NGINX_VERSION=1.23.4',
'PKG_RELEASE=1',
'NJS_VERSION=0.7.11',
'NGINX_PROXY_HEADER_Host=$http_host',
'NGINX_LISTEN_PORT=80',
'SHOW_CATALOG_NB_TAGS=false',
],
'Entrypoint': ['/docker-entrypoint.sh'],
'Cmd': ['nginx', '-g', 'daemon off;'],
'WorkingDir': '/usr/share/nginx/html/',
'Labels': { 'maintainer': 'Jones MAGLOIRE @Joxit' },
'StopSignal': 'SIGQUIT',
'OnBuild': null,
},
'created': '2023-05-16T17:53:59.778774465Z',
'history': [
{
'created': '2023-03-29T18:19:24.348438709Z',
'created_by': '/bin/sh -c #(nop) ADD file:9a4f77dfaba7fd2aa78186e4ef0e7486ad55101cefc1fabbc1b385601bb38920 in / ',
},
{
'created': '2023-03-29T18:19:24.45578926Z',
'created_by': '/bin/sh -c #(nop) CMD ["/bin/sh"]',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:10.826996048Z',
'created_by': '/bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:10.902296598Z',
'created_by': '/bin/sh -c #(nop) ENV NGINX_VERSION=1.23.4',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:10.975496976Z',
'created_by': '/bin/sh -c #(nop) ENV PKG_RELEASE=1',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:16.216540441Z',
'created_by':
'/bin/sh -c set -x \u0026\u0026 addgroup -g 101 -S nginx \u0026\u0026 adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make base \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache --virtual .gettext gettext \u0026\u0026 mv /usr/bin/envsubst /tmp/ \u0026\u0026 runDeps="$( scanelf --needed --nobanner /tmp/envsubst | awk \'{ gsub(/,/, "\\nso:", $2); print "so:" $2 }\' | sort -u | xargs -r apk info --installed | sort -u )" \u0026\u0026 apk add --no-cache $runDeps \u0026\u0026 apk del .gettext \u0026\u0026 mv /tmp/envsubst /usr/local/bin/ \u0026\u0026 apk add --no-cache tzdata \u0026\u0026 ln -sf /dev/stdout /var/log/nginx/access.log \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log \u0026\u0026 mkdir /docker-entrypoint.d',
},
{
'created': '2023-03-29T22:31:16.339435018Z',
'created_by':
'/bin/sh -c #(nop) COPY file:7b307b62e82255f040c9812421a30090bf9abf3685f27b02d77fcca99f997911 in / ',
},
{
'created': '2023-03-29T22:31:16.427781358Z',
'created_by':
'/bin/sh -c #(nop) COPY file:5c18272734349488bd0c94ec8d382c872c1a0a435cca13bd4671353d6021d2cb in /docker-entrypoint.d ',
},
{
'created': '2023-03-29T22:31:16.515135125Z',
'created_by':
'/bin/sh -c #(nop) COPY file:abbcbf84dc17ee4454b6b2e3cf914be88e02cf84d344ec45a5b31235379d722a in /docker-entrypoint.d ',
},
{
'created': '2023-03-29T22:31:16.601542115Z',
'created_by':
'/bin/sh -c #(nop) COPY file:e57eef017a414ca793499729d80a7b9075790c9a804f930f1417e56d506970cf in /docker-entrypoint.d ',
},
{
'created': '2023-03-29T22:31:16.678093757Z',
'created_by': '/bin/sh -c #(nop) ENTRYPOINT ["/docker-entrypoint.sh"]',
'empty_layer': true,
},
{ 'created': '2023-03-29T22:31:16.756398749Z', 'created_by': '/bin/sh -c #(nop) EXPOSE 80', 'empty_layer': true },
{
'created': '2023-03-29T22:31:16.839607817Z',
'created_by': '/bin/sh -c #(nop) STOPSIGNAL SIGQUIT',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:16.921795894Z',
'created_by': '/bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"]',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:30.252298671Z',
'created_by': '/bin/sh -c #(nop) ENV NJS_VERSION=0.7.11',
'empty_layer': true,
},
{
'created': '2023-03-29T22:31:35.828698308Z',
'created_by':
'/bin/sh -c set -x \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers libxslt-dev gd-dev geoip-dev libedit-dev bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make module-geoip module-image-filter module-njs module-xslt \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache curl ca-certificates',
},
{
'created': '2023-05-16T17:53:59.527106164Z',
'created_by': 'LABEL maintainer=Jones MAGLOIRE @Joxit',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.527106164Z',
'created_by': 'WORKDIR /usr/share/nginx/html/',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.527106164Z',
'created_by': 'ENV NGINX_PROXY_HEADER_Host=$http_host',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.527106164Z',
'created_by': 'ENV NGINX_LISTEN_PORT=80',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.527106164Z',
'created_by': 'ENV SHOW_CATALOG_NB_TAGS=false',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.549089552Z',
'created_by': 'COPY nginx/default.conf /etc/nginx/conf.d/default.conf # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.605792395Z',
'created_by': 'COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.627224324Z',
'created_by': 'COPY dist/ /usr/share/nginx/html/ # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.644817137Z',
'created_by': 'COPY favicon.ico /usr/share/nginx/html/ # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.778774465Z',
'created_by':
'RUN /bin/sh -c chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
],
'moby.buildkit.buildinfo.v1':
'eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvbmdpbng6YWxwaW5lIiwicGluIjoic2hhMjU2OjAyZmZkNDM5YjcxZDllYTk0MDhlNDQ5YjU2OGY2NWMwYmJiYjk0YmViZDg3NTBmMWQ4MDIzMWFiNjQ5NjAwOGUifV19',
'os': 'linux',
'rootfs': {
'type': 'layers',
'diff_ids': [
'sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5',
'sha256:1003ff723696bfd596cd65592fa26554840e90780f6937e6ddccc909b8ed1443',
'sha256:1d54586a1706c0af48668c10cbd8246626acb4fec01287be54cd9b26d72df15d',
'sha256:c1cd5c8c68ef2336b2504336206d58931e9215a863a35a741f66aa3f4970b0f5',
'sha256:f0fb842dea4179a94f1b8c2ac178e72690fa2b30e25e03a7a7893794fe9520a5',
'sha256:f9cb3f1f1d3d7c591c4ab02118816fe6761a8f2f7b2500a5ec7421a42b8a5ea2',
'sha256:31531248c7cbf5b31a8d9695c20041b9b3749b8c04b9831331ad93333fcf1474',
'sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef',
'sha256:d2a2fde0ccbec9f84533eba6eae08e61456eae22cdb18cbd6770584a96de079c',
'sha256:417b58457dd524a2ab48d3b4d124910aaff0680035f71816ee6efb5fb08c784a',
'sha256:a8ecc6cb361d80fdf6d9a3149dcd7d3042cf1b26b45a6e591032033c98848de9',
'sha256:f5fe999227158f3d3649ac32585d981b74774e9d3b8f8254104395470753c751',
'sha256:3eaed6821acc2fcc0f298984d83dd9ca0317b028ed4a5b1cdcb58d4e1c6aec74',
],
},
};
const blobArm = {
'architecture': 'arm64',
'config': {
'ExposedPorts': { '80/tcp': {} },
'Env': [
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
'NGINX_VERSION=1.23.4',
'PKG_RELEASE=1',
'NJS_VERSION=0.7.11',
'NGINX_PROXY_HEADER_Host=$http_host',
'NGINX_LISTEN_PORT=80',
'SHOW_CATALOG_NB_TAGS=false',
],
'Entrypoint': ['/docker-entrypoint.sh'],
'Cmd': ['nginx', '-g', 'daemon off;'],
'WorkingDir': '/usr/share/nginx/html/',
'Labels': { 'maintainer': 'Jones MAGLOIRE @Joxit' },
'StopSignal': 'SIGQUIT',
'OnBuild': null,
},
'created': '2023-05-16T17:54:00.037166004Z',
'history': [
{
'created': '2023-03-29T17:39:18.063622104Z',
'created_by': '/bin/sh -c #(nop) ADD file:e51d4089e73ad6dee52b31f0c8059a00c17df6e23f6741fe11b43bd84cc99008 in / ',
},
{
'created': '2023-03-29T17:39:18.167879762Z',
'created_by': '/bin/sh -c #(nop) CMD ["/bin/sh"]',
'empty_layer': true,
},
{
'created': '2023-03-30T04:22:48.660484906Z',
'created_by': '/bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e',
'empty_layer': true,
},
{
'created': '2023-03-30T04:22:48.736880947Z',
'created_by': '/bin/sh -c #(nop) ENV NGINX_VERSION=1.23.4',
'empty_layer': true,
},
{
'created': '2023-03-30T04:22:48.811177358Z',
'created_by': '/bin/sh -c #(nop) ENV PKG_RELEASE=1',
'empty_layer': true,
},
{
'created': '2023-03-30T04:22:53.381662136Z',
'created_by':
'/bin/sh -c set -x \u0026\u0026 addgroup -g 101 -S nginx \u0026\u0026 adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make base \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache --virtual .gettext gettext \u0026\u0026 mv /usr/bin/envsubst /tmp/ \u0026\u0026 runDeps="$( scanelf --needed --nobanner /tmp/envsubst | awk \'{ gsub(/,/, "\\nso:", $2); print "so:" $2 }\' | sort -u | xargs -r apk info --installed | sort -u )" \u0026\u0026 apk add --no-cache $runDeps \u0026\u0026 apk del .gettext \u0026\u0026 mv /tmp/envsubst /usr/local/bin/ \u0026\u0026 apk add --no-cache tzdata \u0026\u0026 ln -sf /dev/stdout /var/log/nginx/access.log \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log \u0026\u0026 mkdir /docker-entrypoint.d',
},
{
'created': '2023-03-30T04:22:53.48126766Z',
'created_by':
'/bin/sh -c #(nop) COPY file:7b307b62e82255f040c9812421a30090bf9abf3685f27b02d77fcca99f997911 in / ',
},
{
'created': '2023-03-30T04:22:53.552453825Z',
'created_by':
'/bin/sh -c #(nop) COPY file:5c18272734349488bd0c94ec8d382c872c1a0a435cca13bd4671353d6021d2cb in /docker-entrypoint.d ',
},
{
'created': '2023-03-30T04:22:53.624623412Z',
'created_by':
'/bin/sh -c #(nop) COPY file:abbcbf84dc17ee4454b6b2e3cf914be88e02cf84d344ec45a5b31235379d722a in /docker-entrypoint.d ',
},
{
'created': '2023-03-30T04:22:53.696466901Z',
'created_by':
'/bin/sh -c #(nop) COPY file:e57eef017a414ca793499729d80a7b9075790c9a804f930f1417e56d506970cf in /docker-entrypoint.d ',
},
{
'created': '2023-03-30T04:22:53.773577427Z',
'created_by': '/bin/sh -c #(nop) ENTRYPOINT ["/docker-entrypoint.sh"]',
'empty_layer': true,
},
{ 'created': '2023-03-30T04:22:53.855706883Z', 'created_by': '/bin/sh -c #(nop) EXPOSE 80', 'empty_layer': true },
{
'created': '2023-03-30T04:22:53.934315121Z',
'created_by': '/bin/sh -c #(nop) STOPSIGNAL SIGQUIT',
'empty_layer': true,
},
{
'created': '2023-03-30T04:22:54.010915592Z',
'created_by': '/bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"]',
'empty_layer': true,
},
{
'created': '2023-03-30T04:23:05.898706916Z',
'created_by': '/bin/sh -c #(nop) ENV NJS_VERSION=0.7.11',
'empty_layer': true,
},
{
'created': '2023-03-30T04:23:11.319535457Z',
'created_by':
'/bin/sh -c set -x \u0026\u0026 apkArch="$(cat /etc/apk/arch)" \u0026\u0026 nginxPackages=" nginx=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-r${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-r${PKG_RELEASE} " \u0026\u0026 apk add --no-cache --virtual .checksum-deps openssl \u0026\u0026 case "$apkArch" in x86_64|aarch64) set -x \u0026\u0026 KEY_SHA512="e09fa32f0a0eab2b879ccbbc4d0e4fb9751486eedda75e35fac65802cc9faa266425edf83e261137a2f4d16281ce2c1a5f4502930fe75154723da014214f0655" \u0026\u0026 wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \u0026\u0026 if echo "$KEY_SHA512 */tmp/nginx_signing.rsa.pub" | sha512sum -c -; then echo "key verification succeeded!"; mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; else echo "key verification failed!"; exit 1; fi \u0026\u0026 apk add -X "https://nginx.org/packages/mainline/alpine/v$(egrep -o \'^[0-9]+\\.[0-9]+\' /etc/alpine-release)/main" --no-cache $nginxPackages ;; *) set -x \u0026\u0026 tempDir="$(mktemp -d)" \u0026\u0026 chown nobody:nobody $tempDir \u0026\u0026 apk add --no-cache --virtual .build-deps gcc libc-dev make openssl-dev pcre2-dev zlib-dev linux-headers libxslt-dev gd-dev geoip-dev libedit-dev bash alpine-sdk findutils \u0026\u0026 su nobody -s /bin/sh -c " export HOME=${tempDir} \u0026\u0026 cd ${tempDir} \u0026\u0026 curl -f -O https://hg.nginx.org/pkg-oss/archive/${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 PKGOSSCHECKSUM=\\"8f3f6c1ddd984c0c7320d3bea25eee42749db6d69c251223cf91d69b8d80b703ab39eb94fcf731399a7693ebd8dd37d1b3232ea1184ca98e5ca0ba6165e1a05c *${NGINX_VERSION}-${PKG_RELEASE}.tar.gz\\" \u0026\u0026 if [ \\"\\$(openssl sha512 -r ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz)\\" = \\"\\$PKGOSSCHECKSUM\\" ]; then echo \\"pkg-oss tarball checksum verification succeeded!\\"; else echo \\"pkg-oss tarball checksum verification failed!\\"; exit 1; fi \u0026\u0026 tar xzvf ${NGINX_VERSION}-${PKG_RELEASE}.tar.gz \u0026\u0026 cd pkg-oss-${NGINX_VERSION}-${PKG_RELEASE} \u0026\u0026 cd alpine \u0026\u0026 make module-geoip module-image-filter module-njs module-xslt \u0026\u0026 apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \u0026\u0026 abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz " \u0026\u0026 cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \u0026\u0026 apk del .build-deps \u0026\u0026 apk add -X ${tempDir}/packages/alpine/ --no-cache $nginxPackages ;; esac \u0026\u0026 apk del .checksum-deps \u0026\u0026 if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \u0026\u0026 if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \u0026\u0026 if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \u0026\u0026 apk add --no-cache curl ca-certificates',
},
{
'created': '2023-05-16T17:53:59.777374567Z',
'created_by': 'LABEL maintainer=Jones MAGLOIRE @Joxit',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.777374567Z',
'created_by': 'WORKDIR /usr/share/nginx/html/',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.777374567Z',
'created_by': 'ENV NGINX_PROXY_HEADER_Host=$http_host',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.777374567Z',
'created_by': 'ENV NGINX_LISTEN_PORT=80',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.777374567Z',
'created_by': 'ENV SHOW_CATALOG_NB_TAGS=false',
'comment': 'buildkit.dockerfile.v0',
'empty_layer': true,
},
{
'created': '2023-05-16T17:53:59.792463797Z',
'created_by': 'COPY nginx/default.conf /etc/nginx/conf.d/default.conf # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.806725927Z',
'created_by': 'COPY bin/90-docker-registry-ui.sh /docker-entrypoint.d/90-docker-registry-ui.sh # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.82462658Z',
'created_by': 'COPY dist/ /usr/share/nginx/html/ # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:53:59.839284221Z',
'created_by': 'COPY favicon.ico /usr/share/nginx/html/ # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
{
'created': '2023-05-16T17:54:00.037166004Z',
'created_by':
'RUN /bin/sh -c chown -R nginx:nginx /etc/nginx/ /usr/share/nginx/html/ /var/cache/nginx # buildkit',
'comment': 'buildkit.dockerfile.v0',
},
],
'moby.buildkit.buildinfo.v1':
'eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvbmdpbng6YWxwaW5lIiwicGluIjoic2hhMjU2OjAyZmZkNDM5YjcxZDllYTk0MDhlNDQ5YjU2OGY2NWMwYmJiYjk0YmViZDg3NTBmMWQ4MDIzMWFiNjQ5NjAwOGUifV19',
'os': 'linux',
'rootfs': {
'type': 'layers',
'diff_ids': [
'sha256:26cbea5cba74143fbe6f584f5fc5321543155aedc4a434fcaa63b643877b5a74',
'sha256:09353074bdde293a418c894712bba3f4fca2c42cef5c2061caf611794c56ce3c',
'sha256:2749f4c7cb991ec74071f7ccec1b7907b85956709d8fccebbe77d44f01809aa9',
'sha256:7e1b91127bea03c6bcbec75fc482f62e5e025b9b4e08f46fe11a6a8d9375d0f2',
'sha256:3638581487963b65388fc14d1f14a1b0be2e82a48249da474047ea2e4601dc8c',
'sha256:fbebe8ba7beddf179286606d8a627f5dd553d78645b71f2703ab114802f0bcd5',
'sha256:363722710bd8c3005b16061d18059259edf0108644e6ab74c36c9a040a824d10',
'sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef',
'sha256:2cf5b89f94fd4a7ce1bc7eaad8d4fb9ae56e83b57b94537fd96682eda514dfc9',
'sha256:417b58457dd524a2ab48d3b4d124910aaff0680035f71816ee6efb5fb08c784a',
'sha256:b9b82eb1d5ef5a2a6ab324c70d655e27f8109959e6dcd43cf811a451c9153b3e',
'sha256:cab4286916ebabccd5ac123b0ee58ee2eaf190bc0bbe0339f106244eb75c0355',
'sha256:e7dc8b731ca3be139b27a043e9d21f711c5505ecfe9f9f0bfc4ed794d88e67e4',
],
},
};
export const ociImageIndexManifest = {
'application/vnd.oci.image.index.v1+json': imageIndex,
imageIndex,
'sha256:868d96eea2ab3b0905caa746339541cef30ed4e0864da7f89e423cb50aee7857': manifestAmd64,
manifestAmd64,
'sha256:ee45307ae7404ccfbe4536677095b1ad1258a261c79ecdf5640d24bec66e1257': manifestArm,
manifestArm,
'sha256:7209907f3aa39f8b259069272274f185c4e9772ea7159722728b5f648c71eaad': blobAmd64,
blobAmd64,
'sha256:6d0a94a37f413ae834a226070fb042386303fd80ac79c6d4a12e986a03416710': blobArm,
blobArm,
};

122
test/repositories.test.js Normal file
View File

@@ -0,0 +1,122 @@
import { getBranching } from '../src/scripts/repositories.js';
import { DockerRegistryUIError } from '../src/scripts/error.js';
import assert from 'assert';
describe('repositories', () => {
describe('getBranching', () => {
it('should not branch for no levels', () => {
const branching = getBranching(0, 0);
assert.deepEqual(branching(['alpine', 'debian', 'nginx']), ['alpine', 'debian', 'nginx']);
assert.deepEqual(branching(['alpine', 'joxit/docker-registry-ui', 'joxit/docker-registry-ui/amd64', 'nginx']), [
'alpine',
'joxit/docker-registry-ui',
'joxit/docker-registry-ui/amd64',
'nginx',
]);
});
it('should branch for one level', () => {
const branching = getBranching(1, 1);
assert.deepEqual(branching(['alpine', 'debian', 'nginx']), ['alpine', 'debian', 'nginx']);
assert.deepEqual(branching(['alpine', 'joxit/docker-registry-ui', 'nginx']), [
'alpine',
{ images: ['joxit/docker-registry-ui'], repo: 'joxit/' },
'nginx',
]);
assert.deepEqual(
branching(['alpine', 'joxit/docker-registry-ui', 'joxit/kokai', 'joxit/docker-registry-ui/amd64', 'nginx']),
[
'alpine',
{ images: ['joxit/docker-registry-ui', 'joxit/docker-registry-ui/amd64', 'joxit/kokai'], repo: 'joxit/' },
'nginx',
]
);
});
it('should branch for two level', () => {
const branching = getBranching(2, 2);
assert.deepEqual(branching(['alpine', 'debian', 'nginx']), ['alpine', 'debian', 'nginx']);
assert.deepEqual(branching(['alpine', 'joxit/docker-registry-ui', 'nginx']), [
'alpine',
'joxit/docker-registry-ui',
'nginx',
]);
assert.deepEqual(
branching([
'alpine',
'joxit/docker-registry-ui',
'joxit/kokai',
'joxit/docker-registry-ui/amd64',
'joxit/docker-registry-ui/amd64/latest',
'joxit/docker-registry-ui/armv7',
'joxit/docker-registry-ui/armv7/latest',
'nginx',
]),
[
'alpine',
'joxit/docker-registry-ui',
{
images: [
'joxit/docker-registry-ui/amd64',
'joxit/docker-registry-ui/amd64/latest',
'joxit/docker-registry-ui/armv7',
'joxit/docker-registry-ui/armv7/latest',
],
repo: 'joxit/docker-registry-ui/',
},
'joxit/kokai',
'nginx',
]
);
});
it('should branch from one to two level', () => {
const branching = getBranching(1, 2);
assert.deepEqual(branching(['alpine', 'debian', 'nginx']), ['alpine', 'debian', 'nginx']);
assert.deepEqual(branching(['alpine', 'joxit/docker-registry-ui', 'nginx']), [
'alpine',
{ images: ['joxit/docker-registry-ui'], repo: 'joxit/' },
'nginx',
]);
assert.deepEqual(
branching([
'alpine',
'joxit/docker-registry-ui',
'joxit/kokai',
'joxit/docker-registry-ui/amd64',
'joxit/docker-registry-ui/amd64/latest',
'joxit/docker-registry-ui/armv7',
'joxit/docker-registry-ui/armv7/latest',
'nginx',
]),
[
'alpine',
{
images: [
'joxit/docker-registry-ui',
{
images: [
'joxit/docker-registry-ui/amd64',
'joxit/docker-registry-ui/amd64/latest',
'joxit/docker-registry-ui/armv7',
'joxit/docker-registry-ui/armv7/latest',
],
repo: 'joxit/docker-registry-ui/',
},
'joxit/kokai',
],
repo: 'joxit/',
},
'nginx',
]
);
});
});
it('should branch from one to two level', () => {
assert.throws(() => getBranching(2, 1), DockerRegistryUIError, `Did not throw on min > max`);
assert.throws(() => getBranching(-2, 1), DockerRegistryUIError, `Did not throw on min < 0`);
assert.throws(() => getBranching(2, -1), DockerRegistryUIError, `Did not throw on max < 0`);
assert.throws(() => getBranching('foo', 'bar'), DockerRegistryUIError, `Did not throw on max < 0`);
});
});

163
test/taglist-order.test.js Normal file
View File

@@ -0,0 +1,163 @@
import { taglistOrderVariants, taglistOrderParser, splitTagToArray } from '../src/scripts/taglist-order.js';
import { getTagComparator } from '../src/scripts/taglist-order.js';
import { DockerRegistryUIError } from '../src/scripts/error.js';
import assert from 'assert';
describe('taglist-order tests', () => {
describe('taglistOrderVariants', () => {
it(`should return the input when it's well formed and num first`, () => {
const expected = ['num-asc;alpha-asc', 'num-asc;alpha-desc', 'num-desc;alpha-asc', 'num-desc;alpha-asc'];
expected.forEach((e) => assert.deepEqual(taglistOrderVariants(e), e));
});
it(`should return the input when it's well formed and alpha first`, () => {
const expected = ['alpha-asc;num-asc', 'alpha-asc;num-desc', 'alpha-desc;num-asc', 'alpha-desc;num-asc'];
expected.forEach((e) => assert.deepEqual(taglistOrderVariants(e), e));
});
it('should return correct default order', () => {
const expected = 'alpha-asc;num-desc';
[undefined, ''].forEach((e) => assert.deepEqual(taglistOrderVariants(e), expected));
});
it('should return correct variant of `num-asc;alpha-asc`', () => {
const expected = 'num-asc;alpha-asc';
['asc', 'num-asc'].forEach((e) => assert.deepEqual(taglistOrderVariants(e), expected));
});
it('should return correct variant of `alpha-desc;num-desc`', () => {
const expected = 'alpha-desc;num-desc';
['desc'].forEach((e) => assert.deepEqual(taglistOrderVariants(e), expected));
});
it('should extend correctly orders', () => {
['alpha-desc', 'alpha-asc'].forEach((e) => assert.deepEqual(taglistOrderVariants(e), `${e};num-asc`));
['num-desc', 'num-asc'].forEach((e) => assert.deepEqual(taglistOrderVariants(e), `${e};alpha-asc`));
});
it('should throw error on incorrect values', () => {
['alpha-desc;alpha-asc', 'foobar'].forEach((e) =>
assert.throws(() => taglistOrderVariants(e), DockerRegistryUIError, `Did not throw on ${e}`)
);
});
});
describe('taglistOrderParser', () => {
it('should have default configuration when empty or undefined', () => {
const expected = { numAsc: false, alphaAsc: true, numFirst: false };
assert.deepEqual(taglistOrderParser(), expected);
assert.deepEqual(taglistOrderParser(''), expected);
});
it('should parse correctly `num-asc;alpha-asc` and variants', () => {
const expected = { numAsc: true, alphaAsc: true, numFirst: true };
['asc', 'num-asc;alpha-asc', 'num-asc'].forEach((e) =>
assert.deepEqual(taglistOrderParser(e), expected, `wrong result for ${e}`)
);
});
it('should parse correctly `alpha-desc;num-desc` and variants', () => {
const expected = { numAsc: false, alphaAsc: false, numFirst: false };
['desc', 'alpha-desc;num-desc'].forEach((e) =>
assert.deepEqual(taglistOrderParser(e), expected, `wrong result for ${e}`)
);
});
it('should parse correctly `alpha-asc;num-desc` and variants', () => {
const expected = { numAsc: false, alphaAsc: true, numFirst: false };
assert.deepEqual(taglistOrderParser('alpha-asc;num-desc'), expected);
});
it('should parse correctly `num-desc;alpha-desc` and variants', () => {
const expected = { numAsc: false, alphaAsc: false, numFirst: true };
assert.deepEqual(taglistOrderParser('num-desc;alpha-desc'), expected);
});
});
describe('splitTagToArray', () => {
it('should reduce tags with numbers', () => {
assert.deepEqual(splitTagToArray('0.2.4'), [0, '.', 2, '.', 4]);
assert.deepEqual(splitTagToArray('1.2.3-SNAPSHOT'), [1, '.', 2, '.', 3, '-SNAPSHOT']);
assert.deepEqual(splitTagToArray('alpine-3.2'), ['alpine-', 3, '.', 2]);
assert.deepEqual(splitTagToArray('10.30.00'), [10, '.', 30, '.', 0]);
assert.deepEqual(splitTagToArray('010.30.00'), [10, '.', 30, '.', 0]);
assert.deepEqual(splitTagToArray('z010.30.00'), ['z', 10, '.', 30, '.', 0]);
});
it('should reduce tags without numbers', () => {
assert.deepEqual(splitTagToArray('main'), ['main']);
assert.deepEqual(splitTagToArray('master'), ['master']);
assert.deepEqual(splitTagToArray('alpine-lts'), ['alpine-lts']);
});
});
describe('getTagComparator', () => {
it('should sort tags with `num-asc;alpha-asc`', () => {
const comparator = getTagComparator(taglistOrderParser('num-asc;alpha-asc'));
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['0.2.4', '0.2.5', '1.2.5']);
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['0.2.4', 'latest', 'main']);
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['1.0.0', '1.0.0-SNAPSHOT', 'latest']);
});
it('should sort tags with `num-desc;alpha-asc`', () => {
const comparator = getTagComparator(taglistOrderParser('num-desc;alpha-asc'));
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['1.2.5', '0.2.5', '0.2.4']);
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['0.2.4', 'latest', 'main']);
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['1.0.0', '1.0.0-SNAPSHOT', 'latest']);
});
it('should sort tags with `num-asc;alpha-desc`', () => {
const comparator = getTagComparator(taglistOrderParser('num-asc;alpha-desc'));
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['0.2.4', '0.2.5', '1.2.5']);
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['0.2.4', 'main', 'latest']);
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['1.0.0', '1.0.0-SNAPSHOT', 'latest']);
});
it('should sort tags with `num-desc;alpha-desc`', () => {
const comparator = getTagComparator(taglistOrderParser('num-desc;alpha-desc'));
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['1.2.5', '0.2.5', '0.2.4']);
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['0.2.4', 'main', 'latest']);
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['1.0.0', '1.0.0-SNAPSHOT', 'latest']);
});
it('should sort tags with `alpha-asc;num-asc`', () => {
const comparator = getTagComparator(taglistOrderParser('alpha-asc;num-asc'));
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['0.2.4', '0.2.5', '1.2.5']);
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['latest', 'main', '0.2.4']);
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['latest', '1.0.0', '1.0.0-SNAPSHOT']);
assert.deepEqual(['latest', 'main', 'edge'].sort(comparator), ['edge', 'latest', 'main']);
});
it('should sort tags with `alpha-asc;num-desc`', () => {
const comparator = getTagComparator(taglistOrderParser('alpha-asc;num-desc'));
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['1.2.5', '0.2.5', '0.2.4']);
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['latest', 'main', '0.2.4']);
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['latest', '1.0.0', '1.0.0-SNAPSHOT']);
assert.deepEqual(['latest', 'main', 'edge'].sort(comparator), ['edge', 'latest', 'main']);
});
it('should sort tags with `alpha-desc;num-asc`', () => {
const comparator = getTagComparator(taglistOrderParser('alpha-desc;num-asc'));
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['0.2.4', '0.2.5', '1.2.5']);
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['main', 'latest', '0.2.4']);
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['latest', '1.0.0', '1.0.0-SNAPSHOT']);
assert.deepEqual(['latest', 'main', 'edge'].sort(comparator), ['main', 'latest', 'edge']);
});
it('should sort tags with `alpha-desc;num-desc`', () => {
const comparator = getTagComparator(taglistOrderParser('alpha-desc;num-desc'));
assert.deepEqual(['0.2.4', '1.2.5', '0.2.5'].sort(comparator), ['1.2.5', '0.2.5', '0.2.4']);
assert.deepEqual(['latest', '0.2.4', 'main'].sort(comparator), ['main', 'latest', '0.2.4']);
assert.deepEqual(['latest', '1.0.0-SNAPSHOT', '1.0.0'].sort(comparator), ['latest', '1.0.0', '1.0.0-SNAPSHOT']);
assert.deepEqual(['latest', 'main', 'edge'].sort(comparator), ['main', 'latest', 'edge']);
});
});
});

52
test/utils.test.js Normal file
View File

@@ -0,0 +1,52 @@
import { isNewestVersion } from '../src/scripts/utils.js';
import assert from 'assert';
describe('utils tests', () => {
describe('isNewestVersion', () => {
it(`should return true for the same version`, () => {
const expected = ['2.0.0', '2.4.1', '2.5.0', null, undefined];
expected.forEach((e) => assert.ok(isNewestVersion(e, e)));
});
it(`should return true with on common versions`, () => {
assert.ok(isNewestVersion('2.5.1', '2.5.0'));
assert.ok(isNewestVersion('2.5.0', '2.0.0'));
assert.ok(isNewestVersion('2.15.0', '1.25.10'));
assert.ok(isNewestVersion('10.10.10', '2.25.20'));
});
it(`should return false on common versions`, () => {
assert.equal(isNewestVersion('1.0.0', '2.5.0'), false);
assert.equal(isNewestVersion('10.10.10', '20.20.20'), false);
assert.equal(isNewestVersion('2.4.10', '2.5.0'), false);
assert.equal(isNewestVersion('2.5.0', '2.6.0'), false);
});
it(`should return true for -dev next versions`, () => {
assert.ok(isNewestVersion('2.5.0-dev', '2.4.1'));
assert.ok(isNewestVersion('2.6.0-dev', '2.5.0'));
assert.ok(isNewestVersion('2.15.0-dev', '2.14.1'));
assert.ok(isNewestVersion('2.15.0-dev', '1.16.0'));
});
it(`should return false for -dev with current minor version`, () => {
assert.equal(isNewestVersion('2.5.0-dev', '2.5.0'), false);
assert.equal(isNewestVersion('2.5.0-dev', '2.5.10'), false);
assert.equal(isNewestVersion('2.15.0-dev', '2.15.0'), false);
assert.equal(isNewestVersion('2.0.0-dev', '2.15.0'), false);
});
it(`should return true for -{commit sha} next versions`, () => {
assert.ok(isNewestVersion('2.5.0-ffb6d14baf', '2.4.1'));
assert.ok(isNewestVersion('2.6.0-ffb6d14baf', '2.5.0'));
assert.ok(isNewestVersion('2.15.0-ffb6d14baf', '2.14.1'));
assert.ok(isNewestVersion('2.15.0-ffb6d14baf', '1.16.0'));
});
it(`should return false for -{commit sha} with current minor version`, () => {
assert.equal(isNewestVersion('2.5.0-ffb6d14baf', '2.5.0'), false);
assert.equal(isNewestVersion('2.5.0-ffb6d14baf', '2.5.10'), false);
assert.equal(isNewestVersion('2.15.0-ffb6d14baf', '2.15.0'), false);
assert.equal(isNewestVersion('2.0.0-ffb6d14baf', '2.15.0'), false);
});
});
});