From cec469ab6706eec5893248c123f6cbc27a926a88 Mon Sep 17 00:00:00 2001 From: Joxit Date: Sat, 27 Apr 2019 21:50:20 +0200 Subject: [PATCH] Update project page/README and example issue-75; rebuild project --- .gitignore | 3 +- README.md | 57 +++++++++++--------- _config.yml | 10 +++- demo/index.html | 8 +-- dist/scripts/docker-registry-ui-static.js | 2 +- dist/scripts/docker-registry-ui.js | 2 +- examples/issue-75/README.md | 2 +- examples/issue-75/registry-config/config.yml | 2 + index.md | 1 + 9 files changed, 53 insertions(+), 34 deletions(-) create mode 120000 index.md diff --git a/.gitignore b/.gitignore index b0b6314..cadd121 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules package-lock.json registry-data -.idea \ No newline at end of file +.idea +_site \ No newline at end of file diff --git a/README.md b/README.md index 4c0d51f..6a03556 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,47 @@ +--- +title: Project Page +--- + # Docker Registry UI ## Overview -This project aims to provide a user interface for your private docker registry v2. -There is no default registry on this UI, you should add your own with the UI. +This project aims to provide a simple and complete user interface for your private docker registry. +You have the choice between two versions, the **standard interface** and the **static interface**. + +In the **standard interface**, there is no default registry, you need to add your own within the UI. You can manage more than one registry server. -All registries will be stored in the [local storage](https://en.wikipedia.org/wiki/Web_storage#Local_and_session_storage) of your browser. +All registries will be stored in the [local storage](https://en.wikipedia.org/wiki/Web_storage#Local_and_session_storage) of your browser. No configuration is needed when you launch the UI. + +In the **static interface**, it will connect to a single registry and will not change. The configuration is done at the start of the interface, when you use the docker images whose tags contain the `static` keyword. 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. -## [GitHub Page](https://joxit.dev/docker-registry-ui) and [Live Demo](https://joxit.dev/docker-registry-ui/demo/) +## [Project Page](https://joxit.dev/docker-registry-ui) and [Live Demo](https://joxit.dev/docker-registry-ui/demo/) ![preview](https://raw.github.com/Joxit/docker-registry-ui/master/docker-registry-ui.gif "Preview of Docker Registry UI") ## Features - List all your repositories/images. -- List all tags for a repository/image -- Sort the tag list -- One interface for many registries -- Use a secured docker registry -- Share your docker registry with query parameter `url` (e.g. `https://joxit.dev/docker-registry-ui/demo?url=https://registry.example.com`) -- Use `joxit/docker-registry-ui:static` as reverse proxy to your docker registry (This will avoid CORS). -- Display image size (see #30) -- Add Title when using REGISTRY_URL (see #28) -- Alpine and Debian based images with supports for arm32v7 and arm64v8 -- Copy `docker pull` command to clipbloard -- Show sha256 for specific tag (hover image tag) -- Display image creation date (see #49) -- Display image history (see #58) -- Display image/tag count -- Image aggregation (see #56) -- Customise docker pull command on static registry UI (see #71) -- Multi-delete and multi-select shortcut (Alt+click) (see #29 and #80) +- List all tags for a image. +- Sort the tag list with number compatibility (see [#46](https://github.com/Joxit/docker-registry-ui/pull/46)). +- Use a secured docker registry. +- Display image size (see [#30](https://github.com/Joxit/docker-registry-ui/issues/30)). +- Multi arch supports, Alpine and Debian based images with supports for arm32v7 and arm64v8. +- Copy `docker pull` command to clipboard (see [#42](https://github.com/Joxit/docker-registry-ui/issues/42)). +- Show sha256 for specific tag (hover image tag). +- Display image creation date (see [#49](https://github.com/Joxit/docker-registry-ui/issues/49)) +- Display image history (see [#58](https://github.com/Joxit/docker-registry-ui/pull/58) & [#61](https://github.com/Joxit/docker-registry-ui/pull/61)). +- Image aggregation (see [#56](https://github.com/Joxit/docker-registry-ui/issues/56)). +- Display image/tag count (see [#56 issue comment](https://github.com/Joxit/docker-registry-ui/issues/56#issuecomment-449246524)). +- Select multiple tags to delete (see [#29](https://github.com/Joxit/docker-registry-ui/issues/29)). +- Select all tags with ALT + Click to delete (see [#80](https://github.com/Joxit/docker-registry-ui/issues/80)). +- One interface for many registries **standard interface**. +- Share your docker registry with query parameter `url` (e.g. `https://joxit.dev/docker-registry-ui/demo?url=https://registry.example.com`) **standard interface**. +- Use `joxit/docker-registry-ui:static` as reverse proxy (with `REGISTRY_URL` environment variable) to your docker registry (This will avoid CORS) **static interface**. +- Add Title when using `REGISTRY_URL` (see [#28](https://github.com/Joxit/docker-registry-ui/issues/28)) **static interface**. +- Customise docker pull command on static registry UI (see [#71](https://github.com/Joxit/docker-registry-ui/issues/71)) **static interface**. ## Getting Started @@ -103,7 +112,7 @@ To run the docker and see the website on your 80 port, try this: docker run -d -p 80:80 joxit/docker-registry-ui ``` -#### Run the static docker +#### Run the static interface Some env options are available for use this interface for only one server. @@ -120,7 +129,7 @@ docker run -d -p 80:80 -e URL=http://127.0.0.1:5000 -e DELETE_IMAGES=true joxit/ ``` Example with `REGISTRY_URL`, this will add a proxy to your registry. -Your registry will be accessible here : `http://127.0.0.1/v2`, this will avoid CORS errors (see #25). +Your registry will be accessible here : `http://127.0.0.1/v2`, this will avoid CORS errors (see [#25](https://github.com/Joxit/docker-registry-ui/issues/25#issuecomment-360522487)). Be careful, `joxit/docker-registry-ui` and `registry:2` will communicate, both containers should be in the same network or use your private IP. ```sh @@ -207,4 +216,4 @@ auth: - [Use docker-registry-ui with traefik](https://github.com/Joxit/docker-registry-ui/tree/master/examples/traefik) - [Use docker-registry-ui with docker registry and Amazon s3 (#75)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-75) - [FIX revproxy to registry does not work when published under non-root url (#73)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-73) -- [Use docker-registry-ui with HTTPS (#20)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-20) \ No newline at end of file +- [Use docker-registry-ui with HTTPS (#20)](https://github.com/Joxit/docker-registry-ui/tree/master/examples/issue-20) diff --git a/_config.yml b/_config.yml index 94a1cb2..dbdc874 100644 --- a/_config.yml +++ b/_config.yml @@ -1,7 +1,13 @@ -title: Docker Registry v2 User Interface +title: Docker Registry User Interface +description: The simplest and most complete UI for your private registry! url: https://joxit.dev/docker-registry-ui google_analytics: UA-99119327-1 theme: jekyll-theme-cayman author: Jones Magloire twitter: - username: Joxit \ No newline at end of file + username: Joxit +defaults: + - scope: + path: "" + values: + image: /screenshot.png \ No newline at end of file diff --git a/demo/index.html b/demo/index.html index 66d82f0..4a31b05 100644 --- a/demo/index.html +++ b/demo/index.html @@ -24,15 +24,15 @@ - - + + - + - Demo | Docker Registry UI + Live Demo | Docker Registry User Interface diff --git a/dist/scripts/docker-registry-ui-static.js b/dist/scripts/docker-registry-ui-static.js index 2d3431f..882b41f 100644 --- a/dist/scripts/docker-registry-ui-static.js +++ b/dist/scripts/docker-registry-ui-static.js @@ -15,4 +15,4 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -function Http(){this.oReq=new XMLHttpRequest,this.oReq.hasHeader=Http.hasHeader,this.oReq.getErrorMessage=Http.getErrorMessage,this._events={},this._headers={}}Http.prototype.addEventListener=function(t,e){this._events[t]=e;const r=this;switch(t){case"loadend":r.oReq.addEventListener("loadend",function(){if(401==this.status){const t=new XMLHttpRequest;for(key in t.open(r._method,r._url),r._events)t.addEventListener(key,r._events[key]);for(key in r._headers)t.setRequestHeader(key,r._headers[key]);t.withCredentials=!0,t.hasHeader=Http.hasHeader,t.getErrorMessage=Http.getErrorMessage,t.send()}else e.bind(this)()});break;case"load":r.oReq.addEventListener("load",function(){401!==this.status&&e.bind(this)()});break;default:r.oReq.addEventListener(t,function(){e.bind(this)()})}},Http.prototype.setRequestHeader=function(t,e){this.oReq.setRequestHeader(t,e),this._headers[t]=e},Http.prototype.open=function(t,e){this._method=t,this._url=e,this.oReq.open(t,e)},Http.prototype.send=function(){this.oReq.send()},Http.hasHeader=function(t){return this.getAllResponseHeaders().split("\n").some(function(e){return new RegExp("^"+t+":","i").test(e)})},Http.getErrorMessage=function(){return registryUI.url()&®istryUI.url().match("^http://")&&"https:"===window.location.protocol?"Mixed Content: The page at `"+window.location.origin+"` was loaded over HTTPS, but requested an insecure server endpoint `"+registryUI.url()+"`. This request has been blocked; the content must be served over HTTPS.":registryUI.url()?this.withCredentials&&!this.hasHeader("Access-Control-Allow-Credentials")?"The `Access-Control-Allow-Credentials` header in the response is missing and must be set to `true` when the request's credentials mode is on. Origin `"+registryUI.url()+"` is therefore not allowed access.":"An error occured: Check your connection and your registry must have `Access-Control-Allow-Origin` header set to `"+window.location.origin+"`":"Incorrect server endpoint."};var registryUI={url:function(){var t="${URL}";return t||((t=window.location.origin+window.location.pathname).endsWith("/")?t.substr(0,t.length-1):t)},name:function(){return"${REGISTRY_TITLE}"},pullUrl:"${PULL_URL}",isImageRemoveActivated:!0,catalog:{},taglist:{},taghistory:{}};window.addEventListener("DOMContentLoaded",function(){riot.mount("*")}),riot.tag2("app",'
',"","",function(t){registryUI.appTag=this,route.base("#!"),route("",function(){route.routeName="home",registryUI.catalog.display&&(registryUI.catalog.loadend=!1),registryUI.appTag.update()}),route("/taglist/*",function(t){route.routeName="taglist",registryUI.taglist.name=t,registryUI.taglist.display&&(registryUI.taglist.loadend=!1),registryUI.appTag.update()}),route("/taghistory/image/*/tag/*",function(t,e){route.routeName="taghistory",registryUI.taghistory.image=t,registryUI.taghistory.tag=e,registryUI.taghistory.display&&(registryUI.taghistory.loadend=!1),registryUI.appTag.update()}),registryUI.home=function(){"home"==route.routeName?registryUI.catalog.display:route("")},registryUI.taghistory.go=function(t,e){route("/taghistory/image/"+t+"/tag/"+e)},registryUI.snackbar=function(t,e){registryUI.appTag.tags["material-snackbar"].addToast({message:t,isError:e},15e3)},registryUI.errorSnackbar=function(t){return registryUI.snackbar(t,!0)},registryUI.cleanName=function(){const t=registryUI.pullUrl||registryUI.url()&®istryUI.url().length>0&®istryUI.url()||window.location.host;return t?t.startsWith("http")?t.replace(/https?:\/\//,""):t:""},route.parser(null,function(t,e){const r=e.replace(/\?/g,"\\?").replace(/\*/g,"([^?#]+?)").replace(/\.\./,".*"),i=new RegExp("^"+r+"$"),s=t.match(i);if(s)return s.slice(1)}),registryUI.isDigit=function(t){return t>="0"&&t<="9"},registryUI.DockerImage=function(t,e){this.name=t,this.tag=e,riot.observable(this),this.on("get-size",function(){return void 0!==this.size?this.trigger("size",this.size):this.fillInfo()}),this.on("get-sha256",function(){return void 0!==this.size?this.trigger("sha256",this.sha256):this.fillInfo()}),this.on("get-date",function(){return void 0!==this.date?this.trigger("date",this.date):this.fillInfo()})},registryUI.DockerImage._tagReduce=function(t,e){return t.length>0&®istryUI.isDigit(t[t.length-1].charAt(0))==registryUI.isDigit(e)?t[t.length-1]+=e:t.push(e),t},registryUI.DockerImage.compare=function(t,e){const r=t.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce,[]),i=e.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce,[]);for(var s=0;s send {typeof opts.item === ⁗string⁗ ? opts.item : opts.item.repo}
{opts.item.images && opts.item.images.length} images expand_more
',"","",function(t){this.on("mount",function(){const t=this,e=this.tags["material-card"];e&&(e.launch=function(t){e.tags["material-waves"].trigger("launch",t)},this.item.images&&1===this.item.images.length&&(this.item=this.item.images[0]),e.root.onclick=function(e){t.item.repo?(t.expanded=!t.expanded,t.update({expanded:t.expanded,expanding:!0}),setTimeout(function(){t.update({expanded:t.expanded,expanding:!1})},50)):registryUI.taglist.go(t.item)})})}),riot.tag2("catalog",'

