Compare commits
65 Commits
Author | SHA1 | Date |
---|---|---|
silverwind | a36e3aac57 | |
Lukas Engelter | 7025df687c | |
Lukas Engelter | cfbc6e76a8 | |
toinux | dc9bdcbedd | |
Joxit | 6c3c27e215 | |
Jones Magloire | 6318ccfdf5 | |
Christian | 686b1709b2 | |
Joxit | e79a20a5e5 | |
Joxit | 7991442fce | |
Joxit | de6d09c98c | |
Joxit | 1f2913248e | |
Joxit | 3414d7b517 | |
AJWavio | cd99f6e231 | |
Joxit | f015187b14 | |
Joxit | 07713f1425 | |
Joxit | f560025e70 | |
Joxit | bf9c6c82e7 | |
Joxit | c74a9aeaa3 | |
cui fliter | 42bcec50df | |
Joxit | 18dd5ca129 | |
Joxit | 84b31f2cfb | |
Joxit | 16d01d4dbf | |
Joxit | 834a0ea40a | |
Joxit | c8383a9c80 | |
Joxit | ae9591c79a | |
Joxit | b88dc4567d | |
Joxit | ffb6d14baf | |
Joxit | 684f82f24e | |
Artur Mostowski | 9cfb6791f8 | |
Joxit | 4091baa341 | |
Jones Magloire | affb0572c9 | |
Joxit | dbfc9fe587 | |
Artur Mostowski | d2e6cdcab1 | |
Joxit | 1031034bc4 | |
Joxit | 398fa65fa1 | |
Artur Mostowski | b6604421bb | |
Joxit | 03157d841e | |
Joxit | e7e762d6d9 | |
Joxit | 8e98c1c63b | |
Joxit | aca633720a | |
Joxit | 5a340291c2 | |
Joxit | 9ebbbc3518 | |
Jones Magloire | d2222bef05 | |
Joxit | c6dee14d79 | |
Joxit | 92584fc3da | |
Joxit | 78606e07f1 | |
Alexander Wolz | bc80050a44 | |
Joxit | 8bbfc5c390 | |
Joxit | fbab517a17 | |
Joxit | 34d1ed90ad | |
Joxit | a135c00866 | |
Joxit | ba322e076f | |
Joxit | a77103a2d4 | |
Joxit | b0ea4e5fb8 | |
Joxit | edb5aa97e8 | |
xcaliburinhand | ca7202d1f5 | |
Joxit | a3e987482e | |
Joxit | 2b63fb725c | |
Joxit | b0811086fd | |
Joxit | 43a242312c | |
silverwind | bfc901eb0b | |
Joxit | f984633bc3 | |
Joxit | ea508e6a1d | |
Joxit | 4f452207c4 | |
Joxit | b2590115a7 |
|
@ -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
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
name: Custom issue template
|
||||
about: Describe this issue template's purpose here.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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 }}
|
|
@ -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
|
||||
|
|
|
@ -7,3 +7,4 @@ _site
|
|||
*.orig
|
||||
.serve/
|
||||
demo/v2
|
||||
.version.json
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -44,3 +45,11 @@
|
|||
- [@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)
|
||||
|
|
|
@ -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.
|
|
@ -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"
|
||||
|
||||
|
|
102
README.md
102
README.md
|
@ -1,13 +1,10 @@
|
|||
---
|
||||
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
|
||||
|
||||
|
@ -17,12 +14,22 @@ You may need the [migration guide from 1.x to 2.x](https://github.com/Joxit/dock
|
|||
|
||||
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
|
||||
|
||||
- Many ways to delete multiple images at once
|
||||
|
@ -38,6 +45,7 @@ If you like my work and want to support it, don't hesitate to [sponsor me](https
|
|||
- 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:`).
|
||||
|
||||
Checkout all options in [Available options](#available-options) section.
|
||||
|
||||
|
@ -73,6 +81,10 @@ Checkout all options in [Available options](#available-options) section.
|
|||
- 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.
|
||||
|
||||
|
@ -87,20 +99,27 @@ Some env options are available for use this interface for **only one server** (w
|
|||
- `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/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) and [#132](https://github.com/Joxit/docker-registry-ui/pull/132)). (default: `100000`). 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`: Desactivate 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. 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
|
||||
- `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). Can be 0 to disable branching. (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). Can be 0 to disable branching. (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/).
|
||||
|
||||
### Theme options
|
||||
|
@ -109,16 +128,59 @@ This featureswas added to version 2.4.0. See more about this in [#283](https://g
|
|||
|
||||
| Environment variable | light theme value | dark theme value |
|
||||
| --- | --- | --- |
|
||||
| `THEME_PRIMARY_TEXT` | `#25313b` | `#8A9EBA` |
|
||||
| `THEME_NEUTRAL_TEXT` | `#777777` | `#36527A` |
|
||||
| `THEME_PRIMARY_TEXT` | `#25313b` | `#98a8bd` |
|
||||
| `THEME_NEUTRAL_TEXT` | `#777777` | `#6d7fab` |
|
||||
| `THEME_BACKGROUND` | `#ffffff` | `#22272e` |
|
||||
| `THEME_HOVER_BACKGROUND` | `#eeeeee` | `#30404D` |
|
||||
| `THEME_ACCENT_TEXT` | `#6680a1` | `#5684FF` |
|
||||
| `THEME_HOVER_BACKGROUND` | `#eeeeee` | `#343a4b` |
|
||||
| `THEME_ACCENT_TEXT` | `#5f7796` | `#5c88ff` |
|
||||
| `THEME_HEADER_TEXT` | `#ffffff` | `#ffffff` |
|
||||
| `THEME_HEADER_BACKGROUND` | `#25313b` | `#333A45` |
|
||||
| `THEME_HEADER_ACCENT_TEXT` | `#7b9ac2` | `#7ea1ff` |
|
||||
| `THEME_HEADER_BACKGROUND` | `#25313b` | `#333a45` |
|
||||
| `THEME_FOOTER_TEXT` | `#ffffff` | `#ffffff` |
|
||||
| `THEME_FOOTER_NEUTRAL_TEXT` | `#999999` | `#999999` |
|
||||
| `THEME_FOOTER_BACKGROUND` | `#555555` | `#555555` |
|
||||
| `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.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
|
||||
|
||||
|
|
|
@ -6,11 +6,18 @@ 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
|
||||
|
||||
grep -o 'THEME[A-Z_]*' index.html | while read e; do
|
||||
sed -i "s~\${$e}~$(printenv $e)~" index.html
|
||||
|
@ -59,7 +66,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
single-registry="false"
|
||||
default-registries="https://joxit.dev/docker-registry-demo"
|
||||
show-catalog-nb-tags="true"
|
||||
taglist-order=""
|
||||
theme="auto"
|
||||
/>
|
||||
<script>
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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 |
|
@ -26,6 +26,11 @@
|
|||
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}"
|
||||
|
@ -37,4 +42,5 @@
|
|||
theme-footer-text="${THEME_FOOTER_TEXT}"
|
||||
theme-footer-neutra-text="${THEME_FOOTER_NEUTRAL_TEXT}"
|
||||
theme-footer-background="${THEME_FOOTER_BACKGROUND}"
|
||||
tags-per-page="${TAGLIST_PAGE_SIZE}"
|
||||
></docker-registry-ui><script src="docker-registry-ui.js"></script></body></html>
|
Binary file not shown.
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 3.6 MiB |
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
- ./read-write.htpasswd:/etc/nginx/auth/write.htpasswd:ro
|
||||
- ./read-only.htpasswd:/etc/nginx/auth/read.htpasswd:ro
|
||||
depends_on:
|
||||
- registry
|
||||
networks:
|
||||
- registry-ui-net
|
||||
networks:
|
||||
registry-ui-net:
|
||||
registry-ui-net:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
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;
|
||||
|
@ -31,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
|
||||
#
|
||||
|
@ -42,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;
|
||||
#}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "docker-registry-ui",
|
||||
"version": "2.4.0",
|
||||
"version": "2.5.7",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"format": "npm run format-html && npm run format-js && npm run format-riot",
|
||||
|
@ -9,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",
|
||||
|
@ -31,10 +32,11 @@
|
|||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-terser": "^0.2.1",
|
||||
"core-js": "^3.27.1",
|
||||
"mocha": "^10.2.0",
|
||||
"node-sass": "^8.0.0",
|
||||
"prettier": "^2.8.1",
|
||||
"riot": "^7.1.0",
|
||||
"riot-mui": "github:joxit/riot-5-mui#a97f2d3",
|
||||
"riot-mui": "github:joxit/riot-5-mui#a477acc",
|
||||
"rollup": "^3.9.0",
|
||||
"rollup-plugin-app-utils": "^1.0.6",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
|
|
|
@ -14,10 +14,24 @@ 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(),
|
||||
|
|
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 44 KiB |
|
@ -20,7 +20,12 @@ 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-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>
|
||||
|
@ -45,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>
|
||||
|
@ -65,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;
|
||||
|
@ -95,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 });
|
||||
|
|
|
@ -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,33 +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({ 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);
|
||||
}
|
||||
|
@ -106,7 +114,7 @@ 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,
|
||||
});
|
||||
});
|
||||
|
@ -115,4 +123,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
catalog {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
catalog > material-card {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</catalog>
|
||||
|
|
|
@ -58,10 +58,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) {
|
||||
|
@ -86,8 +87,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);
|
||||
|
|
|
@ -17,7 +17,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
<docker-registry-ui>
|
||||
<header>
|
||||
<material-navbar>
|
||||
<span class="logo">Docker Registry UI</span>
|
||||
<span class="logo">
|
||||
<span>Docker Registry UI</span>
|
||||
<version-notification version="{ latest }" on-notify="{ notifySnackbar }"></version-notification>
|
||||
</span>
|
||||
<div class="menu">
|
||||
<search-bar on-search="{ onSearch }"></search-bar>
|
||||
<dialogs-menu
|
||||
|
@ -31,6 +34,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
</material-navbar>
|
||||
</header>
|
||||
<main>
|
||||
<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 base="#!">
|
||||
<route path="{baseRoute}">
|
||||
<catalog
|
||||
|
@ -41,6 +59,10 @@ 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/(.*)">
|
||||
|
@ -50,11 +72,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/(.*)">
|
||||
|
@ -69,6 +95,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>
|
||||
|
@ -80,13 +107,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
on-authenticated="{ state.onAuthenticated }"
|
||||
opened="{ state.authenticationDialogOpened }"
|
||||
></registry-authentication>
|
||||
<error-page
|
||||
if="{ state.pageError }"
|
||||
code="{ state.pageError.code }"
|
||||
status="{ state.pageError.status }"
|
||||
message="{ state.pageError.message }"
|
||||
url="{ state.pageError.url }"
|
||||
></error-page>
|
||||
<material-snackbar message="{ state.snackbarMessage }" is-error="{ state.snackbarIsError }"></material-snackbar>
|
||||
</main>
|
||||
<footer>
|
||||
|
@ -118,7 +138,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
</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';
|
||||
|
@ -126,7 +146,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
import DialogsMenu from './dialogs/dialogs-menu.riot';
|
||||
import SearchBar from './search-bar.riot';
|
||||
import ErrorPage from './error-page.riot';
|
||||
import { stripHttps, getRegistryServers, setRegistryServers, truthy, stringToArray } from '../scripts/utils';
|
||||
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';
|
||||
|
||||
|
@ -140,6 +161,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
Router,
|
||||
Route,
|
||||
ErrorPage,
|
||||
VersionNotification,
|
||||
},
|
||||
onUpdated(props, state) {
|
||||
state.snackbarIsError = false;
|
||||
|
@ -154,7 +176,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
}
|
||||
|
||||
window.onselectstart = (e) => {
|
||||
if (e.target && e.target.className) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
@ -166,7 +188,7 @@ 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);
|
||||
|
@ -192,6 +214,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 {
|
||||
|
@ -213,7 +236,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
snackbarMessage: message,
|
||||
snackbarIsError: isError || false,
|
||||
});
|
||||
} else if (message && message.code) {
|
||||
} else if (message && (message.code || message.errors)) {
|
||||
this.update({
|
||||
pageError: message,
|
||||
});
|
||||
|
@ -238,11 +261,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
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);
|
||||
|
@ -257,6 +291,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
material-navbar .nav-wrapper .logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
material-footer {
|
||||
color: var(--footer-neutral-text);
|
||||
background-color: var(--footer-background);
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<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 shame
|
||||
`<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>
|
||||
|
@ -29,6 +29,42 @@
|
|||
<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_BRANCH and CATALOG_MAX_BRANCH</p>
|
||||
</template>
|
||||
</div>
|
||||
<script>
|
||||
export default {
|
||||
|
@ -37,6 +73,8 @@
|
|||
switch (props.code) {
|
||||
case 'CATALOG_NOT_FOUND':
|
||||
return '404';
|
||||
case 'PAGINATION_NUMBER_INVALID':
|
||||
return '400';
|
||||
}
|
||||
},
|
||||
URL: window.URL,
|
||||
|
@ -46,7 +84,7 @@
|
|||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 20px;
|
||||
margin: 20px 3em;
|
||||
}
|
||||
:host .content {
|
||||
margin: auto;
|
||||
|
@ -65,5 +103,21 @@
|
|||
: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>
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<material-input
|
||||
label="Search in page"
|
||||
text-color="var(--header-text)"
|
||||
label-color="var(--neutral-text)"
|
||||
color="var(--accent-text)"
|
||||
label-color="var(--header-accent-text)"
|
||||
color="var(--header-accent-text)"
|
||||
></material-input>
|
||||
<script>
|
||||
import { router } from '@riotjs/route';
|
||||
|
|
|
@ -89,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();
|
||||
},
|
||||
|
@ -159,7 +160,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
return router.taglist(this.props.image);
|
||||
},
|
||||
showDockerfile() {
|
||||
console.log(this);
|
||||
this.update({ showDockerfile: true });
|
||||
},
|
||||
onDockerfileClose() {
|
||||
|
@ -287,5 +287,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
h2 .material-icons {
|
||||
margin-left: .25em;
|
||||
}
|
||||
</style>
|
||||
</tag-history>
|
||||
|
|
|
@ -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>
|
|
@ -39,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 }"
|
||||
|
@ -47,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 }"
|
||||
|
@ -54,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,
|
||||
|
@ -79,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);
|
||||
|
@ -86,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) {
|
||||
|
@ -104,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) {
|
||||
|
@ -133,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,
|
||||
|
@ -168,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();
|
||||
|
|
|
@ -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 }"
|
||||
|
@ -103,7 +106,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
on-notify="{ props.onNotify }"
|
||||
></copy-to-clipboard>
|
||||
</td>
|
||||
<td class="show-tag-history">
|
||||
<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">
|
||||
|
@ -130,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';
|
||||
|
@ -146,6 +153,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
RemoveImage,
|
||||
TagHistoryButton,
|
||||
ConfirmDeleteImage,
|
||||
Architectures,
|
||||
},
|
||||
onBeforeMount(props) {
|
||||
this.state = {
|
||||
|
@ -183,31 +191,31 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
this.update({
|
||||
multiDelete: true,
|
||||
toDelete: this.state.toDelete,
|
||||
slectedImage: undefined,
|
||||
selectedImage: undefined,
|
||||
});
|
||||
} else {
|
||||
this.update({
|
||||
multiDelete: event.target.checked,
|
||||
slectedImage: undefined,
|
||||
selectedImage: undefined,
|
||||
});
|
||||
}
|
||||
},
|
||||
onRemoveImageChange(action, image, shiftKey) {
|
||||
let confirmDeleteImage = false;
|
||||
let singleDeleteAction = false;
|
||||
let slectedImage = undefined;
|
||||
let selectedImage = undefined;
|
||||
switch (action) {
|
||||
case ACTION_CHECK_TO_DELETE: {
|
||||
this.state.toDelete.add(image);
|
||||
if (shiftKey) {
|
||||
slectedImage = this.supportShiftKey(image, true);
|
||||
selectedImage = this.supportShiftKey(image, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_UNCHECK_TO_DELETE: {
|
||||
this.state.toDelete.delete(image);
|
||||
if (shiftKey) {
|
||||
slectedImage = this.supportShiftKey(image, false);
|
||||
selectedImage = this.supportShiftKey(image, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -222,18 +230,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
toDelete: this.state.toDelete,
|
||||
confirmDeleteImage,
|
||||
singleDeleteAction,
|
||||
slectedImage,
|
||||
selectedImage,
|
||||
});
|
||||
},
|
||||
supportShiftKey(selectedImage, addOrRemove) {
|
||||
if (!this.state.slectedImage) {
|
||||
if (!this.state.selectedImage) {
|
||||
return selectedImage;
|
||||
} else {
|
||||
let shouldChange = false;
|
||||
const tags = getPage(this.props.tags, this.props.page);
|
||||
const tags = this.getPage(this.props.tags, this.props.page);
|
||||
tags
|
||||
.filter((image) => {
|
||||
if (image == this.state.slectedImage || image == selectedImage) {
|
||||
if (image == this.state.selectedImage || image == selectedImage) {
|
||||
shouldChange = !shouldChange;
|
||||
return true;
|
||||
}
|
||||
|
@ -261,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()||0) - (e1.creationDate?.getTime()||0)
|
||||
: (e1.creationDate?.getTime()||0) - (e2.creationDate?.getTime()||0)
|
||||
? (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));
|
||||
|
@ -277,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>
|
||||
|
|
|
@ -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);
|
||||
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>
|
|
@ -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 |
|
@ -39,6 +39,7 @@
|
|||
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,43 +48,58 @@
|
|||
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-neutra-text="${THEME_FOOTER_NEUTRAL_TEXT}"
|
||||
theme-footer-neutral-text="${THEME_FOOTER_NEUTRAL_TEXT}"
|
||||
theme-footer-background="${THEME_FOOTER_BACKGROUND}"
|
||||
tags-per-page="${TAGLIST_PAGE_SIZE}"
|
||||
>
|
||||
</docker-registry-ui>
|
||||
<!-- endbuild -->
|
||||
<!-- build:keep developement -->
|
||||
<docker-registry-ui
|
||||
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-neutra-text=""
|
||||
theme-footer-neutral-text=""
|
||||
theme-footer-background=""
|
||||
tags-per-page=""
|
||||
>
|
||||
</docker-registry-ui>
|
||||
<!-- endbuild -->
|
||||
|
|
|
@ -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) {}
|
||||
};
|
|
@ -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);
|
||||
|
@ -150,7 +152,10 @@ 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) {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export class DockerRegistryUIError extends Error {
|
||||
constructor(msg, code) {
|
||||
super(msg);
|
||||
this.isError = true;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
|
@ -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,6 +73,8 @@ 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;
|
||||
}
|
||||
|
@ -76,6 +84,11 @@ export class Http {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}, []);
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
};
|
|
@ -1,26 +1,28 @@
|
|||
const LIGHT_THEME = {
|
||||
'primary-text': '#25313b',
|
||||
'neutral-text': '#777',
|
||||
'background': '#fff',
|
||||
'hover-background': '#eee',
|
||||
'accent-text': '#6680a1',
|
||||
'header-text': '#fff',
|
||||
'neutral-text': '#777777',
|
||||
'background': '#ffffff',
|
||||
'hover-background': '#eeeeee',
|
||||
'accent-text': '#5f7796',
|
||||
'header-text': '#ffffff',
|
||||
'header-accent-text': '#7b9ac2',
|
||||
'header-background': '#25313b',
|
||||
'footer-text': '#fff',
|
||||
'footer-neutral-text': '#999',
|
||||
'footer-background': '#555',
|
||||
'footer-text': '#ffffff',
|
||||
'footer-neutral-text': '#adbacd',
|
||||
'footer-background': '#344251',
|
||||
};
|
||||
const DARK_THEME = {
|
||||
'primary-text': '#8A9EBA',
|
||||
'neutral-text': '#36527A',
|
||||
'primary-text': '#98a8bd',
|
||||
'neutral-text': '#6d7fab',
|
||||
'background': '#22272e',
|
||||
'hover-background': '#30404D',
|
||||
'accent-text': '#5684FF',
|
||||
'header-text': '#fff',
|
||||
'header-background': '#333A45',
|
||||
'footer-text': '#fff',
|
||||
'footer-neutral-text': '#999',
|
||||
'footer-background': '#555',
|
||||
'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';
|
||||
|
@ -34,7 +36,7 @@ const normalizeKey = (k) =>
|
|||
.replace(/^theme-/, '');
|
||||
|
||||
const preferDarkMode = ({ theme }) => {
|
||||
if (theme === 'auto' || theme === "") {
|
||||
if (theme === 'auto' || theme === '') {
|
||||
switch (localStorage.getItem(LOCAL_STORAGE_THEME)) {
|
||||
case 'dark':
|
||||
return true;
|
||||
|
|
|
@ -217,6 +217,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) {}
|
||||
}
|
||||
|
|
|
@ -69,17 +69,18 @@ material-card {
|
|||
material-card,
|
||||
material-tabs,
|
||||
pagination .container {
|
||||
max-width: 95%;
|
||||
margin: auto;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 1515px * 0.95 = 1440px */
|
||||
@media screen and (min-width: 1515px) {
|
||||
material-card,
|
||||
material-tabs,
|
||||
pagination .container {
|
||||
material-card,
|
||||
material-tabs,
|
||||
pagination .container,
|
||||
catalog {
|
||||
max-width: 95%;
|
||||
/* 1515px * 0.95 = 1440px */
|
||||
@media screen and (min-width: 1515px) {
|
||||
max-width: 1440px;
|
||||
}
|
||||
}
|
||||
|
@ -335,10 +336,6 @@ footer {
|
|||
bottom: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
min-height: calc(100% - 136px);
|
||||
}
|
||||
|
||||
material-footer {
|
||||
padding: 0.5em 1em;
|
||||
li {
|
||||
|
@ -390,6 +387,12 @@ 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;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -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`);
|
||||
});
|
||||
});
|
|
@ -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']);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue