Merge pull request #58 from lennartblom/master

New Feature: Overview of tag history content
This commit is contained in:
Jones Magloire 2018-12-08 15:49:55 +01:00 committed by GitHub
commit e89a0112ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 606 additions and 133 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
node_modules node_modules
package-lock.json package-lock.json
registry-data registry-data
.idea

192
src/images/docker-logo.svg Normal file
View file

@ -0,0 +1,192 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
id="svg2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="259.5px" height="145px"
viewBox="0 0 259.5 145" enable-background="new 0 0 259.5 145" xml:space="preserve">
<path id="path14" fill="#394D54" d="M147.488,45.732h22.866v23.375h11.562c5.339,0,10.831-0.951,15.887-2.665
c2.484-0.842,5.273-2.015,7.724-3.489c-3.228-4.214-4.876-9.536-5.361-14.781c-0.659-7.134,0.781-16.42,5.609-22.004l2.403-2.78
l2.864,2.302c7.211,5.794,13.276,13.89,14.345,23.118c8.683-2.554,18.877-1.95,26.531,2.468l3.14,1.812l-1.653,3.226
c-6.473,12.634-20.005,16.547-33.235,15.854c-19.797,49.308-62.898,72.653-115.157,72.653c-26.999,0-51.77-10.094-65.875-34.047
c-0.827-1.488-1.536-3.045-2.287-4.572c-4.768-10.545-6.352-22.096-5.277-33.637l0.322-3.457H51.45V45.732h22.866V22.867h45.732V0
h27.44L147.488,45.732"/>
<g>
<defs>
<path id="SVGID_1_" d="M76,2v46H54v23H35.581c-0.078,0.666-0.141,1.333-0.206,2c-1.151,12.531,1.037,24.088,6.062,33.969
L43.125,110c1.011,1.816,2.191,3.523,3.438,5.188c1.245,1.662,1.686,2.582,2.469,3.688C62.32,133.811,82.129,141,105,141
c50.648,0,93.633-22.438,112.656-72.844C231.153,69.541,244.1,66.081,250,54.562c-9.398-5.424-21.479-3.685-28.438-0.188L240,2
l-72,46h-23V2H76z"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" overflow="visible"/>
</clipPath>
<g clip-path="url(#SVGID_2_)">
<g>
<g id="g20" transform="translate(0,-22.866)">
<path id="path22" fill="#00ACD3" d="M123.859,3.811h19.818v19.817h-19.818V3.811z"/>
<path id="path24" fill="#20C2EF" d="M123.859,26.677h19.818v19.818h-19.818V26.677z"/>
<path id="path26" stroke="#394D54" stroke-width="1.56" d="M126.292,21.977V5.461 M129.264,21.977V5.461 M132.266,21.977V5.461
M135.27,21.977V5.461 M138.272,21.977V5.461 M141.244,21.977V5.461"/>
<g id="use28" transform="translate(0,22.866)">
<path id="path26_1_" stroke="#394D54" stroke-width="1.56" d="M126.292,44.843V28.327 M129.264,44.843V28.327 M132.266,44.843
V28.327 M135.27,44.843V28.327 M138.272,44.843V28.327 M141.244,44.843V28.327"/>
</g>
</g>
<g id="g18">
<g id="g20_1_" transform="translate(0,-22.866)">
<path id="path22_1_" fill="#00ACD3" d="M146.725,46.493h19.817V26.677h-19.817V46.493z"/>
<path id="path24_1_" fill="#20C2EF" d="M146.725,23.628h19.817V3.81h-19.817V23.628z"/>
<path id="path26_2_" stroke="#394D54" stroke-width="1.56" d="M149.158,28.328v16.516 M152.13,28.328v16.516 M155.132,28.328
v16.516 M158.135,28.328v16.516 M161.138,28.328v16.516 M164.11,28.328v16.516"/>
<g id="use30" transform="matrix(1,0,0,-1,22.866,4.572651)">
<path id="path26_3_" stroke="#394D54" stroke-width="1.56" d="M149.158,5.462v16.516 M152.13,5.462v16.516 M155.132,5.462
v16.516 M158.135,5.462v16.516 M161.138,5.462v16.516 M164.11,5.462v16.516"/>
</g>
</g>
</g>
<g>
<g>
<g id="g20_2_" transform="translate(0,-22.866)">
<path id="path22_2_" fill="#00ACD3" d="M32.395,49.543h19.817V69.36H32.395V49.543z"/>
<path id="path24_2_" fill="#20C2EF" d="M32.395,72.408h19.817v19.818H32.395V72.408z"/>
<path id="path26_4_" stroke="#394D54" stroke-width="1.56" d="M34.828,67.709V51.193 M37.8,67.709V51.193 M40.802,67.709
V51.193 M43.806,67.709V51.193 M46.809,67.709V51.193 M49.78,67.709V51.193"/>
<g id="use28_1_" transform="translate(0,22.866)">
<path id="path26_5_" stroke="#394D54" stroke-width="1.56" d="M34.828,90.575V74.059 M37.8,90.575V74.059 M40.802,90.575
V74.059 M43.806,90.575V74.059 M46.809,90.575V74.059 M49.78,90.575V74.059"/>
</g>
</g>
<g id="g18_1_">
<g id="g20_3_" transform="translate(0,-22.866)">
<path id="path22_3_" fill="#00ACD3" d="M55.261,92.225h19.817V72.408H55.261V92.225z"/>
<path id="path24_3_" fill="#20C2EF" d="M55.261,69.36h19.817V49.542H55.261V69.36z"/>
<path id="path26_6_" stroke="#394D54" stroke-width="1.56" d="M57.694,74.06v16.516 M60.666,74.06v16.516 M63.668,74.06
v16.516 M66.671,74.06v16.516 M69.674,74.06v16.516 M72.646,74.06v16.516"/>
<g id="use32" transform="translate(-91.464,45.732)">
<path id="path26_7_" stroke="#394D54" stroke-width="1.56" d="M57.694,51.194v16.516 M60.666,51.194v16.516 M63.668,51.194
v16.516 M66.671,51.194v16.516 M69.674,51.194v16.516 M72.646,51.194v16.516"/>
</g>
</g>
</g>
<g>
<g>
<g id="g20_4_" transform="translate(0,-22.866)">
<path id="path22_4_" fill="#00ACD3" d="M78.127,49.543h19.817V69.36H78.127V49.543z"/>
<path id="path24_4_" fill="#20C2EF" d="M78.127,72.408h19.817v19.818H78.127V72.408z"/>
<path id="path26_8_" stroke="#394D54" stroke-width="1.56" d="M80.561,67.709V51.193 M83.532,67.709V51.193 M86.534,67.709
V51.193 M89.538,67.709V51.193 M92.541,67.709V51.193 M95.512,67.709V51.193"/>
<g id="use28_2_" transform="translate(0,22.866)">
<path id="path26_9_" stroke="#394D54" stroke-width="1.56" d="M80.561,90.575V74.059 M83.532,90.575V74.059 M86.534,90.575
V74.059 M89.538,90.575V74.059 M92.541,90.575V74.059 M95.512,90.575V74.059"/>
</g>
</g>
<g id="g18_2_">
<g id="g20_5_" transform="translate(0,-22.866)">
<path id="path22_5_" fill="#00ACD3" d="M100.993,92.225h19.817V72.408h-19.817V92.225z"/>
<path id="path24_5_" fill="#20C2EF" d="M100.993,69.36h19.817V49.542h-19.817V69.36z"/>
<path id="path26_10_" stroke="#394D54" stroke-width="1.56" d="M103.426,74.06v16.516 M106.398,74.06v16.516 M109.4,74.06
v16.516 M112.403,74.06v16.516 M115.406,74.06v16.516 M118.378,74.06v16.516"/>
<g id="use34" transform="translate(-45.732,45.732)">
<path id="path26_11_" stroke="#394D54" stroke-width="1.56" d="M103.426,51.194v16.516 M106.398,51.194v16.516
M109.4,51.194v16.516 M112.403,51.194v16.516 M115.406,51.194v16.516 M118.378,51.194v16.516"/>
</g>
</g>
</g>
<g>
<g id="g16">
<g id="g20_6_" transform="translate(0,-22.866)">
<path id="path22_6_" fill="#00ACD3" d="M123.859,49.543h19.818V69.36h-19.818V49.543z"/>
<path id="path24_6_" fill="#20C2EF" d="M123.859,72.408h19.818v19.818h-19.818V72.408z"/>
<path id="path26_12_" stroke="#394D54" stroke-width="1.56" d="M126.292,67.709V51.193 M129.264,67.709V51.193
M132.266,67.709V51.193 M135.27,67.709V51.193 M138.272,67.709V51.193 M141.244,67.709V51.193"/>
<g id="use28_3_" transform="translate(0,22.866)">
<path id="path26_13_" stroke="#394D54" stroke-width="1.56" d="M126.292,90.575V74.059 M129.264,90.575V74.059
M132.266,90.575V74.059 M135.27,90.575V74.059 M138.272,90.575V74.059 M141.244,90.575V74.059"/>
</g>
</g>
<g id="g18_3_">
<g id="g20_7_" transform="translate(0,-22.866)">
<path id="path22_7_" fill="#00ACD3" d="M146.725,92.225h19.817V72.408h-19.817V92.225z"/>
<path id="path24_7_" fill="#20C2EF" d="M146.725,69.36h19.817V49.542h-19.817V69.36z"/>
<path id="path26_14_" stroke="#394D54" stroke-width="1.56" d="M149.158,74.06v16.516 M152.13,74.06v16.516
M155.132,74.06v16.516 M158.135,74.06v16.516 M161.138,74.06v16.516 M164.11,74.06v16.516"/>
<g id="use36" transform="translate(0,45.732)">
<path id="path26_15_" stroke="#394D54" stroke-width="1.56" d="M149.158,51.194v16.516 M152.13,51.194v16.516
M155.132,51.194v16.516 M158.135,51.194v16.516 M161.138,51.194v16.516 M164.11,51.194v16.516"/>
</g>
</g>
</g>
</g>
<g>
<defs>
<path id="SVGID_3_" d="M76,2v46H54v23H35.581c-0.078,0.666-0.141,1.333-0.206,2c-1.151,12.531,1.037,24.088,6.062,33.969
L43.125,110c1.011,1.816,2.191,3.523,3.438,5.188c1.245,1.662,1.686,2.582,2.469,3.688C62.32,133.811,82.129,141,105,141
c50.648,0,93.633-22.438,112.656-72.844C231.153,69.541,244.1,66.081,250,54.562c-9.398-5.424-21.479-3.685-28.438-0.188
L240,2l-72,46h-23V2H76z"/>
</defs>
<clipPath id="SVGID_4_">
<use xlink:href="#SVGID_3_" overflow="visible"/>
</clipPath>
<path id="path38" clip-path="url(#SVGID_4_)" fill="#17B5EB" d="M221.57,54.379c1.533-11.915-7.384-21.274-12.914-25.718
c-6.373,7.368-7.363,26.678,2.635,34.808c-5.58,4.956-17.337,9.448-29.375,9.448H34C32.829,85.484,34,146,34,146h217
l-0.986-91.424C240.615,49.152,228.529,50.882,221.57,54.379"/>
</g>
<g>
<defs>
<path id="SVGID_5_" d="M76,2v46H54v23H35.581c-0.078,0.666-0.141,1.333-0.206,2c-1.151,12.531,1.037,24.088,6.062,33.969
L43.125,110c1.011,1.816,2.191,3.523,3.438,5.188c1.245,1.662,1.686,2.582,2.469,3.688C62.32,133.811,82.129,141,105,141
c50.648,0,93.633-22.438,112.656-72.844C231.153,69.541,244.1,66.081,250,54.562c-9.398-5.424-21.479-3.685-28.438-0.188
L240,2l-72,46h-23V2H76z"/>
</defs>
<clipPath id="SVGID_6_">
<use xlink:href="#SVGID_5_" overflow="visible"/>
</clipPath>
<path id="path40" clip-path="url(#SVGID_6_)" fill-opacity="0.17" d="M34,89v57h217V89"/>
</g>
<g>
<defs>
<path id="SVGID_7_" d="M76,2v46H54v23H35.581c-0.078,0.666-0.141,1.333-0.206,2c-1.151,12.531,1.037,24.088,6.062,33.969
L43.125,110c1.011,1.816,2.191,3.523,3.438,5.188c1.245,1.662,1.686,2.582,2.469,3.688C62.32,133.811,82.129,141,105,141
c50.648,0,93.633-22.438,112.656-72.844C231.153,69.541,244.1,66.081,250,54.562c-9.398-5.424-21.479-3.685-28.438-0.188
L240,2l-72,46h-23V2H76z"/>
</defs>
<clipPath id="SVGID_8_">
<use xlink:href="#SVGID_7_" overflow="visible"/>
</clipPath>
<path id="path42" clip-path="url(#SVGID_8_)" fill="#D4EDF1" d="M111.237,140.891c-13.54-6.426-20.972-15.16-25.107-24.695
L45,118l21,28L111.237,140.891"/>
</g>
<g>
<defs>
<path id="SVGID_9_" d="M76,2v46H54v23H35.581c-0.078,0.666-0.141,1.333-0.206,2c-1.151,12.531,1.037,24.088,6.062,33.969
L43.125,110c1.011,1.816,2.191,3.523,3.438,5.188c1.245,1.662,1.686,2.582,2.469,3.688C62.32,133.811,82.129,141,105,141
c50.648,0,93.633-22.438,112.656-72.844C231.153,69.541,244.1,66.081,250,54.562c-9.398-5.424-21.479-3.685-28.438-0.188
L240,2l-72,46h-23V2H76z"/>
</defs>
<clipPath id="SVGID_10_">
<use xlink:href="#SVGID_9_" overflow="visible"/>
</clipPath>
<path id="path44" clip-path="url(#SVGID_10_)" fill-opacity="0.085" d="M222.5,53.938v0.031
c-20.861,26.889-50.783,50.379-82.906,62.719c-28.655,11.008-53.638,11.061-70.875,2.219
c-1.856-1.049-3.676-2.211-5.5-3.312c-12.637-8.832-19.754-23.441-19.156-42.688H34V146h217V50h-25L222.5,53.938z"/>
</g>
<path id="path46" fill="none" stroke="#394D54" stroke-width="3.4" stroke-linecap="round" d="M45.625,117.031
c14.165,0.775,29.282,0.914,42.469-3.219"/>
<path id="path48" fill="#D4EDF1" d="M102.17,106.959c0,3.02-2.448,5.467-5.467,5.467c-3.02,0-5.467-2.447-5.467-5.467
c0-3.019,2.448-5.467,5.467-5.467C99.723,101.492,102.17,103.94,102.17,106.959z"/>
<path id="path50" fill="#394D54" d="M98.122,103.308c-0.478,0.276-0.8,0.793-0.8,1.384c0,0.883,0.715,1.598,1.598,1.598
c0.604,0,1.13-0.336,1.401-0.832c0.192,0.463,0.298,0.97,0.298,1.502c0,2.162-1.753,3.915-3.916,3.915
c-2.162,0-3.916-1.753-3.916-3.915c0-2.163,1.754-3.916,3.916-3.916C97.204,103.043,97.681,103.137,98.122,103.308z"/>
<path id="path52" fill="#394D54" d="M0,90.162h254.328c-5.538-1.404-17.521-3.303-15.545-10.56
c-10.069,11.651-34.353,8.175-40.481,2.429c-6.825,9.898-46.554,6.137-49.325-1.574c-8.556,10.04-35.067,10.04-43.624,0
c-2.772,7.711-42.501,11.473-49.327,1.574c-6.128,5.746-30.41,9.223-40.48-2.429C17.522,86.859,5.539,88.758,0,90.162"/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -39,6 +39,8 @@
<!-- endbuild --> <!-- endbuild -->
<!-- build:js scripts/docker-registry-ui.js --> <!-- build:js scripts/docker-registry-ui.js -->
<script src="tags/catalog.tag" type="riot/tag"></script> <script src="tags/catalog.tag" type="riot/tag"></script>
<script src="tags/tag-history-button.tag" type="riot/tag"></script>
<script src="tags/tag-history.tag" type="riot/tag"></script>
<script src="tags/taglist.tag" type="riot/tag"></script> <script src="tags/taglist.tag" type="riot/tag"></script>
<script src="tags/image-tag.tag" type="riot/tag"></script> <script src="tags/image-tag.tag" type="riot/tag"></script>
<script src="tags/remove-image.tag" type="riot/tag"></script> <script src="tags/remove-image.tag" type="riot/tag"></script>