Repositories of {registryUI.name()}
{registryUI.catalog.length} images

',"","",function(t){registryUI.catalog.instance=this,registryUI.catalog.display=function(){registryUI.catalog.repositories=[];const t=new Http;t.addEventListener("load",function(){registryUI.catalog.repositories=[],200==this.status?(registryUI.catalog.repositories=JSON.parse(this.responseText).repositories||[],registryUI.catalog.repositories.sort(),registryUI.catalog.length=registryUI.catalog.repositories.length,registryUI.catalog.repositories=registryUI.catalog.repositories.reduce(function(t,e){const r=e.indexOf("/");if(r>0){const i=e.substring(0,r)+"/";return 0!=t.length&&t[t.length-1].repo==i||t.push({repo:i,images:[]}),t[t.length-1].images.push(e),t}return t.push(e),t},[])):404==this.status?registryUI.snackbar("Server not found",!0):registryUI.snackbar(this.responseText)}),t.addEventListener("error",function(){registryUI.snackbar(this.getErrorMessage(),!0),registryUI.catalog.repositories=[]}),t.addEventListener("loadend",function(){registryUI.catalog.loadend=!0,registryUI.catalog.instance.update()}),t.open("GET",registryUI.url()+"/v2/_catalog?n=100000"),t.send()},registryUI.catalog.display()}),riot.tag2("copy-to-clipboard",' content_copy ',"","",function(t){this.dockerCmd="docker pull "+registryUI.cleanName()+"/"+t.image.name+":"+t.image.tag,this.copy=function(){const t=this.refs.input;t.style.display="block",t.select(),document.execCommand("copy"),t.style.display="none",registryUI.snackbar("`"+this.dockerCmd+"` has been copied to clipboard.")}}),riot.tag2("image-date",'
{this.dateFormat(this.date)} ago
',"","",function(t){const e=this;this.dateFormat=function(t){if(void 0===t)return"";const e=["a second","seconds","a minute","minutes","an hour","hours","a day","days","a month","months","a year","years"],r=[1,60,3600,86400,2592e3,31104e3,1/0],i=(new Date-t)/1e3;for(var s=0;s=i)return e[2*s];if(r[s+1]>i)return Math.floor(i/r[s])+" "+e[2*s+1]}},t.image.on("creation-date",function(t){e.date=t,e.localDate=t.toLocaleString(),e.update()}),t.image.trigger("get-date")}),riot.tag2("image-size",'
{registryUI.bytesToSize(this.size)}
',"","",function(t){const e=this;t.image.on("size",function(t){e.size=t,e.update()}),t.image.trigger("get-size")}),riot.tag2("image-tag",'
{opts.image.tag}
',"","",function(t){const e=this;t.image.on("sha256",function(t){e.sha256=t.substring(0,19),e.update()}),t.image.trigger("get-sha256")}),riot.tag2("remove-image",' delete ',"","",function(t){const e=this;this.on("update",function(){!this.opts.multiDelete&&this.tags["material-checkbox"].checked&&this.tags["material-checkbox"].toggle()}),this.on("mount",function(){this.delete=this.tags["material-button"].root.onclick=function(t){const r=e.opts.image.name,i=e.opts.image.tag,s=new Http;s.addEventListener("loadend",function(){if(registryUI.taglist.go(r),200==this.status){if(!this.hasHeader("Docker-Content-Digest"))return void registryUI.errorSnackbar("You need to add Access-Control-Expose-Headers: ['Docker-Content-Digest'] in your server configuration.");const e=this.getResponseHeader("Docker-Content-Digest"),s=new Http;s.addEventListener("loadend",function(){200==this.status||202==this.status?(registryUI.taglist.display(),registryUI.snackbar("Deleting "+r+":"+i+" image. Run `registry garbage-collect config.yml` on your registry")):404==this.status?t||registryUI.errorSnackbar("Digest not found"):registryUI.snackbar(this.responseText)}),s.open("DELETE",registryUI.url()+"/v2/"+r+"/manifests/"+e),s.setRequestHeader("Accept","application/vnd.docker.distribution.manifest.v2+json"),s.addEventListener("error",function(){registryUI.errorSnackbar("An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: ['DELETE'].")}),s.send()}else 404==this.status?registryUI.errorSnackbar("Manifest for "+r+":"+i+" not found"):registryUI.snackbar(this.responseText)}),s.open("HEAD",registryUI.url()+"/v2/"+r+"/manifests/"+i),s.setRequestHeader("Accept","application/vnd.docker.distribution.manifest.v2+json"),s.send()},this.tags["material-checkbox"].on("toggle",function(){registryUI.taglist.instance.trigger("toggle-remove-image",this.checked)})})}),riot.tag2("tag-history-button",' history ',"","",function(t){this.on("mount",function(){const t=this;this.refs.button.root.onclick=function(){registryUI.taghistory._image=t.opts.image,registryUI.taghistory.go(t.opts.image.name,t.opts.image.tag)}}),this.update()}),riot.tag2("tag-history-element",'
{this.getIcon(entry.key)}

{entry.key.replace(\'_\', \' \')}

{entry.value}
{e}
',"",'class="{entry.key}"',function(t){this.getIcon=function(t){switch(t){case"architecture":return"memory";case"created":return"event";case"docker_version":return"";case"os":return"developer_board";case"Cmd":return"launch";case"Entrypoint":return"input";case"Env":return"notes";case"Labels":return"label";case"User":return"face";case"Volumes":return"storage";case"WorkingDir":return"home";case"author":return"account_circle";case"id":case"digest":return"settings_ethernet";case"created_by":return"build";case"size":return"get_app";case"ExposedPorts":return"router"}}}),riot.tag2("tag-history",'
arrow_back

History of {registryUI.taghistory.image}:{registryUI.taghistory.tag} history

',"","",function(t){const e=this,r=function(t){switch(t){case"id":return 1;case"created":return 2;case"created_by":return 3;case"size":return 4;case"os":return 5;case"architecture":return 6;case"linux":return 7;case"docker_version":return 8;default:return 10}},i=function(t,e){return r(t.key)-r(e.key)},s=function(t,e){switch(t){case"created":return new Date(e).toLocaleString();case"created_by":const r=e.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+)/);return r&&r[1]||"RUN";case"size":return registryUI.bytesToSize(e);case"Entrypoint":case"Cmd":return(e||[]).join(" ");case"Labels":return Object.keys(e||{}).map(function(t){return e[t]?t+"="+e[t]:""});case"Volumes":case"ExposedPorts":return Object.keys(e)}return e||""},a=function(t){function r(t){const e=[];for(const r in t)if(t.hasOwnProperty(r)&&"empty_layer"!=r){const i=t[r],a={key:r,value:s(r,i)};e.push(a)}return e.sort(i)}e.elements.push(r(function(t){const e=["architecture","User","created","docker_version","os","Cmd","Entrypoint","Env","Labels","User","Volumes","WorkingDir","author","id","ExposedPorts"].reduce(function(e,r){const i=t[r]||t.config[r];return i&&(e[r]=i),e},{});return!e.author&&e.Labels&&e.Labels.maintainer&&(e.author=t.config.Labels.maintainer,delete e.Labels.maintainer),e}(t))),t.history.reverse().forEach(function(t){e.elements.push(r(t))}),registryUI.taghistory.loadend=!0,e.update()};registryUI.taghistory.display=function(){e.elements=[];const t=registryUI.taghistory._image&®istryUI.taghistory._image.blobs;if(t)return window.scrollTo(0,0),a(t);const r=new registryUI.DockerImage(registryUI.taghistory.image,registryUI.taghistory.tag);r.fillInfo(),r.on("blobs",a)},this.on("mount",function(){e.refs["tag-history-tag"].tags["material-button"].root.onclick=function(){registryUI.taglist.go(registryUI.taghistory.image)}}),registryUI.taghistory.display(),e.update()}),riot.tag2("taglist",'
arrow_back

Tags of {registryUI.name() + \'/\' + registryUI.taglist.name}
{registryUI.taglist.tags.length} tags

Repository Creation date Size Tag History delete
{image.name}
',"","",function(t){var e=registryUI.taglist.instance=this;this.multiDelete=!1,this.toDelete=0,this.on("delete",function(){registryUI.isImageRemoveActivated&&this.multiDelete}),this.on("multi-delete",function(){registryUI.isImageRemoveActivated&&(this.multiDelete=!this.multiDelete)}),this.on("toggle-remove-image",function(t){t?this.toDelete++:this.toDelete--,this.toDelete<=1&&this.update()}),this._getRemoveImageTags=function(){var t=e.refs["taglist-tag"].tags["remove-image"];return t instanceof Array||(t=[t]),t},registryUI.taglist.bulkDelete=function(){e.multiDelete&&e.toDelete>0&&e._getRemoveImageTags().filter(function(t){return t.tags["material-checkbox"].checked}).forEach(function(t){t.delete(!0)})},this.on("mount",function(){var t=this.tags["material-card"].refs["remove-tag-checkbox"].toggle;this.tags["material-card"].refs["remove-tag-checkbox"].toggle=function(r){r.altKey?e._getRemoveImageTags().filter(function(t){return!t.tags["material-checkbox"].checked}).forEach(function(t){t.tags["material-checkbox"].toggle()}):t()},this.tags["material-card"].refs["remove-tag-checkbox"].on("toggle",function(){registryUI.taglist.instance.multiDelete=this.checked,registryUI.taglist.instance.update()})}),registryUI.taglist.display=function(){if(registryUI.taglist.tags=[],"taglist"==route.routeName){const t=new Http;registryUI.taglist.instance.update(),t.addEventListener("load",function(){registryUI.taglist.tags=[],200==this.status?(registryUI.taglist.tags=JSON.parse(this.responseText).tags||[],registryUI.taglist.tags=registryUI.taglist.tags.map(function(t){return new registryUI.DockerImage(registryUI.taglist.name,t)}).sort(registryUI.DockerImage.compare),console.log("end sort")):404==this.status?registryUI.snackbar("Server not found",!0):registryUI.snackbar(this.responseText,!0)}),t.addEventListener("error",function(){registryUI.snackbar(this.getErrorMessage(),!0),registryUI.taglist.tags=[]}),t.addEventListener("loadend",function(){registryUI.taglist.loadend=!0,registryUI.taglist.instance.update()}),t.open("GET",registryUI.url()+"/v2/"+registryUI.taglist.name+"/tags/list"),t.send(),registryUI.taglist.asc=!0}},registryUI.taglist.display(),registryUI.taglist.instance.update(),registryUI.taglist.reverse=function(){registryUI.taglist.asc?(registryUI.taglist.tags.reverse(),registryUI.taglist.asc=!1):(registryUI.taglist.tags.sort(registryUI.DockerImage.compare),registryUI.taglist.asc=!0),registryUI.taglist.instance.update()}}); \ No newline at end of file +function Http(){this.oReq=new XMLHttpRequest,this.oReq.hasHeader=Http.hasHeader,this.oReq.getErrorMessage=Http.getErrorMessage,this._events={},this._headers={}}Http.prototype.addEventListener=function(t,e){this._events[t]=e;const r=this;switch(t){case"loadend":r.oReq.addEventListener("loadend",function(){if(401==this.status){const t=new XMLHttpRequest;for(key in t.open(r._method,r._url),r._events)t.addEventListener(key,r._events[key]);for(key in r._headers)t.setRequestHeader(key,r._headers[key]);t.withCredentials=!0,t.hasHeader=Http.hasHeader,t.getErrorMessage=Http.getErrorMessage,t.send()}else e.bind(this)()});break;case"load":r.oReq.addEventListener("load",function(){401!==this.status&&e.bind(this)()});break;default:r.oReq.addEventListener(t,function(){e.bind(this)()})}},Http.prototype.setRequestHeader=function(t,e){this.oReq.setRequestHeader(t,e),this._headers[t]=e},Http.prototype.open=function(t,e){this._method=t,this._url=e,this.oReq.open(t,e)},Http.prototype.send=function(){this.oReq.send()},Http.hasHeader=function(t){return this.getAllResponseHeaders().split("\n").some(function(e){return new RegExp("^"+t+":","i").test(e)})},Http.getErrorMessage=function(){return registryUI.url()&®istryUI.url().match("^http://")&&"https:"===window.location.protocol?"Mixed Content: The page at `"+window.location.origin+"` was loaded over HTTPS, but requested an insecure server endpoint `"+registryUI.url()+"`. This request has been blocked; the content must be served over HTTPS.":registryUI.url()?this.withCredentials&&!this.hasHeader("Access-Control-Allow-Credentials")?"The `Access-Control-Allow-Credentials` header in the response is missing and must be set to `true` when the request's credentials mode is on. Origin `"+registryUI.url()+"` is therefore not allowed access.":"An error occured: Check your connection and your registry must have `Access-Control-Allow-Origin` header set to `"+window.location.origin+"`":"Incorrect server endpoint."};var registryUI={url:function(){var t="${URL}";return t||((t=window.location.origin+window.location.pathname).endsWith("/")?t.substr(0,t.length-1):t)},name:function(){return"${REGISTRY_TITLE}"},pullUrl:"${PULL_URL}",isImageRemoveActivated:!0,catalog:{},taglist:{},taghistory:{}};window.addEventListener("DOMContentLoaded",function(){riot.mount("*")}),riot.tag2("app",'
',"","",function(t){registryUI.appTag=this,route.base("#!"),route("",function(){route.routeName="home",registryUI.catalog.display&&(registryUI.catalog.loadend=!1),registryUI.appTag.update()}),route("/taglist/*",function(t){route.routeName="taglist",registryUI.taglist.name=t,registryUI.taglist.display&&(registryUI.taglist.loadend=!1),registryUI.appTag.update()}),route("/taghistory/image/*/tag/*",function(t,e){route.routeName="taghistory",registryUI.taghistory.image=t,registryUI.taghistory.tag=e,registryUI.taghistory.display&&(registryUI.taghistory.loadend=!1),registryUI.appTag.update()}),registryUI.home=function(){"home"==route.routeName?registryUI.catalog.display:route("")},registryUI.taghistory.go=function(t,e){route("/taghistory/image/"+t+"/tag/"+e)},registryUI.snackbar=function(t,e){registryUI.appTag.tags["material-snackbar"].addToast({message:t,isError:e},15e3)},registryUI.errorSnackbar=function(t){return registryUI.snackbar(t,!0)},registryUI.cleanName=function(){const t=registryUI.pullUrl||registryUI.url()&®istryUI.url().length>0&®istryUI.url()||window.location.host;return t?t.startsWith("http")?t.replace(/https?:\/\//,""):t:""},route.parser(null,function(t,e){const r=e.replace(/\?/g,"\\?").replace(/\*/g,"([^?#]+?)").replace(/\.\./,".*"),i=new RegExp("^"+r+"$"),s=t.match(i);if(s)return s.slice(1)}),registryUI.isDigit=function(t){return t>="0"&&t<="9"},registryUI.DockerImage=function(t,e){this.name=t,this.tag=e,riot.observable(this),this.on("get-size",function(){return void 0!==this.size?this.trigger("size",this.size):this.fillInfo()}),this.on("get-sha256",function(){return void 0!==this.size?this.trigger("sha256",this.sha256):this.fillInfo()}),this.on("get-date",function(){return void 0!==this.date?this.trigger("date",this.date):this.fillInfo()})},registryUI.DockerImage._tagReduce=function(t,e){return t.length>0&®istryUI.isDigit(t[t.length-1].charAt(0))==registryUI.isDigit(e)?t[t.length-1]+=e:t.push(e),t},registryUI.DockerImage.compare=function(t,e){const r=t.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce,[]),i=e.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce,[]);for(var s=0;s send {typeof opts.item === ⁗string⁗ ? opts.item : opts.item.repo}
{opts.item.images && opts.item.images.length} images expand_more
',"","",function(t){this.on("mount",function(){const t=this,e=this.tags["material-card"];e&&(e.launch=function(t){e.tags["material-waves"].trigger("launch",t)},this.item.images&&1===this.item.images.length&&(this.item=this.item.images[0]),e.root.onclick=function(e){t.item.repo?(t.expanded=!t.expanded,t.update({expanded:t.expanded,expanding:!0}),setTimeout(function(){t.update({expanded:t.expanded,expanding:!1})},50)):registryUI.taglist.go(t.item)})})}),riot.tag2("catalog",'

Repositories of {registryUI.name()}
{registryUI.catalog.length} images

',"","",function(t){registryUI.catalog.instance=this,registryUI.catalog.display=function(){registryUI.catalog.repositories=[];const t=new Http;t.addEventListener("load",function(){registryUI.catalog.repositories=[],200==this.status?(registryUI.catalog.repositories=JSON.parse(this.responseText).repositories||[],registryUI.catalog.repositories.sort(),registryUI.catalog.length=registryUI.catalog.repositories.length,registryUI.catalog.repositories=registryUI.catalog.repositories.reduce(function(t,e){const r=e.indexOf("/");if(r>0){const i=e.substring(0,r)+"/";return 0!=t.length&&t[t.length-1].repo==i||t.push({repo:i,images:[]}),t[t.length-1].images.push(e),t}return t.push(e),t},[])):404==this.status?registryUI.snackbar("Server not found",!0):registryUI.snackbar(this.responseText)}),t.addEventListener("error",function(){registryUI.snackbar(this.getErrorMessage(),!0),registryUI.catalog.repositories=[]}),t.addEventListener("loadend",function(){registryUI.catalog.loadend=!0,registryUI.catalog.instance.update()}),t.open("GET",registryUI.url()+"/v2/_catalog?n=100000"),t.send()},registryUI.catalog.display()}),riot.tag2("copy-to-clipboard",' content_copy ',"","",function(t){this.dockerCmd="docker pull "+registryUI.cleanName()+"/"+t.image.name+":"+t.image.tag,this.copy=function(){const t=this.refs.input;t.style.display="block",t.select(),document.execCommand("copy"),t.style.display="none",registryUI.snackbar("`"+this.dockerCmd+"` has been copied to clipboard.")}}),riot.tag2("image-date",'
{this.dateFormat(this.date)} ago
',"","",function(t){const e=this;this.dateFormat=function(t){if(void 0===t)return"";const e=["a second","seconds","a minute","minutes","an hour","hours","a day","days","a month","months","a year","years"],r=[1,60,3600,86400,2592e3,31104e3,1/0],i=(new Date-t)/1e3;for(var s=0;s=i)return e[2*s];if(r[s+1]>i)return Math.floor(i/r[s])+" "+e[2*s+1]}},t.image.on("creation-date",function(t){e.date=t,e.localDate=t.toLocaleString(),e.update()}),t.image.trigger("get-date")}),riot.tag2("image-size",'
{registryUI.bytesToSize(this.size)}
',"","",function(t){const e=this;t.image.on("size",function(t){e.size=t,e.update()}),t.image.trigger("get-size")}),riot.tag2("image-tag",'
{opts.image.tag}
',"","",function(t){const e=this;t.image.on("sha256",function(t){e.sha256=t.substring(0,19),e.update()}),t.image.trigger("get-sha256")}),riot.tag2("remove-image",' delete ',"","",function(t){const e=this;this.on("update",function(){!this.opts.multiDelete&&this.tags["material-checkbox"].checked&&this.tags["material-checkbox"].toggle()}),this.on("mount",function(){this.delete=this.tags["material-button"].root.onclick=function(t){const r=e.opts.image.name,i=e.opts.image.tag,s=new Http;s.addEventListener("loadend",function(){if(registryUI.taglist.go(r),200==this.status){if(!this.hasHeader("Docker-Content-Digest"))return void registryUI.errorSnackbar("You need to add Access-Control-Expose-Headers: ['Docker-Content-Digest'] in your server configuration.");const e=this.getResponseHeader("Docker-Content-Digest"),s=new Http;s.addEventListener("loadend",function(){200==this.status||202==this.status?(registryUI.taglist.display(),registryUI.snackbar("Deleting "+r+":"+i+" image. Run `registry garbage-collect config.yml` on your registry")):404==this.status?t||registryUI.errorSnackbar("Digest not found"):registryUI.snackbar(this.responseText)}),s.open("DELETE",registryUI.url()+"/v2/"+r+"/manifests/"+e),s.setRequestHeader("Accept","application/vnd.docker.distribution.manifest.v2+json"),s.addEventListener("error",function(){registryUI.errorSnackbar("An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: ['DELETE'].")}),s.send()}else 404==this.status?registryUI.errorSnackbar("Manifest for "+r+":"+i+" not found"):registryUI.snackbar(this.responseText)}),s.open("HEAD",registryUI.url()+"/v2/"+r+"/manifests/"+i),s.setRequestHeader("Accept","application/vnd.docker.distribution.manifest.v2+json"),s.send()},this.tags["material-checkbox"].on("toggle",function(){registryUI.taglist.instance.trigger("toggle-remove-image",this.checked)})})}),riot.tag2("tag-history-button",' history ',"","",function(t){this.on("mount",function(){const t=this;this.refs.button.root.onclick=function(){registryUI.taghistory._image=t.opts.image,registryUI.taghistory.go(t.opts.image.name,t.opts.image.tag)}}),this.update()}),riot.tag2("tag-history-element",'
{this.getIcon(entry.key)}

{entry.key.replace(\'_\', \' \')}

{entry.value}
{e}
',"",'class="{entry.key}"',function(t){this.getIcon=function(t){switch(t){case"architecture":return"memory";case"created":return"event";case"docker_version":return"";case"os":return"developer_board";case"Cmd":return"launch";case"Entrypoint":return"input";case"Env":return"notes";case"Labels":return"label";case"User":return"face";case"Volumes":return"storage";case"WorkingDir":return"home";case"author":return"account_circle";case"id":case"digest":return"settings_ethernet";case"created_by":return"build";case"size":return"get_app";case"ExposedPorts":return"router"}}}),riot.tag2("tag-history",'
arrow_back

History of {registryUI.taghistory.image}:{registryUI.taghistory.tag} history

',"","",function(t){const e=this,r=function(t){switch(t){case"id":return 1;case"created":return 2;case"created_by":return 3;case"size":return 4;case"os":return 5;case"architecture":return 6;case"linux":return 7;case"docker_version":return 8;default:return 10}},i=function(t,e){return r(t.key)-r(e.key)},s=function(t,e){switch(t){case"created":return new Date(e).toLocaleString();case"created_by":const r=e.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+)/);return r&&r[1]||"RUN";case"size":return registryUI.bytesToSize(e);case"Entrypoint":case"Cmd":return(e||[]).join(" ");case"Labels":return Object.keys(e||{}).map(function(t){return e[t]?t+"="+e[t]:""});case"Volumes":case"ExposedPorts":return Object.keys(e)}return e||""},a=function(t){function r(t){const e=[];for(const r in t)if(t.hasOwnProperty(r)&&"empty_layer"!=r){const i=t[r],a={key:r,value:s(r,i)};e.push(a)}return e.sort(i)}e.elements.push(r(function(t){const e=["architecture","User","created","docker_version","os","Cmd","Entrypoint","Env","Labels","User","Volumes","WorkingDir","author","id","ExposedPorts"].reduce(function(e,r){const i=t[r]||t.config[r];return i&&(e[r]=i),e},{});return!e.author&&e.Labels&&e.Labels.maintainer&&(e.author=t.config.Labels.maintainer,delete e.Labels.maintainer),e}(t))),t.history.reverse().forEach(function(t){e.elements.push(r(t))}),registryUI.taghistory.loadend=!0,e.update()};registryUI.taghistory.display=function(){e.elements=[];const t=registryUI.taghistory._image&®istryUI.taghistory._image.blobs;if(t)return window.scrollTo(0,0),a(t);const r=new registryUI.DockerImage(registryUI.taghistory.image,registryUI.taghistory.tag);r.fillInfo(),r.on("blobs",a)},this.on("mount",function(){e.refs["tag-history-tag"].tags["material-button"].root.onclick=function(){registryUI.taglist.go(registryUI.taghistory.image)}}),registryUI.taghistory.display(),e.update()}),riot.tag2("taglist",'
arrow_back

Tags of {registryUI.name() + \'/\' + registryUI.taglist.name}
{registryUI.taglist.tags.length} tags

Repository Creation date Size Tag History delete
{image.name}
',"","",function(t){var e=registryUI.taglist.instance=this;this.multiDelete=!1,this.toDelete=0,this.on("delete",function(){registryUI.isImageRemoveActivated&&this.multiDelete}),this.on("multi-delete",function(){registryUI.isImageRemoveActivated&&(this.multiDelete=!this.multiDelete)}),this.on("toggle-remove-image",function(t){t?this.toDelete++:this.toDelete--,this.toDelete<=1&&this.update()}),this._getRemoveImageTags=function(){var t=e.refs["taglist-tag"].tags["remove-image"];return t instanceof Array||(t=[t]),t},registryUI.taglist.bulkDelete=function(){e.multiDelete&&e.toDelete>0&&e._getRemoveImageTags().filter(function(t){return t.tags["material-checkbox"].checked}).forEach(function(t){t.delete(!0)})},this.on("mount",function(){var t=this.tags["material-card"].refs["remove-tag-checkbox"].toggle;this.tags["material-card"].refs["remove-tag-checkbox"].toggle=function(r){r.altKey?e._getRemoveImageTags().filter(function(t){return!t.tags["material-checkbox"].checked}).forEach(function(t){t.tags["material-checkbox"].toggle()}):t()},this.tags["material-card"].refs["remove-tag-checkbox"].on("toggle",function(){registryUI.taglist.instance.multiDelete=this.checked,registryUI.taglist.instance.update()})}),registryUI.taglist.display=function(){if(registryUI.taglist.tags=[],"taglist"==route.routeName){const t=new Http;registryUI.taglist.instance.update(),t.addEventListener("load",function(){registryUI.taglist.tags=[],200==this.status?(registryUI.taglist.tags=JSON.parse(this.responseText).tags||[],registryUI.taglist.tags=registryUI.taglist.tags.map(function(t){return new registryUI.DockerImage(registryUI.taglist.name,t)}).sort(registryUI.DockerImage.compare)):404==this.status?registryUI.snackbar("Server not found",!0):registryUI.snackbar(this.responseText,!0)}),t.addEventListener("error",function(){registryUI.snackbar(this.getErrorMessage(),!0),registryUI.taglist.tags=[]}),t.addEventListener("loadend",function(){registryUI.taglist.loadend=!0,registryUI.taglist.instance.update()}),t.open("GET",registryUI.url()+"/v2/"+registryUI.taglist.name+"/tags/list"),t.send(),registryUI.taglist.asc=!0}},registryUI.taglist.display(),registryUI.taglist.instance.update(),registryUI.taglist.reverse=function(){registryUI.taglist.asc?(registryUI.taglist.tags.reverse(),registryUI.taglist.asc=!1):(registryUI.taglist.tags.sort(registryUI.DockerImage.compare),registryUI.taglist.asc=!0),registryUI.taglist.instance.update()}}); \ No newline at end of file diff --git a/dist/scripts/docker-registry-ui.js b/dist/scripts/docker-registry-ui.js index dfe2bec..1c272d3 100644 --- a/dist/scripts/docker-registry-ui.js +++ b/dist/scripts/docker-registry-ui.js @@ -15,4 +15,4 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -function Http(){this.oReq=new XMLHttpRequest,this.oReq.hasHeader=Http.hasHeader,this.oReq.getErrorMessage=Http.getErrorMessage,this._events={},this._headers={}}Http.prototype.addEventListener=function(e,t){this._events[e]=t;const r=this;switch(e){case"loadend":r.oReq.addEventListener("loadend",function(){if(401==this.status){const e=new XMLHttpRequest;for(key in e.open(r._method,r._url),r._events)e.addEventListener(key,r._events[key]);for(key in r._headers)e.setRequestHeader(key,r._headers[key]);e.withCredentials=!0,e.hasHeader=Http.hasHeader,e.getErrorMessage=Http.getErrorMessage,e.send()}else t.bind(this)()});break;case"load":r.oReq.addEventListener("load",function(){401!==this.status&&t.bind(this)()});break;default:r.oReq.addEventListener(e,function(){t.bind(this)()})}},Http.prototype.setRequestHeader=function(e,t){this.oReq.setRequestHeader(e,t),this._headers[e]=t},Http.prototype.open=function(e,t){this._method=e,this._url=t,this.oReq.open(e,t)},Http.prototype.send=function(){this.oReq.send()},Http.hasHeader=function(e){return this.getAllResponseHeaders().split("\n").some(function(t){return new RegExp("^"+e+":","i").test(t)})},Http.getErrorMessage=function(){return registryUI.url()&®istryUI.url().match("^http://")&&"https:"===window.location.protocol?"Mixed Content: The page at `"+window.location.origin+"` was loaded over HTTPS, but requested an insecure server endpoint `"+registryUI.url()+"`. This request has been blocked; the content must be served over HTTPS.":registryUI.url()?this.withCredentials&&!this.hasHeader("Access-Control-Allow-Credentials")?"The `Access-Control-Allow-Credentials` header in the response is missing and must be set to `true` when the request's credentials mode is on. Origin `"+registryUI.url()+"` is therefore not allowed access.":"An error occured: Check your connection and your registry must have `Access-Control-Allow-Origin` header set to `"+window.location.origin+"`":"Incorrect server endpoint."};var registryUI={URL_QUERY_PARAM_REGEX:/[&?]url=/,URL_PARAM_REGEX:/^url=/};registryUI.name=registryUI.url=function(e){if(!registryUI._url){const e=registryUI.getUrlQueryParam();if(e)try{return registryUI._url=registryUI.decodeURI(e),registryUI._url}catch(e){console.log(e)}registryUI._url=registryUI.getRegistryServer(0)}return registryUI._url},registryUI.getRegistryServer=function(e){try{const t=JSON.parse(localStorage.getItem("registryServer"));if(t instanceof Array)return isNaN(e)?t.map(function(e){return e.trim().replace(/\/*$/,"")}):t[e]}catch(e){}return isNaN(e)?[]:""},registryUI.addServer=function(e){const t=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,""),-1==t.indexOf(e)&&(t.push(e),registryUI._url||registryUI.updateHistory(e),localStorage.setItem("registryServer",JSON.stringify(t)))},registryUI.changeServer=function(e){var t=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,"");const r=t.indexOf(e);-1!=r&&(t.splice(r,1),t=[e].concat(t),registryUI.updateHistory(e),localStorage.setItem("registryServer",JSON.stringify(t)))},registryUI.removeServer=function(e){const t=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,"");const r=t.indexOf(e);-1!=r&&(t.splice(r,1),localStorage.setItem("registryServer",JSON.stringify(t)),e==registryUI.url()&&(registryUI.updateHistory(registryUI.getRegistryServer(0)),route("")))},registryUI.updateHistory=function(e){history.pushState(null,"",(e?"?url="+registryUI.encodeURI(e):"?")+window.location.hash),registryUI._url=e},registryUI.getUrlQueryParam=function(){const e=window.location.search;if(registryUI.URL_QUERY_PARAM_REGEX.test(e)){const t=e.split(/^\?|&/).find(function(e){return e&®istryUI.URL_PARAM_REGEX.test(e)});return t?t.replace(registryUI.URL_PARAM_REGEX,""):t}},registryUI.encodeURI=function(e){return e.indexOf("&")<0?window.encodeURIComponent(e):btoa(e)},registryUI.decodeURI=function(e){return e.startsWith("http")?window.decodeURIComponent(e):atob(e)},registryUI.isImageRemoveActivated=!0,registryUI.catalog={},registryUI.taglist={},registryUI.taghistory={},window.addEventListener("DOMContentLoaded",function(){riot.mount("*")}),riot.tag2("app",'
',"","",function(e){registryUI.appTag=this,route.base("#!"),route("",function(){route.routeName="home",registryUI.catalog.display&&(registryUI.catalog.loadend=!1),registryUI.appTag.update()}),route("/taglist/*",function(e){route.routeName="taglist",registryUI.taglist.name=e,registryUI.taglist.display&&(registryUI.taglist.loadend=!1),registryUI.appTag.update()}),route("/taghistory/image/*/tag/*",function(e,t){route.routeName="taghistory",registryUI.taghistory.image=e,registryUI.taghistory.tag=t,registryUI.taghistory.display&&(registryUI.taghistory.loadend=!1),registryUI.appTag.update()}),registryUI.home=function(){"home"==route.routeName?registryUI.catalog.display:route("")},registryUI.taghistory.go=function(e,t){route("/taghistory/image/"+e+"/tag/"+t)},registryUI.snackbar=function(e,t){registryUI.appTag.tags["material-snackbar"].addToast({message:e,isError:t},15e3)},registryUI.errorSnackbar=function(e){return registryUI.snackbar(e,!0)},registryUI.cleanName=function(){const e=registryUI.pullUrl||registryUI.url()&®istryUI.url().length>0&®istryUI.url()||window.location.host;return e?e.startsWith("http")?e.replace(/https?:\/\//,""):e:""},route.parser(null,function(e,t){const r=t.replace(/\?/g,"\\?").replace(/\*/g,"([^?#]+?)").replace(/\.\./,".*"),i=new RegExp("^"+r+"$"),a=e.match(i);if(a)return a.slice(1)}),registryUI.isDigit=function(e){return e>="0"&&e<="9"},registryUI.DockerImage=function(e,t){this.name=e,this.tag=t,riot.observable(this),this.on("get-size",function(){return void 0!==this.size?this.trigger("size",this.size):this.fillInfo()}),this.on("get-sha256",function(){return void 0!==this.size?this.trigger("sha256",this.sha256):this.fillInfo()}),this.on("get-date",function(){return void 0!==this.date?this.trigger("date",this.date):this.fillInfo()})},registryUI.DockerImage._tagReduce=function(e,t){return e.length>0&®istryUI.isDigit(e[e.length-1].charAt(0))==registryUI.isDigit(t)?e[e.length-1]+=t:e.push(t),e},registryUI.DockerImage.compare=function(e,t){const r=e.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce,[]),i=t.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce,[]);for(var a=0;a send {typeof opts.item === ⁗string⁗ ? opts.item : opts.item.repo}
{opts.item.images && opts.item.images.length} images expand_more
',"","",function(e){this.on("mount",function(){const e=this,t=this.tags["material-card"];t&&(t.launch=function(e){t.tags["material-waves"].trigger("launch",e)},this.item.images&&1===this.item.images.length&&(this.item=this.item.images[0]),t.root.onclick=function(t){e.item.repo?(e.expanded=!e.expanded,e.update({expanded:e.expanded,expanding:!0}),setTimeout(function(){e.update({expanded:e.expanded,expanding:!1})},50)):registryUI.taglist.go(e.item)})})}),riot.tag2("catalog",'

Repositories of {registryUI.name()}
{registryUI.catalog.length} images

',"","",function(e){registryUI.catalog.instance=this,registryUI.catalog.display=function(){registryUI.catalog.repositories=[];const e=new Http;e.addEventListener("load",function(){registryUI.catalog.repositories=[],200==this.status?(registryUI.catalog.repositories=JSON.parse(this.responseText).repositories||[],registryUI.catalog.repositories.sort(),registryUI.catalog.length=registryUI.catalog.repositories.length,registryUI.catalog.repositories=registryUI.catalog.repositories.reduce(function(e,t){const r=t.indexOf("/");if(r>0){const i=t.substring(0,r)+"/";return 0!=e.length&&e[e.length-1].repo==i||e.push({repo:i,images:[]}),e[e.length-1].images.push(t),e}return e.push(t),e},[])):404==this.status?registryUI.snackbar("Server not found",!0):registryUI.snackbar(this.responseText)}),e.addEventListener("error",function(){registryUI.snackbar(this.getErrorMessage(),!0),registryUI.catalog.repositories=[]}),e.addEventListener("loadend",function(){registryUI.catalog.loadend=!0,registryUI.catalog.instance.update()}),e.open("GET",registryUI.url()+"/v2/_catalog?n=100000"),e.send()},registryUI.catalog.display()}),riot.tag2("copy-to-clipboard",' content_copy ',"","",function(e){this.dockerCmd="docker pull "+registryUI.cleanName()+"/"+e.image.name+":"+e.image.tag,this.copy=function(){const e=this.refs.input;e.style.display="block",e.select(),document.execCommand("copy"),e.style.display="none",registryUI.snackbar("`"+this.dockerCmd+"` has been copied to clipboard.")}}),riot.tag2("image-date",'
{this.dateFormat(this.date)} ago
',"","",function(e){const t=this;this.dateFormat=function(e){if(void 0===e)return"";const t=["a second","seconds","a minute","minutes","an hour","hours","a day","days","a month","months","a year","years"],r=[1,60,3600,86400,2592e3,31104e3,1/0],i=(new Date-e)/1e3;for(var a=0;a=i)return t[2*a];if(r[a+1]>i)return Math.floor(i/r[a])+" "+t[2*a+1]}},e.image.on("creation-date",function(e){t.date=e,t.localDate=e.toLocaleString(),t.update()}),e.image.trigger("get-date")}),riot.tag2("image-size",'
{registryUI.bytesToSize(this.size)}
',"","",function(e){const t=this;e.image.on("size",function(e){t.size=e,t.update()}),e.image.trigger("get-size")}),riot.tag2("image-tag",'
{opts.image.tag}
',"","",function(e){const t=this;e.image.on("sha256",function(e){t.sha256=e.substring(0,19),t.update()}),e.image.trigger("get-sha256")}),riot.tag2("remove-image",' delete ',"","",function(e){const t=this;this.on("update",function(){!this.opts.multiDelete&&this.tags["material-checkbox"].checked&&this.tags["material-checkbox"].toggle()}),this.on("mount",function(){this.delete=this.tags["material-button"].root.onclick=function(e){const r=t.opts.image.name,i=t.opts.image.tag,a=new Http;a.addEventListener("loadend",function(){if(registryUI.taglist.go(r),200==this.status){if(!this.hasHeader("Docker-Content-Digest"))return void registryUI.errorSnackbar("You need to add Access-Control-Expose-Headers: ['Docker-Content-Digest'] in your server configuration.");const t=this.getResponseHeader("Docker-Content-Digest"),a=new Http;a.addEventListener("loadend",function(){200==this.status||202==this.status?(registryUI.taglist.display(),registryUI.snackbar("Deleting "+r+":"+i+" image. Run `registry garbage-collect config.yml` on your registry")):404==this.status?e||registryUI.errorSnackbar("Digest not found"):registryUI.snackbar(this.responseText)}),a.open("DELETE",registryUI.url()+"/v2/"+r+"/manifests/"+t),a.setRequestHeader("Accept","application/vnd.docker.distribution.manifest.v2+json"),a.addEventListener("error",function(){registryUI.errorSnackbar("An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: ['DELETE'].")}),a.send()}else 404==this.status?registryUI.errorSnackbar("Manifest for "+r+":"+i+" not found"):registryUI.snackbar(this.responseText)}),a.open("HEAD",registryUI.url()+"/v2/"+r+"/manifests/"+i),a.setRequestHeader("Accept","application/vnd.docker.distribution.manifest.v2+json"),a.send()},this.tags["material-checkbox"].on("toggle",function(){registryUI.taglist.instance.trigger("toggle-remove-image",this.checked)})})}),riot.tag2("tag-history-button",' history ',"","",function(e){this.on("mount",function(){const e=this;this.refs.button.root.onclick=function(){registryUI.taghistory._image=e.opts.image,registryUI.taghistory.go(e.opts.image.name,e.opts.image.tag)}}),this.update()}),riot.tag2("tag-history-element",'
{this.getIcon(entry.key)}

{entry.key.replace(\'_\', \' \')}

{entry.value}
{e}
',"",'class="{entry.key}"',function(e){this.getIcon=function(e){switch(e){case"architecture":return"memory";case"created":return"event";case"docker_version":return"";case"os":return"developer_board";case"Cmd":return"launch";case"Entrypoint":return"input";case"Env":return"notes";case"Labels":return"label";case"User":return"face";case"Volumes":return"storage";case"WorkingDir":return"home";case"author":return"account_circle";case"id":case"digest":return"settings_ethernet";case"created_by":return"build";case"size":return"get_app";case"ExposedPorts":return"router"}}}),riot.tag2("tag-history",'
arrow_back

History of {registryUI.taghistory.image}:{registryUI.taghistory.tag} history

',"","",function(e){const t=this,r=function(e){switch(e){case"id":return 1;case"created":return 2;case"created_by":return 3;case"size":return 4;case"os":return 5;case"architecture":return 6;case"linux":return 7;case"docker_version":return 8;default:return 10}},i=function(e,t){return r(e.key)-r(t.key)},a=function(e,t){switch(e){case"created":return new Date(t).toLocaleString();case"created_by":const r=t.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+)/);return r&&r[1]||"RUN";case"size":return registryUI.bytesToSize(t);case"Entrypoint":case"Cmd":return(t||[]).join(" ");case"Labels":return Object.keys(t||{}).map(function(e){return t[e]?e+"="+t[e]:""});case"Volumes":case"ExposedPorts":return Object.keys(t)}return t||""},s=function(e){function r(e){const t=[];for(const r in e)if(e.hasOwnProperty(r)&&"empty_layer"!=r){const i=e[r],s={key:r,value:a(r,i)};t.push(s)}return t.sort(i)}t.elements.push(r(function(e){const t=["architecture","User","created","docker_version","os","Cmd","Entrypoint","Env","Labels","User","Volumes","WorkingDir","author","id","ExposedPorts"].reduce(function(t,r){const i=e[r]||e.config[r];return i&&(t[r]=i),t},{});return!t.author&&t.Labels&&t.Labels.maintainer&&(t.author=e.config.Labels.maintainer,delete t.Labels.maintainer),t}(e))),e.history.reverse().forEach(function(e){t.elements.push(r(e))}),registryUI.taghistory.loadend=!0,t.update()};registryUI.taghistory.display=function(){t.elements=[];const e=registryUI.taghistory._image&®istryUI.taghistory._image.blobs;if(e)return window.scrollTo(0,0),s(e);const r=new registryUI.DockerImage(registryUI.taghistory.image,registryUI.taghistory.tag);r.fillInfo(),r.on("blobs",s)},this.on("mount",function(){t.refs["tag-history-tag"].tags["material-button"].root.onclick=function(){registryUI.taglist.go(registryUI.taghistory.image)}}),registryUI.taghistory.display(),t.update()}),riot.tag2("taglist",'
arrow_back

Tags of {registryUI.name() + \'/\' + registryUI.taglist.name}
{registryUI.taglist.tags.length} tags

Repository Creation date Size Tag History delete
{image.name}
',"","",function(e){var t=registryUI.taglist.instance=this;this.multiDelete=!1,this.toDelete=0,this.on("delete",function(){registryUI.isImageRemoveActivated&&this.multiDelete}),this.on("multi-delete",function(){registryUI.isImageRemoveActivated&&(this.multiDelete=!this.multiDelete)}),this.on("toggle-remove-image",function(e){e?this.toDelete++:this.toDelete--,this.toDelete<=1&&this.update()}),this._getRemoveImageTags=function(){var e=t.refs["taglist-tag"].tags["remove-image"];return e instanceof Array||(e=[e]),e},registryUI.taglist.bulkDelete=function(){t.multiDelete&&t.toDelete>0&&t._getRemoveImageTags().filter(function(e){return e.tags["material-checkbox"].checked}).forEach(function(e){e.delete(!0)})},this.on("mount",function(){var e=this.tags["material-card"].refs["remove-tag-checkbox"].toggle;this.tags["material-card"].refs["remove-tag-checkbox"].toggle=function(r){r.altKey?t._getRemoveImageTags().filter(function(e){return!e.tags["material-checkbox"].checked}).forEach(function(e){e.tags["material-checkbox"].toggle()}):e()},this.tags["material-card"].refs["remove-tag-checkbox"].on("toggle",function(){registryUI.taglist.instance.multiDelete=this.checked,registryUI.taglist.instance.update()})}),registryUI.taglist.display=function(){if(registryUI.taglist.tags=[],"taglist"==route.routeName){const e=new Http;registryUI.taglist.instance.update(),e.addEventListener("load",function(){registryUI.taglist.tags=[],200==this.status?(registryUI.taglist.tags=JSON.parse(this.responseText).tags||[],registryUI.taglist.tags=registryUI.taglist.tags.map(function(e){return new registryUI.DockerImage(registryUI.taglist.name,e)}).sort(registryUI.DockerImage.compare),console.log("end sort")):404==this.status?registryUI.snackbar("Server not found",!0):registryUI.snackbar(this.responseText,!0)}),e.addEventListener("error",function(){registryUI.snackbar(this.getErrorMessage(),!0),registryUI.taglist.tags=[]}),e.addEventListener("loadend",function(){registryUI.taglist.loadend=!0,registryUI.taglist.instance.update()}),e.open("GET",registryUI.url()+"/v2/"+registryUI.taglist.name+"/tags/list"),e.send(),registryUI.taglist.asc=!0}},registryUI.taglist.display(),registryUI.taglist.instance.update(),registryUI.taglist.reverse=function(){registryUI.taglist.asc?(registryUI.taglist.tags.reverse(),registryUI.taglist.asc=!1):(registryUI.taglist.tags.sort(registryUI.DockerImage.compare),registryUI.taglist.asc=!0),registryUI.taglist.instance.update()}}),riot.tag2("add",'
Add your Server ?
Write your URL without /v2
Add Cancel
',"","",function(e){registryUI.addTag=registryUI.addTag||{},this.one("mount",function(){registryUI.addTag.dialog=this.tags["material-popup"],registryUI.addTag.dialog.getAddServer=function(){return this.tags["material-input"]?this.tags["material-input"].value:""}}),registryUI.addTag.onkeyup=function(e){13==e.keyCode&®istryUI.addTag.add()},registryUI.addTag.show=function(){registryUI.addTag.dialog.open()},registryUI.addTag.add=function(){registryUI.addTag.dialog.getAddServer().length>0&®istryUI.addServer(registryUI.addTag.dialog.getAddServer()),registryUI.home(),registryUI.addTag.close()},registryUI.addTag.close=function(){registryUI.addTag.dialog.tags["material-input"].value="",registryUI.addTag.dialog.close()}}),riot.tag2("change",'
Change your Server ?
Change Cancel
',"","",function(e){registryUI.changeTag=registryUI.changeTag||{},this.one("mount",function(){registryUI.changeTag.dialog=this.tags["material-popup"],registryUI.changeTag.dialog.getServerUrl=function(){return this.refs["server-list"]?this.refs["server-list"].value:""},registryUI.changeTag.dialog.on("updated",function(){this.refs["server-list"]&&(this.refs["server-list"].value=registryUI.url())})}),registryUI.changeTag.show=function(){registryUI.changeTag.dialog.open()},registryUI.changeTag.change=function(){registryUI.changeTag.dialog.getServerUrl().length>0&®istryUI.changeServer(registryUI.changeTag.dialog.getServerUrl()),registryUI.home(),registryUI.changeTag.dialog.close()},registryUI.changeTag.close=function(){registryUI.changeTag.dialog.close()}}),riot.tag2("menu",' more_vert

Add URL

Change URL

Remove URL

',"","",function(e){registryUI.menuTag=registryUI.menuTag||{},registryUI.menuTag.update=this.update,this.one("mount",function(e){const t=this;registryUI.menuTag.close=function(){t.tags["material-dropdown"].close(),t.update()},registryUI.menuTag.isOpen=function(){return t.tags["material-dropdown"].opened},registryUI.menuTag.toggle=function(){t.tags["material-dropdown"].opened?t.tags["material-dropdown"].close():t.tags["material-dropdown"].open(),t.update()}})}),riot.tag2("remove",'
Remove your Registry Server ?
Close
',"","",function(e){registryUI.removeTag=registryUI.removeTag||{},registryUI.removeTag.update=this.update,registryUI.removeTag.removeUrl=function(e){registryUI.removeServer(e),registryUI.removeTag.close()},registryUI.removeTag.close=function(){registryUI.removeTag.dialog.close(),registryUI.removeTag.update()},registryUI.removeTag.show=function(){registryUI.removeTag.dialog.open()},this.one("mount",function(){registryUI.removeTag.dialog=this.tags["material-popup"]})}); \ No newline at end of file +function Http(){this.oReq=new XMLHttpRequest,this.oReq.hasHeader=Http.hasHeader,this.oReq.getErrorMessage=Http.getErrorMessage,this._events={},this._headers={}}Http.prototype.addEventListener=function(e,t){this._events[e]=t;const r=this;switch(e){case"loadend":r.oReq.addEventListener("loadend",function(){if(401==this.status){const e=new XMLHttpRequest;for(key in e.open(r._method,r._url),r._events)e.addEventListener(key,r._events[key]);for(key in r._headers)e.setRequestHeader(key,r._headers[key]);e.withCredentials=!0,e.hasHeader=Http.hasHeader,e.getErrorMessage=Http.getErrorMessage,e.send()}else t.bind(this)()});break;case"load":r.oReq.addEventListener("load",function(){401!==this.status&&t.bind(this)()});break;default:r.oReq.addEventListener(e,function(){t.bind(this)()})}},Http.prototype.setRequestHeader=function(e,t){this.oReq.setRequestHeader(e,t),this._headers[e]=t},Http.prototype.open=function(e,t){this._method=e,this._url=t,this.oReq.open(e,t)},Http.prototype.send=function(){this.oReq.send()},Http.hasHeader=function(e){return this.getAllResponseHeaders().split("\n").some(function(t){return new RegExp("^"+e+":","i").test(t)})},Http.getErrorMessage=function(){return registryUI.url()&®istryUI.url().match("^http://")&&"https:"===window.location.protocol?"Mixed Content: The page at `"+window.location.origin+"` was loaded over HTTPS, but requested an insecure server endpoint `"+registryUI.url()+"`. This request has been blocked; the content must be served over HTTPS.":registryUI.url()?this.withCredentials&&!this.hasHeader("Access-Control-Allow-Credentials")?"The `Access-Control-Allow-Credentials` header in the response is missing and must be set to `true` when the request's credentials mode is on. Origin `"+registryUI.url()+"` is therefore not allowed access.":"An error occured: Check your connection and your registry must have `Access-Control-Allow-Origin` header set to `"+window.location.origin+"`":"Incorrect server endpoint."};var registryUI={URL_QUERY_PARAM_REGEX:/[&?]url=/,URL_PARAM_REGEX:/^url=/};registryUI.name=registryUI.url=function(e){if(!registryUI._url){const e=registryUI.getUrlQueryParam();if(e)try{return registryUI._url=registryUI.decodeURI(e),registryUI._url}catch(e){console.log(e)}registryUI._url=registryUI.getRegistryServer(0)}return registryUI._url},registryUI.getRegistryServer=function(e){try{const t=JSON.parse(localStorage.getItem("registryServer"));if(t instanceof Array)return isNaN(e)?t.map(function(e){return e.trim().replace(/\/*$/,"")}):t[e]}catch(e){}return isNaN(e)?[]:""},registryUI.addServer=function(e){const t=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,""),-1==t.indexOf(e)&&(t.push(e),registryUI._url||registryUI.updateHistory(e),localStorage.setItem("registryServer",JSON.stringify(t)))},registryUI.changeServer=function(e){var t=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,"");const r=t.indexOf(e);-1!=r&&(t.splice(r,1),t=[e].concat(t),registryUI.updateHistory(e),localStorage.setItem("registryServer",JSON.stringify(t)))},registryUI.removeServer=function(e){const t=registryUI.getRegistryServer();e=e.trim().replace(/\/*$/,"");const r=t.indexOf(e);-1!=r&&(t.splice(r,1),localStorage.setItem("registryServer",JSON.stringify(t)),e==registryUI.url()&&(registryUI.updateHistory(registryUI.getRegistryServer(0)),route("")))},registryUI.updateHistory=function(e){history.pushState(null,"",(e?"?url="+registryUI.encodeURI(e):"?")+window.location.hash),registryUI._url=e},registryUI.getUrlQueryParam=function(){const e=window.location.search;if(registryUI.URL_QUERY_PARAM_REGEX.test(e)){const t=e.split(/^\?|&/).find(function(e){return e&®istryUI.URL_PARAM_REGEX.test(e)});return t?t.replace(registryUI.URL_PARAM_REGEX,""):t}},registryUI.encodeURI=function(e){return e.indexOf("&")<0?window.encodeURIComponent(e):btoa(e)},registryUI.decodeURI=function(e){return e.startsWith("http")?window.decodeURIComponent(e):atob(e)},registryUI.isImageRemoveActivated=!0,registryUI.catalog={},registryUI.taglist={},registryUI.taghistory={},window.addEventListener("DOMContentLoaded",function(){riot.mount("*")}),riot.tag2("app",'
',"","",function(e){registryUI.appTag=this,route.base("#!"),route("",function(){route.routeName="home",registryUI.catalog.display&&(registryUI.catalog.loadend=!1),registryUI.appTag.update()}),route("/taglist/*",function(e){route.routeName="taglist",registryUI.taglist.name=e,registryUI.taglist.display&&(registryUI.taglist.loadend=!1),registryUI.appTag.update()}),route("/taghistory/image/*/tag/*",function(e,t){route.routeName="taghistory",registryUI.taghistory.image=e,registryUI.taghistory.tag=t,registryUI.taghistory.display&&(registryUI.taghistory.loadend=!1),registryUI.appTag.update()}),registryUI.home=function(){"home"==route.routeName?registryUI.catalog.display:route("")},registryUI.taghistory.go=function(e,t){route("/taghistory/image/"+e+"/tag/"+t)},registryUI.snackbar=function(e,t){registryUI.appTag.tags["material-snackbar"].addToast({message:e,isError:t},15e3)},registryUI.errorSnackbar=function(e){return registryUI.snackbar(e,!0)},registryUI.cleanName=function(){const e=registryUI.pullUrl||registryUI.url()&®istryUI.url().length>0&®istryUI.url()||window.location.host;return e?e.startsWith("http")?e.replace(/https?:\/\//,""):e:""},route.parser(null,function(e,t){const r=t.replace(/\?/g,"\\?").replace(/\*/g,"([^?#]+?)").replace(/\.\./,".*"),i=new RegExp("^"+r+"$"),a=e.match(i);if(a)return a.slice(1)}),registryUI.isDigit=function(e){return e>="0"&&e<="9"},registryUI.DockerImage=function(e,t){this.name=e,this.tag=t,riot.observable(this),this.on("get-size",function(){return void 0!==this.size?this.trigger("size",this.size):this.fillInfo()}),this.on("get-sha256",function(){return void 0!==this.size?this.trigger("sha256",this.sha256):this.fillInfo()}),this.on("get-date",function(){return void 0!==this.date?this.trigger("date",this.date):this.fillInfo()})},registryUI.DockerImage._tagReduce=function(e,t){return e.length>0&®istryUI.isDigit(e[e.length-1].charAt(0))==registryUI.isDigit(t)?e[e.length-1]+=t:e.push(t),e},registryUI.DockerImage.compare=function(e,t){const r=e.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce,[]),i=t.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce,[]);for(var a=0;a send {typeof opts.item === ⁗string⁗ ? opts.item : opts.item.repo}
{opts.item.images && opts.item.images.length} images expand_more
',"","",function(e){this.on("mount",function(){const e=this,t=this.tags["material-card"];t&&(t.launch=function(e){t.tags["material-waves"].trigger("launch",e)},this.item.images&&1===this.item.images.length&&(this.item=this.item.images[0]),t.root.onclick=function(t){e.item.repo?(e.expanded=!e.expanded,e.update({expanded:e.expanded,expanding:!0}),setTimeout(function(){e.update({expanded:e.expanded,expanding:!1})},50)):registryUI.taglist.go(e.item)})})}),riot.tag2("catalog",'

Repositories of {registryUI.name()}
{registryUI.catalog.length} images

',"","",function(e){registryUI.catalog.instance=this,registryUI.catalog.display=function(){registryUI.catalog.repositories=[];const e=new Http;e.addEventListener("load",function(){registryUI.catalog.repositories=[],200==this.status?(registryUI.catalog.repositories=JSON.parse(this.responseText).repositories||[],registryUI.catalog.repositories.sort(),registryUI.catalog.length=registryUI.catalog.repositories.length,registryUI.catalog.repositories=registryUI.catalog.repositories.reduce(function(e,t){const r=t.indexOf("/");if(r>0){const i=t.substring(0,r)+"/";return 0!=e.length&&e[e.length-1].repo==i||e.push({repo:i,images:[]}),e[e.length-1].images.push(t),e}return e.push(t),e},[])):404==this.status?registryUI.snackbar("Server not found",!0):registryUI.snackbar(this.responseText)}),e.addEventListener("error",function(){registryUI.snackbar(this.getErrorMessage(),!0),registryUI.catalog.repositories=[]}),e.addEventListener("loadend",function(){registryUI.catalog.loadend=!0,registryUI.catalog.instance.update()}),e.open("GET",registryUI.url()+"/v2/_catalog?n=100000"),e.send()},registryUI.catalog.display()}),riot.tag2("copy-to-clipboard",' content_copy ',"","",function(e){this.dockerCmd="docker pull "+registryUI.cleanName()+"/"+e.image.name+":"+e.image.tag,this.copy=function(){const e=this.refs.input;e.style.display="block",e.select(),document.execCommand("copy"),e.style.display="none",registryUI.snackbar("`"+this.dockerCmd+"` has been copied to clipboard.")}}),riot.tag2("image-date",'
{this.dateFormat(this.date)} ago
',"","",function(e){const t=this;this.dateFormat=function(e){if(void 0===e)return"";const t=["a second","seconds","a minute","minutes","an hour","hours","a day","days","a month","months","a year","years"],r=[1,60,3600,86400,2592e3,31104e3,1/0],i=(new Date-e)/1e3;for(var a=0;a=i)return t[2*a];if(r[a+1]>i)return Math.floor(i/r[a])+" "+t[2*a+1]}},e.image.on("creation-date",function(e){t.date=e,t.localDate=e.toLocaleString(),t.update()}),e.image.trigger("get-date")}),riot.tag2("image-size",'
{registryUI.bytesToSize(this.size)}
',"","",function(e){const t=this;e.image.on("size",function(e){t.size=e,t.update()}),e.image.trigger("get-size")}),riot.tag2("image-tag",'
{opts.image.tag}
',"","",function(e){const t=this;e.image.on("sha256",function(e){t.sha256=e.substring(0,19),t.update()}),e.image.trigger("get-sha256")}),riot.tag2("remove-image",' delete ',"","",function(e){const t=this;this.on("update",function(){!this.opts.multiDelete&&this.tags["material-checkbox"].checked&&this.tags["material-checkbox"].toggle()}),this.on("mount",function(){this.delete=this.tags["material-button"].root.onclick=function(e){const r=t.opts.image.name,i=t.opts.image.tag,a=new Http;a.addEventListener("loadend",function(){if(registryUI.taglist.go(r),200==this.status){if(!this.hasHeader("Docker-Content-Digest"))return void registryUI.errorSnackbar("You need to add Access-Control-Expose-Headers: ['Docker-Content-Digest'] in your server configuration.");const t=this.getResponseHeader("Docker-Content-Digest"),a=new Http;a.addEventListener("loadend",function(){200==this.status||202==this.status?(registryUI.taglist.display(),registryUI.snackbar("Deleting "+r+":"+i+" image. Run `registry garbage-collect config.yml` on your registry")):404==this.status?e||registryUI.errorSnackbar("Digest not found"):registryUI.snackbar(this.responseText)}),a.open("DELETE",registryUI.url()+"/v2/"+r+"/manifests/"+t),a.setRequestHeader("Accept","application/vnd.docker.distribution.manifest.v2+json"),a.addEventListener("error",function(){registryUI.errorSnackbar("An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: ['DELETE'].")}),a.send()}else 404==this.status?registryUI.errorSnackbar("Manifest for "+r+":"+i+" not found"):registryUI.snackbar(this.responseText)}),a.open("HEAD",registryUI.url()+"/v2/"+r+"/manifests/"+i),a.setRequestHeader("Accept","application/vnd.docker.distribution.manifest.v2+json"),a.send()},this.tags["material-checkbox"].on("toggle",function(){registryUI.taglist.instance.trigger("toggle-remove-image",this.checked)})})}),riot.tag2("tag-history-button",' history ',"","",function(e){this.on("mount",function(){const e=this;this.refs.button.root.onclick=function(){registryUI.taghistory._image=e.opts.image,registryUI.taghistory.go(e.opts.image.name,e.opts.image.tag)}}),this.update()}),riot.tag2("tag-history-element",'
{this.getIcon(entry.key)}

{entry.key.replace(\'_\', \' \')}

{entry.value}
{e}
',"",'class="{entry.key}"',function(e){this.getIcon=function(e){switch(e){case"architecture":return"memory";case"created":return"event";case"docker_version":return"";case"os":return"developer_board";case"Cmd":return"launch";case"Entrypoint":return"input";case"Env":return"notes";case"Labels":return"label";case"User":return"face";case"Volumes":return"storage";case"WorkingDir":return"home";case"author":return"account_circle";case"id":case"digest":return"settings_ethernet";case"created_by":return"build";case"size":return"get_app";case"ExposedPorts":return"router"}}}),riot.tag2("tag-history",'
arrow_back

History of {registryUI.taghistory.image}:{registryUI.taghistory.tag} history

',"","",function(e){const t=this,r=function(e){switch(e){case"id":return 1;case"created":return 2;case"created_by":return 3;case"size":return 4;case"os":return 5;case"architecture":return 6;case"linux":return 7;case"docker_version":return 8;default:return 10}},i=function(e,t){return r(e.key)-r(t.key)},a=function(e,t){switch(e){case"created":return new Date(t).toLocaleString();case"created_by":const r=t.match(/\/bin\/sh *-c *#\(nop\) *([A-Z]+)/);return r&&r[1]||"RUN";case"size":return registryUI.bytesToSize(t);case"Entrypoint":case"Cmd":return(t||[]).join(" ");case"Labels":return Object.keys(t||{}).map(function(e){return t[e]?e+"="+t[e]:""});case"Volumes":case"ExposedPorts":return Object.keys(t)}return t||""},s=function(e){function r(e){const t=[];for(const r in e)if(e.hasOwnProperty(r)&&"empty_layer"!=r){const i=e[r],s={key:r,value:a(r,i)};t.push(s)}return t.sort(i)}t.elements.push(r(function(e){const t=["architecture","User","created","docker_version","os","Cmd","Entrypoint","Env","Labels","User","Volumes","WorkingDir","author","id","ExposedPorts"].reduce(function(t,r){const i=e[r]||e.config[r];return i&&(t[r]=i),t},{});return!t.author&&t.Labels&&t.Labels.maintainer&&(t.author=e.config.Labels.maintainer,delete t.Labels.maintainer),t}(e))),e.history.reverse().forEach(function(e){t.elements.push(r(e))}),registryUI.taghistory.loadend=!0,t.update()};registryUI.taghistory.display=function(){t.elements=[];const e=registryUI.taghistory._image&®istryUI.taghistory._image.blobs;if(e)return window.scrollTo(0,0),s(e);const r=new registryUI.DockerImage(registryUI.taghistory.image,registryUI.taghistory.tag);r.fillInfo(),r.on("blobs",s)},this.on("mount",function(){t.refs["tag-history-tag"].tags["material-button"].root.onclick=function(){registryUI.taglist.go(registryUI.taghistory.image)}}),registryUI.taghistory.display(),t.update()}),riot.tag2("taglist",'
arrow_back

Tags of {registryUI.name() + \'/\' + registryUI.taglist.name}
{registryUI.taglist.tags.length} tags

Repository Creation date Size Tag History delete
{image.name}
',"","",function(e){var t=registryUI.taglist.instance=this;this.multiDelete=!1,this.toDelete=0,this.on("delete",function(){registryUI.isImageRemoveActivated&&this.multiDelete}),this.on("multi-delete",function(){registryUI.isImageRemoveActivated&&(this.multiDelete=!this.multiDelete)}),this.on("toggle-remove-image",function(e){e?this.toDelete++:this.toDelete--,this.toDelete<=1&&this.update()}),this._getRemoveImageTags=function(){var e=t.refs["taglist-tag"].tags["remove-image"];return e instanceof Array||(e=[e]),e},registryUI.taglist.bulkDelete=function(){t.multiDelete&&t.toDelete>0&&t._getRemoveImageTags().filter(function(e){return e.tags["material-checkbox"].checked}).forEach(function(e){e.delete(!0)})},this.on("mount",function(){var e=this.tags["material-card"].refs["remove-tag-checkbox"].toggle;this.tags["material-card"].refs["remove-tag-checkbox"].toggle=function(r){r.altKey?t._getRemoveImageTags().filter(function(e){return!e.tags["material-checkbox"].checked}).forEach(function(e){e.tags["material-checkbox"].toggle()}):e()},this.tags["material-card"].refs["remove-tag-checkbox"].on("toggle",function(){registryUI.taglist.instance.multiDelete=this.checked,registryUI.taglist.instance.update()})}),registryUI.taglist.display=function(){if(registryUI.taglist.tags=[],"taglist"==route.routeName){const e=new Http;registryUI.taglist.instance.update(),e.addEventListener("load",function(){registryUI.taglist.tags=[],200==this.status?(registryUI.taglist.tags=JSON.parse(this.responseText).tags||[],registryUI.taglist.tags=registryUI.taglist.tags.map(function(e){return new registryUI.DockerImage(registryUI.taglist.name,e)}).sort(registryUI.DockerImage.compare)):404==this.status?registryUI.snackbar("Server not found",!0):registryUI.snackbar(this.responseText,!0)}),e.addEventListener("error",function(){registryUI.snackbar(this.getErrorMessage(),!0),registryUI.taglist.tags=[]}),e.addEventListener("loadend",function(){registryUI.taglist.loadend=!0,registryUI.taglist.instance.update()}),e.open("GET",registryUI.url()+"/v2/"+registryUI.taglist.name+"/tags/list"),e.send(),registryUI.taglist.asc=!0}},registryUI.taglist.display(),registryUI.taglist.instance.update(),registryUI.taglist.reverse=function(){registryUI.taglist.asc?(registryUI.taglist.tags.reverse(),registryUI.taglist.asc=!1):(registryUI.taglist.tags.sort(registryUI.DockerImage.compare),registryUI.taglist.asc=!0),registryUI.taglist.instance.update()}}),riot.tag2("add",'
Add your Server ?
Write your URL without /v2
Add Cancel
',"","",function(e){registryUI.addTag=registryUI.addTag||{},this.one("mount",function(){registryUI.addTag.dialog=this.tags["material-popup"],registryUI.addTag.dialog.getAddServer=function(){return this.tags["material-input"]?this.tags["material-input"].value:""}}),registryUI.addTag.onkeyup=function(e){13==e.keyCode&®istryUI.addTag.add()},registryUI.addTag.show=function(){registryUI.addTag.dialog.open()},registryUI.addTag.add=function(){registryUI.addTag.dialog.getAddServer().length>0&®istryUI.addServer(registryUI.addTag.dialog.getAddServer()),registryUI.home(),registryUI.addTag.close()},registryUI.addTag.close=function(){registryUI.addTag.dialog.tags["material-input"].value="",registryUI.addTag.dialog.close()}}),riot.tag2("change",'
Change your Server ?
Change Cancel
',"","",function(e){registryUI.changeTag=registryUI.changeTag||{},this.one("mount",function(){registryUI.changeTag.dialog=this.tags["material-popup"],registryUI.changeTag.dialog.getServerUrl=function(){return this.refs["server-list"]?this.refs["server-list"].value:""},registryUI.changeTag.dialog.on("updated",function(){this.refs["server-list"]&&(this.refs["server-list"].value=registryUI.url())})}),registryUI.changeTag.show=function(){registryUI.changeTag.dialog.open()},registryUI.changeTag.change=function(){registryUI.changeTag.dialog.getServerUrl().length>0&®istryUI.changeServer(registryUI.changeTag.dialog.getServerUrl()),registryUI.home(),registryUI.changeTag.dialog.close()},registryUI.changeTag.close=function(){registryUI.changeTag.dialog.close()}}),riot.tag2("menu",' more_vert

Add URL

Change URL

Remove URL

',"","",function(e){registryUI.menuTag=registryUI.menuTag||{},registryUI.menuTag.update=this.update,this.one("mount",function(e){const t=this;registryUI.menuTag.close=function(){t.tags["material-dropdown"].close(),t.update()},registryUI.menuTag.isOpen=function(){return t.tags["material-dropdown"].opened},registryUI.menuTag.toggle=function(){t.tags["material-dropdown"].opened?t.tags["material-dropdown"].close():t.tags["material-dropdown"].open(),t.update()}})}),riot.tag2("remove",'
Remove your Registry Server ?
Close
',"","",function(e){registryUI.removeTag=registryUI.removeTag||{},registryUI.removeTag.update=this.update,registryUI.removeTag.removeUrl=function(e){registryUI.removeServer(e),registryUI.removeTag.close()},registryUI.removeTag.close=function(){registryUI.removeTag.dialog.close(),registryUI.removeTag.update()},registryUI.removeTag.show=function(){registryUI.removeTag.dialog.open()},this.one("mount",function(){registryUI.removeTag.dialog=this.tags["material-popup"]})}); \ No newline at end of file diff --git a/examples/issue-75/README.md b/examples/issue-75/README.md index f07d295..276df10 100644 --- a/examples/issue-75/README.md +++ b/examples/issue-75/README.md @@ -2,6 +2,6 @@ Run this command `docker-compose up -d`, then you can push your images (e.g localhost:5000/alpine). -Be careful, the docker registry is using status codes 307 for each requests, that means you must configure your s3 to accept same requests as your private registry (that means `DELETE`, `Access-Control-Allow-Origin` and others). +Be careful, the docker registry is using status codes 307 for each requests, that means you must configure your s3 to accept same requests as your private registry (that means `DELETE`, `Access-Control-Allow-Origin` and others). To avoid this, we need the option `storage.redirect.disable: true`, with this you will use your registry credentials (if you are using it). This s3 server allow all requests. \ No newline at end of file diff --git a/examples/issue-75/registry-config/config.yml b/examples/issue-75/registry-config/config.yml index 508cdd6..cd8ac81 100644 --- a/examples/issue-75/registry-config/config.yml +++ b/examples/issue-75/registry-config/config.yml @@ -20,6 +20,8 @@ storage: v4auth: true chunksize: 5242880 rootdirectory: / + redirect: + disable: true http: addr: 0.0.0.0:5000 headers: diff --git a/index.md b/index.md new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/index.md @@ -0,0 +1 @@ +README.md \ No newline at end of file