View file

@ -110,6 +110,7 @@ registryUI.decodeURI = function(url) {
registryUI.isImageRemoveActivated = true; registryUI.isImageRemoveActivated = true;
registryUI.catalog = {}; registryUI.catalog = {};
registryUI.taglist = {}; registryUI.taglist = {};
registryUI.taghistory = {};
window.addEventListener('DOMContentLoaded', function() { window.addEventListener('DOMContentLoaded', function() {
riot.mount('*'); riot.mount('*');

View file

@ -24,6 +24,7 @@ registryUI.name = function() {
registryUI.isImageRemoveActivated = true; registryUI.isImageRemoveActivated = true;
registryUI.catalog = {}; registryUI.catalog = {};
registryUI.taglist = {}; registryUI.taglist = {};
registryUI.taghistory = {};
window.addEventListener('DOMContentLoaded', function() { window.addEventListener('DOMContentLoaded', function() {
riot.mount('*'); riot.mount('*');

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
html>body { html > body {
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif !important; font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif !important;
} }
@ -94,12 +94,12 @@ h2 {
list-style: none; list-style: none;
} }
.list.highlight>li:hover { .list.highlight > li:hover {
background-color: #eee; background-color: #eee;
cursor: pointer; cursor: pointer;
} }
.list>li { .list > li {
box-sizing: border-box; box-sizing: border-box;
line-height: 1; line-height: 1;
height: 48px; height: 48px;
@ -107,7 +107,7 @@ h2 {
overflow: hidden; overflow: hidden;
} }
.list>li i.material-icons { .list > li i.material-icons {
margin-right: 32px; margin-right: 32px;
height: 24px; height: 24px;
width: 24px; width: 24px;
@ -116,7 +116,7 @@ h2 {
color: #757575; color: #757575;
} }
.list>li>span { .list > li > span {
height: 100%; height: 100%;
text-decoration: none; text-decoration: none;
box-sizing: border-box; box-sizing: border-box;
@ -322,6 +322,114 @@ select {
padding: 12px 5px; padding: 12px 5px;
} }
.show-tag-history {
width: 30px;
text-align: center;
}
.remove-tag {
padding: 12px 5px;
width: 30px;
text-align: center;
}
.copy-to-clipboard a:hover { .copy-to-clipboard a:hover {
cursor: pointer; cursor: pointer;
} }
material-card.tag-history{
margin-bottom: 0;
}
.tag-history-element {
margin-top:5px;
margin-bottom: 0;
}
.tag-history-element > div {
padding: 20px;
min-width: 100px;
}
.tag-history-element i {
font-size: 20px;
padding: 0px;
}
.tag-history-element .created i:after {
content: 'event';
}
.tag-history-element .config i:after {
content: 'build';
}
.tag-history-element .config .value:after,
.tag-history-element .container_config .value:after {
content: 'WIP for display of config value';
font-style: italic;
font-size: 12px;
}
.tag-history-element .container_config i:after {
content: 'code';
}
.tag-history-element .throwaway i:after {
content: 'eject';
}
.tag-history-element .parent i:after {
content: 'supervisor_account';
}
.tag-history-element .architecture i:after {
content: 'memory';
}
.tag-history-element .os i:after {
content: 'developer_board';
}
.tag-history-element .container i:after {
content: 'dns';
}
.tag-history-element .id i:after {
content: 'settings_ethernet';
}
.tag-history-element .docker_version {
background-size: 35px auto;
background-image: url("images/docker-logo.svg");
background-repeat: no-repeat;
background-position: 11px 20px;
}
.tag-history-element > div {
display: block;
width: 420px;
float: left;
}
.tag-history-element .docker_version div.headline {
padding-left: 10px;
}
.tag-history-element .headline p {
font-weight: bold;
line-height: 20px;
position: relative;
display: inline;
top: -4px;
}
.tag-history-element .id div.value,
.tag-history-element .container div.value,
.tag-history-element .parent div.value {
font-size: 12px;
}
tag-history-button button {
background: none;
border: none;
}

View file

@ -1,18 +1,18 @@
<!-- <!--
Copyright (C) 2016 Jones Magloire @Joxit Copyright (C) 2016 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<app> <app>
<header> <header>
@ -24,6 +24,7 @@
<main> <main>
<catalog if="{route.routeName == 'home'}"></catalog> <catalog if="{route.routeName == 'home'}"></catalog>
<taglist if="{route.routeName == 'taglist'}"></taglist> <taglist if="{route.routeName == 'taglist'}"></taglist>
<tag-history if="{route.routeName == 'taghistory'}"></tag-history>
<change></change> <change></change>
<add></add> <add></add>
<remove></remove> <remove></remove>
@ -31,7 +32,8 @@
</main> </main>
<footer> <footer>
<material-footer> <material-footer>
<a class="material-footer-logo" href="https://joxit.github.io/docker-registry-ui/">Docker Registry UI %%GULP_INJECT_VERSION%%</a> <a class="material-footer-logo" href="https://joxit.github.io/docker-registry-ui/">Docker Registry UI
%%GULP_INJECT_VERSION%%</a>
<ul class="material-footer-link-list"> <ul class="material-footer-link-list">
<li> <li>
<a href="https://github.com/Joxit/docker-registry-ui">Contribute on GitHub</a> <a href="https://github.com/Joxit/docker-registry-ui">Contribute on GitHub</a>
@ -46,7 +48,7 @@
<script> <script>
registryUI.appTag = this; registryUI.appTag = this;
route.base('#!') route.base('#!');
route('', function() { route('', function() {
route.routeName = 'home'; route.routeName = 'home';
if (registryUI.catalog.display) { if (registryUI.catalog.display) {
@ -57,48 +59,65 @@
}); });
route('/taglist/*', function(image) { route('/taglist/*', function(image) {
route.routeName = 'taglist'; route.routeName = 'taglist';
registryUI.taglist.name = image registryUI.taglist.name = image;
if (registryUI.taglist.display) { if (registryUI.taglist.display) {
registryUI.taglist.loadend = false; registryUI.taglist.loadend = false;
registryUI.taglist.display(); registryUI.taglist.display();
} }
registryUI.appTag.update(); registryUI.appTag.update();
}); });
route('/taghistory/image/*/tag/*', function(image, tag) {
route.routeName = 'taghistory';
registryUI.taghistory.image = image;
registryUI.taghistory.tag = tag;
if (registryUI.taghistory.display) {
registryUI.taghistory.loadend = false;
registryUI.taghistory.display();
}
registryUI.appTag.update();
});
registryUI.home = function() { registryUI.home = function() {
if(route.routeName == 'home') { if (route.routeName == 'home') {
registryUI.catalog.display(); registryUI.catalog.display;
} else { } else {
route(''); route('');
} }
}; };
registryUI.snackbar = function (message, isError) {
registryUI.taghistory.go = function(image, tag) {
route('/taghistory/image/' + image + '/tag/' + tag);
};
registryUI.snackbar = function(message, isError) {
registryUI.appTag.tags['material-snackbar'].addToast({'message': message, 'isError': isError}, 15000); registryUI.appTag.tags['material-snackbar'].addToast({'message': message, 'isError': isError}, 15000);
}; };
registryUI.errorSnackbar = function (message) { registryUI.errorSnackbar = function(message) {
return registryUI.snackbar(message, true); return registryUI.snackbar(message, true);
} };
registryUI.cleanName = function() { registryUI.cleanName = function() {
const url = (registryUI.url() && registryUI.url().length > 0 && registryUI.url()) || window.location.host; const url = (registryUI.url() && registryUI.url().length > 0 && registryUI.url()) || window.location.host;
if (url) { if (url) {
return url.startsWith('http') ? url.replace(/https?:\/\//, '') : url; return url.startsWith('http') ? url.replace(/https?:\/\//, '') : url;
} }
return ''; return '';
} };
route.parser(null, function(path, filter) { route.parser(null, function(path, filter) {
const f = filter const f = filter
.replace(/\?/g, '\\?') .replace(/\?/g, '\\?')
.replace(/\*/g, '([^?#]+?)') .replace(/\*/g, '([^?#]+?)')
.replace(/\.\./, '.*') .replace(/\.\./, '.*');
const re = new RegExp('^' + f + '$') const re = new RegExp('^' + f + '$');
const args = path.match(re) const args = path.match(re);
if (args) return args.slice(1) if (args) return args.slice(1)
}); });
registryUI.isDigit = function(char) { registryUI.isDigit = function(char) {
return char >= '0' && char <= '9'; return char >= '0' && char <= '9';
} };
registryUI.DockerImage = function (name, tag) { registryUI.DockerImage = function(name, tag) {
this.name = name; this.name = name;
this.tag = tag; this.tag = tag;
riot.observable(this); riot.observable(this);
@ -122,14 +141,14 @@
}); });
}; };
registryUI.DockerImage._tagReduce = function (acc, e) { registryUI.DockerImage._tagReduce = function(acc, e) {
if (acc.length > 0 && registryUI.isDigit(acc[acc.length - 1].charAt(0)) == registryUI.isDigit(e)) { if (acc.length > 0 && registryUI.isDigit(acc[acc.length - 1].charAt(0)) == registryUI.isDigit(e)) {
acc[acc.length - 1] += e; acc[acc.length - 1] += e;
} else { } else {
acc.push(e); acc.push(e);
} }
return acc; return acc;
} };
registryUI.DockerImage.compare = function(e1, e2) { registryUI.DockerImage.compare = function(e1, e2) {
const tag1 = e1.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce, []); const tag1 = e1.tag.match(/./g).reduce(registryUI.DockerImage._tagReduce, []);
@ -156,10 +175,10 @@
this._fillInfoWaiting = true; this._fillInfoWaiting = true;
const oReq = new Http(); const oReq = new Http();
const self = this; const self = this;
oReq.addEventListener('loadend', function () { oReq.addEventListener('loadend', function() {
if (this.status == 200 || this.status == 202) { if (this.status == 200 || this.status == 202) {
const response = JSON.parse(this.responseText); const response = JSON.parse(this.responseText);
self.size = response.layers.reduce(function (acc, e) { self.size = response.layers.reduce(function(acc, e) {
return acc + e.size; return acc + e.size;
}, 0); }, 0);
self.sha256 = response.config.digest; self.sha256 = response.config.digest;
@ -175,12 +194,12 @@
oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/manifests/' + self.tag); oReq.open('GET', registryUI.url() + '/v2/' + self.name + '/manifests/' + self.tag);
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json'); oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
oReq.send(); oReq.send();
} };
registryUI.DockerImage.prototype.getBlobs = function(blob) { registryUI.DockerImage.prototype.getBlobs = function(blob) {
const oReq = new Http(); const oReq = new Http();
const self = this; const self = this;
oReq.addEventListener('loadend', function () { oReq.addEventListener('loadend', function() {
if (this.status == 200 || this.status == 202) { if (this.status == 200 || this.status == 202) {
const response = JSON.parse(this.responseText); const response = JSON.parse(this.responseText);
self.creationDate = new Date(response.created); self.creationDate = new Date(response.created);

View file

@ -1,18 +1,18 @@
<!-- <!--
Copyright (C) 2016 Jones Magloire @Joxit Copyright (C) 2016 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<catalog> <catalog>
<!-- Begin of tag --> <!-- Begin of tag -->
@ -35,10 +35,10 @@
<script> <script>
registryUI.catalog.instance = this; registryUI.catalog.instance = this;
registryUI.catalog.display = function () { registryUI.catalog.display = function() {
registryUI.catalog.repositories = []; registryUI.catalog.repositories = [];
var oReq = new Http(); var oReq = new Http();
oReq.addEventListener('load', function () { oReq.addEventListener('load', function() {
registryUI.catalog.repositories = []; registryUI.catalog.repositories = [];
if (this.status == 200) { if (this.status == 200) {
registryUI.catalog.repositories = JSON.parse(this.responseText).repositories || []; registryUI.catalog.repositories = JSON.parse(this.responseText).repositories || [];
@ -49,18 +49,18 @@
registryUI.snackbar(this.responseText); registryUI.snackbar(this.responseText);
} }
}); });
oReq.addEventListener('error', function () { oReq.addEventListener('error', function() {
registryUI.snackbar(this.getErrorMessage(), true); registryUI.snackbar(this.getErrorMessage(), true);
registryUI.catalog.repositories = []; registryUI.catalog.repositories = [];
}); });
oReq.addEventListener('loadend', function () { oReq.addEventListener('loadend', function() {
registryUI.catalog.loadend = true; registryUI.catalog.loadend = true;
registryUI.catalog.instance.update(); registryUI.catalog.instance.update();
}); });
oReq.open('GET', registryUI.url() + '/v2/_catalog?n=100000'); oReq.open('GET', registryUI.url() + '/v2/_catalog?n=100000');
oReq.send(); oReq.send();
}; };
registryUI.catalog.go = function (image) { registryUI.catalog.go = function(image) {
route('taglist/' + image); route('taglist/' + image);
}; };
registryUI.catalog.display(); registryUI.catalog.display();

View file

@ -32,7 +32,7 @@
return Math.floor(diff / maxSeconds[i]) + ' ' + labels[i * 2 + 1]; return Math.floor(diff / maxSeconds[i]) + ' ' + labels[i * 2 + 1];
} }
} }
} };
opts.image.on('creation-date', function(date) { opts.image.on('creation-date', function(date) {
self.date = date; self.date = date;
self.localDate = date.toLocaleString() self.localDate = date.toLocaleString()

View file

@ -1,29 +1,30 @@
<!-- <!--
Copyright (C) 2016 Jones Magloire @Joxit Copyright (C) 2016 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<remove-image> <remove-image>
<a href="#" title="This will delete the image." onclick="registryUI.removeImage.remove('{ opts.image.name }', '{ opts.image.tag }')"> <a href="#" title="This will delete the image."
onclick="registryUI.removeImage.remove('{ opts.image.name }', '{ opts.image.tag }')">
<i class="material-icons">delete</i> <i class="material-icons">delete</i>
</a> </a>
<script type="text/javascript"> <script type="text/javascript">
registryUI.removeImage = registryUI.removeImage || {}; registryUI.removeImage = registryUI.removeImage || {};
registryUI.removeImage.remove = function (name, tag) { registryUI.removeImage.remove = function(name, tag) {
var oReq = new Http(); var oReq = new Http();
oReq.addEventListener('loadend', function () { oReq.addEventListener('loadend', function() {
registryUI.taglist.refresh(); registryUI.taglist.refresh();
if (this.status == 200) { if (this.status == 200) {
if (!this.hasHeader('Docker-Content-Digest')) { if (!this.hasHeader('Docker-Content-Digest')) {
@ -32,7 +33,7 @@
} }
var digest = this.getResponseHeader('Docker-Content-Digest'); var digest = this.getResponseHeader('Docker-Content-Digest');
var oReq = new Http(); var oReq = new Http();
oReq.addEventListener('loadend', function () { oReq.addEventListener('loadend', function() {
if (this.status == 200 || this.status == 202) { if (this.status == 200 || this.status == 202) {
registryUI.taglist.refresh(); registryUI.taglist.refresh();
registryUI.snackbar('Deleting ' + name + ':' + tag + ' image. Run `registry garbage-collect config.yml` on your registry'); registryUI.snackbar('Deleting ' + name + ':' + tag + ' image. Run `registry garbage-collect config.yml` on your registry');
@ -44,7 +45,7 @@
}); });
oReq.open('DELETE', registryUI.url() + '/v2/' + name + '/manifests/' + digest); oReq.open('DELETE', registryUI.url() + '/v2/' + name + '/manifests/' + digest);
oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json'); oReq.setRequestHeader('Accept', 'application/vnd.docker.distribution.manifest.v2+json');
oReq.addEventListener('error', function () { oReq.addEventListener('error', function() {
registryUI.errorSnackbar('An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].'); registryUI.errorSnackbar('An error occurred when deleting image. Check if your server accept DELETE methods Access-Control-Allow-Methods: [\'DELETE\'].');
}); });
oReq.send(); oReq.send();

View file

@ -1,18 +1,18 @@
<!-- <!--
Copyright (C) 2016 Jones Magloire @Joxit Copyright (C) 2016 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<remove> <remove>
@ -31,28 +31,30 @@
</ul> </ul>
</div> </div>
<div class="material-popup-action"> <div class="material-popup-action">
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="registryUI.removeTag.close();">Close</material-button> <material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="registryUI.removeTag.close();">
Close
</material-button>
</div> </div>
</material-popup> </material-popup>
<script type="text/javascript"> <script type="text/javascript">
registryUI.removeTag = registryUI.removeTag || {} registryUI.removeTag = registryUI.removeTag || {}
registryUI.removeTag.update = this.update; registryUI.removeTag.update = this.update;
registryUI.removeTag.removeUrl = function (url) { registryUI.removeTag.removeUrl = function(url) {
registryUI.removeServer(url); registryUI.removeServer(url);
registryUI.removeTag.close(); registryUI.removeTag.close();
}; };
registryUI.removeTag.close = function () { registryUI.removeTag.close = function() {
registryUI.removeTag.dialog.close(); registryUI.removeTag.dialog.close();
registryUI.removeTag.update(); registryUI.removeTag.update();
}; };
registryUI.removeTag.show = function () { registryUI.removeTag.show = function() {
registryUI.removeTag.dialog.open(); registryUI.removeTag.dialog.open();
}; };
this.one('mount', function () { this.one('mount', function() {
registryUI.removeTag.dialog = this.tags['material-popup']; registryUI.removeTag.dialog = this.tags['material-popup'];
}); });
</script> </script>

View file

@ -0,0 +1,23 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<tag-history-button>
<button title="This will show the history of given tag"
onclick="registryUI.taghistory.go('{ opts.image.name }', '{ opts.image.tag }');">
<i class="material-icons">history</i>
</button>
</tag-history-button>

109
src/tags/tag-history.tag Normal file
View file

@ -0,0 +1,109 @@
<!--
Copyright (C) 2016-2018 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<tag-history>
<material-card ref="tag-history-tag" class="tag-history">
<div class="material-card-title-action">
<a href="#!taglist/{registryUI.taghistory.image}" onclick="registryUI.home();">
<i class="material-icons">arrow_back</i>
</a>
<h2>
History of { registryUI.taghistory.image }:{ registryUI.taghistory.tag } <i class="material-icons">history</i>
</h2>
</div>
<div class="material-card-title-action">
<p>Nested v1Compatibility history elements:</p>
</div>
</material-card>
<div hide="{ registryUI.taghistory.loadend }" class="spinner-wrapper">
<material-spinner></material-spinner>
</div>
<material-card each="{ guiElement in registryUI.taghistory.elements }" class="tag-history-element">
<div each="{ entry in guiElement }" class="{ entry.key }">
<div class="headline"><i class="material-icons"></i>
<p>{ entry.key }</p></div>
<div class="value"> { entry.value }</div>
</div>
</material-card>
<script type="text/javascript">
registryUI.taghistory.instance = this;
registryUI.taghistory.display = function() {
let oReq = new Http();
registryUI.taghistory.instance.update();
oReq.addEventListener('load', function() {
registryUI.taghistory.elements = [];
function modifySpecificAttributeTypes(attribute, value) {
switch (attribute) {
case "created":
return new Date(value).toLocaleString();
case "container_config":
case "config":
return "";
}
return value;
}
if (this.status === 200) {
const elements = JSON.parse(this.responseText).history || [];
for (const index in elements) {
const parsedNestedElements = JSON.parse(elements[index].v1Compatibility || {});
const guiElements = [];
for (const attribute in parsedNestedElements) {
if (parsedNestedElements.hasOwnProperty(attribute)) {
const value = parsedNestedElements[attribute];
const guiElement = {
"key": attribute,
"value": modifySpecificAttributeTypes(attribute, value)
};
guiElements.push(guiElement);
}
}
registryUI.taghistory.elements.push(guiElements);
}
} else if (this.status === 404) {
registryUI.snackbar('Manifest could not be fetched', true);
} else {
registryUI.snackbar(this.responseText);
}
});
oReq.addEventListener('error', function() {
registryUI.snackbar(this.getErrorMessage(), true);
registryUI.taghistory.elements = [];
});
oReq.addEventListener('loadend', function() {
registryUI.taghistory.loadend = true;
registryUI.taghistory.instance.update();
});
oReq.open('GET', registryUI.url() + '/v2/' + registryUI.taghistory.image + '/manifests/' + registryUI.taghistory.tag);
oReq.send();
};
registryUI.taghistory.display();
registryUI.taghistory.instance.update();
</script>
</tag-history>

View file

@ -1,18 +1,18 @@
<!-- <!--
Copyright (C) 2016 Jones Magloire @Joxit Copyright (C) 2016 Jones Magloire @Joxit
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<taglist> <taglist>
<!-- Begin of tag --> <!-- Begin of tag -->
@ -33,8 +33,13 @@
<th></th> <th></th>
<th>Creation date</th> <th>Creation date</th>
<th>Size</th> <th>Size</th>
<th class="{ registryUI.taglist.asc ? 'material-card-th-sorted-ascending' : 'material-card-th-sorted-descending' }" onclick="registryUI.taglist.reverse();">Tag</th>
<th show="{ registryUI.isImageRemoveActivated }"></th> <th
class="{ registryUI.taglist.asc ? 'material-card-th-sorted-ascending' : 'material-card-th-sorted-descending' }"
onclick="registryUI.taglist.reverse();">Tag
</th>
<th class="show-tag-history">History</th>
<th class="remove-tag" show="{ registryUI.isImageRemoveActivated }"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -43,9 +48,18 @@
<td class="copy-to-clipboard"> <td class="copy-to-clipboard">
<copy-to-clipboard image={ image }/> <copy-to-clipboard image={ image }/>
</td> </td>
<td><image-date image="{ image }" /></td> <td>
<td><image-size image="{ image }" /></td> <image-date image="{ image }"/>
<td><image-tag image="{ image }" /></td> </td>
<td>
<image-size image="{ image }"/>
</td>
<td>
<image-tag image="{ image }"/>
</td>
<td class="show-tag-history">
<tag-history-button image={ image }/>
</td>
<td show="{ registryUI.isImageRemoveActivated }"> <td show="{ registryUI.isImageRemoveActivated }">
<remove-image image={ image }/> <remove-image image={ image }/>
</td> </td>
@ -55,12 +69,12 @@
</material-card> </material-card>
<script> <script>
registryUI.taglist.instance = this; registryUI.taglist.instance = this;
registryUI.taglist.display = function () { registryUI.taglist.display = function() {
registryUI.taglist.tags = []; registryUI.taglist.tags = [];
if (route.routeName == 'taglist') { if (route.routeName == 'taglist') {
var oReq = new Http(); var oReq = new Http();
registryUI.taglist.instance.update(); registryUI.taglist.instance.update();
oReq.addEventListener('load', function () { oReq.addEventListener('load', function() {
registryUI.taglist.tags = []; registryUI.taglist.tags = [];
if (this.status == 200) { if (this.status == 200) {
registryUI.taglist.tags = JSON.parse(this.responseText).tags || []; registryUI.taglist.tags = JSON.parse(this.responseText).tags || [];
@ -73,11 +87,11 @@
registryUI.snackbar(this.responseText, true); registryUI.snackbar(this.responseText, true);
} }
}); });
oReq.addEventListener('error', function () { oReq.addEventListener('error', function() {
registryUI.snackbar(this.getErrorMessage(), true); registryUI.snackbar(this.getErrorMessage(), true);
registryUI.taglist.tags = []; registryUI.taglist.tags = [];
}); });
oReq.addEventListener('loadend', function () { oReq.addEventListener('loadend', function() {
registryUI.taglist.loadend = true; registryUI.taglist.loadend = true;
registryUI.taglist.instance.update(); registryUI.taglist.instance.update();
}); });
@ -89,7 +103,7 @@
registryUI.taglist.display(); registryUI.taglist.display();
registryUI.taglist.instance.update(); registryUI.taglist.instance.update();
registryUI.taglist.reverse = function () { registryUI.taglist.reverse = function() {
if (registryUI.taglist.asc) { if (registryUI.taglist.asc) {
registryUI.taglist.tags.reverse(); registryUI.taglist.tags.reverse();
registryUI.taglist.asc = false; registryUI.taglist.asc = false;
@ -99,7 +113,7 @@
} }
registryUI.taglist.instance.update(); registryUI.taglist.instance.update();
}; };
registryUI.taglist.refresh = function () { registryUI.taglist.refresh = function() {
route(registryUI.taglist.name); route(registryUI.taglist.name);
}; };
</script> </script>