Skip to content

Commit d4db466

Browse files
committed
Merge branch 'v2' of github.com:Filirom1/docker-registry-frontend into v2
2 parents f287a38 + 095e0fe commit d4db466

File tree

14 files changed

+306
-107
lines changed

14 files changed

+306
-107
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ By default 20 repositories will be listed per page. To adjust this number, to
136136
let's say 50 pass `-e ENV_DEFAULT_REPOSITORIES_PER_PAGE=50` to your `docker run`
137137
command.
138138

139+
# Default tags per page
140+
141+
By default 10 tags will be listed per page. To adjust this number, to
142+
let's say 5 pass `-e ENV_DEFAULT_TAGS_PER_PAGE=5` to your `docker run`
143+
command. Note that providing a big number will result in a heavy load on browsers.
144+
139145
# Contributions are welcome!
140146

141147
If you like the application, I invite you to contribute and report bugs or feature request on the project's github page: [https://github.com/kwk/docker-registry-frontend][3].

app/app-mode.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"browseOnly": true, "defaultRepositoriesPerPage": 20}
1+
{"browseOnly": true, "defaultRepositoriesPerPage": 20 , "defaultTagsPerPage":10}

app/app.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,18 @@ angular
6464
templateUrl: 'repository/repository-detail.html',
6565
controller: 'RepositoryDetailController',
6666
}).
67+
when('/repository/:repositoryUser/:repositoryName/:tagsPage?', {
68+
templateUrl: 'repository/repository-detail.html',
69+
controller: 'RepositoryDetailController'
70+
}).
6771
when('/repository/:repositoryUser/:repositoryName/tags/:searchName?', {
6872
templateUrl: 'repository/repository-detail.html',
6973
controller: 'RepositoryController',
7074
}).
7175
when('/about', {
7276
templateUrl: 'about.html',
7377
}).
74-
when('/tag/:repositoryUser/:repositoryName/:tagName/:imageId', {
78+
when('/tag/:repositoryUser/:repositoryName/:tagName/', {
7579
templateUrl: 'tag/tag-detail.html',
7680
controller: 'TagController',
7781
}).

app/image/image-controller.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,31 @@
88
* Controller of the docker-registry-frontend
99
*/
1010
angular.module('image-controller', ['registry-services', 'app-mode-services'])
11-
.controller('ImageController', ['$scope', '$route', '$routeParams', '$location', '$log', '$filter', 'Image', 'Ancestry', 'AppMode',
12-
function($scope, $route, $routeParams, $location, $log, $filter, Image, Ancestry, AppMode){
13-
$scope.imageId = $route.current.params.imageId;
14-
$scope.imageDetails = Image.query( {imageId: $scope.imageId} );
15-
$scope.imageAncestry = Ancestry.query( {imageId: $scope.imageId} );
11+
.controller('ImageController', ['$scope', '$route', '$routeParams', '$location', '$log', '$filter', 'Manifest', 'Blob', 'AppMode',
12+
function($scope, $route, $routeParams, $location, $log, $filter, Manifest, Blob, AppMode){
13+
14+
1615
$scope.appMode = AppMode.query();
16+
$scope.totalImageSize = 0;
17+
$scope.imageDetails = Manifest.query({repoUser: $scope.repositoryUser, repoName: $scope.repositoryName, tagName: $scope.tagName});
18+
19+
20+
21+
1722
/**
1823
* Calculates the total download size for the image based on
19-
* it's ancestry.
24+
* it's layers.
2025
*/
2126
$scope.totalImageSize = null;
2227
$scope.calculateTotalImageSize = function() {
2328
$scope.totalImageSize = 0;
24-
angular.forEach($scope.imageAncestry, function (id, key) {
25-
/* We have to use the $promise object here to be sure the result is accessible */
26-
Image.get( {imageId: id} ).$promise.then(function (result) {
27-
if (!isNaN(result.Size-0)) {
28-
$scope.totalImageSize += result.Size;
29+
angular.forEach($scope.imageDetails.fsLayers, function (id, key) {
30+
Blob.query({repoUser: $scope.repositoryUser, repoName: $scope.repositoryName, digest: id.blobSum}).$promise.then( function(data, headers){
31+
if(!isNaN(data.contentLength-0)){
32+
$scope.totalImageSize += data.contentLength;
2933
}
3034
});
3135
});
3236
};
37+
3338
}]);

app/image/image-details-directive.html

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ <h2>
3737
</p>
3838
</div>
3939
</div>
40+
<div class="form-group">
41+
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-align-justify"></span> Layers</label>
42+
<div class="col-sm-10">
43+
<p class="form-control-static">{{imageDetails.layers}}</p>
44+
</div>
45+
</div>
4046
<div class="form-group">
4147
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-eye-open"></span> Docker version</label>
4248
<div class="col-sm-10">
@@ -52,46 +58,63 @@ <h2>
5258
<div class="form-group">
5359
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-qrcode"></span> ID</label>
5460
<div class="col-sm-10">
55-
<p class="form-control-static"><a href="image/{{imageDetails.id}}">{{imageDetails.id | limitTo: 12}}</a></p>
61+
<p class="form-control-static">
62+
{{imageDetails.id | limitTo: 12}}
63+
</p>
5664
</div>
5765
</div>
5866
<div class="form-group">
5967
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-arrow-up"></span> Parent's ID</label>
6068
<div class="col-sm-10">
61-
<p class="form-control-static"><a href="image/{{imageDetails.parent}}">{{imageDetails.parent | limitTo: 12}}</a></p>
62-
</div>
63-
</div>
64-
<div class="form-group">
65-
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-compressed"></span> Size <small>(not including base image sizes)</small></label>
66-
<div class="col-sm-10">
67-
<p class="form-control-static">{{imageDetails.Size/1024/1024 | number: 2}} <b>MB</b> {{imageDetails.Size / 1024 | number: 2}} <b>KB</b> {{imageDetails.Size}} <b>B</b></p>
69+
<p class="form-control-static">
70+
{{imageDetails.parent | limitTo: 12}}
71+
</p>
6872
</div>
6973
</div>
7074
<div class="form-group">
7175
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-compressed"></span> Size <small>(including base image sizes)</small></label>
7276
<div class="col-sm-10">
7377
<p class="form-control-static">
7478
<span ng-show="totalImageSize!==null">
75-
{{totalImageSize/1024/1024 | number: 2}} <b>MB</b> {{totalImageSize / 1024 | number: 2}} <b>KB</b> {{totalImageSize}} <b>B</b>
79+
{{totalImageSize/1024/1024 | number: 2}} <b>MB</b>
7680
</span>
7781
<button type="submit" class="btn btn-info" ng-click="calculateTotalImageSize()" ng-show="totalImageSize===null">
7882
<span class="glyphicon glyphicon-stats"></span> Calculate
7983
</button>
8084
</p>
8185
</div>
8286
</div>
87+
8388
</form>
8489
</tab>
8590
<tab>
8691
<tab-heading>
87-
Image Ancestry
92+
Labels
93+
</tab-heading>
94+
<table class="table">
95+
<thead>
96+
<tr>
97+
<th>Key</th>
98+
<th>Value</th>
99+
</tr>
100+
</thead>
101+
<tbody>
102+
<tr ng-repeat="(key, value) in imageDetails.labels">
103+
<td>{{key}}</td>
104+
<td>{{value}}</td>
105+
</tr>
106+
</tbody>
107+
</table>
108+
</tab>
109+
<tab>
110+
<tab-heading>
111+
Dockerfile
88112
</tab-heading>
89-
<div class="list-group">
90-
<a ng-repeat="img in imageAncestry" href="image/{{img}}" class="list-group-item" ng-class="{active: imageDetails.id==img}">
91-
<span class="glyphicon" ng-class="{'glyphicon-arrow-down': ($first&&!$last)||$middle}"></span>
92-
{{img | limitTo:12}}
93-
</a>
94-
</div>
113+
<pre>
114+
<code ng-repeat="instruction in imageDetails.dockerfile track by $index">
115+
{{instruction}}
116+
</code>
117+
</pre>
95118
</tab>
96119
</tabset>
97120

app/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ <h3 class="text-muted">Docker Registry Frontend</h3>
6969
<script src="bower_components/moment/moment.js"></script>
7070
<script src="bower_components/angular-moment/angular-moment.js"></script>
7171
<script src="bower_components/angular-smart-table/dist/smart-table.min.js"></script>
72-
<script src="bower_components/angular-filter/dist/angular-filter.js"></script>
72+
<script src="bower_components/angular-filter/dist/angular-filter.min.js"></script>
7373
<script src="bower_components/angular-bootstrap-checkbox/angular-bootstrap-checkbox.js"></script>
7474
<!-- endbower -->
7575
<!-- endbuild -->

app/repository/repository-detail-controller.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,26 @@ angular.module('repository-detail-controller', ['registry-services', 'app-mode-s
2020
$scope.repositoryName = $route.current.params.repositoryName;
2121
$scope.repository = $scope.repositoryUser + '/' + $scope.repositoryName;
2222

23-
$scope.appMode = AppMode.query();
23+
$scope.appMode = AppMode.query( function (result){
24+
$scope.tagsPerPage = result.defaultTagsPerPage
25+
});
26+
$scope.maxTagsPage = undefined;
2427

28+
// Method used to disable next & previous links
29+
$scope.getNextHref = function (){
30+
if($scope.maxTagsPage > $scope.tagsCurrentPage){
31+
var nextPageNumber = $scope.tagsCurrentPage + 1;
32+
return '/repository/'+$scope.repository+'/'+ nextPageNumber;
33+
}
34+
return '#'
35+
}
36+
$scope.getPreviousHref = function (){
37+
if($scope.tagsCurrentPage > 1){
38+
var previousPageNumber = $scope.tagsCurrentPage - 1;
39+
return '/repository/'+$scope.repository+'/'+ previousPageNumber;
40+
}
41+
return '#'
42+
}
2543
// selected repos
2644
$scope.selectedRepositories = [];
2745

app/repository/repository-detail.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,18 @@ <h1>
2323
</h1>
2424

2525
<tag-list></tag-list>
26+
<nav ng-if="maxTagsPage">
27+
<ul class="pager">
28+
<li class="previous" ng-class="{disabled: tagsCurrentPage <= 1}">
29+
<a href="{{getPreviousHref()}}" >
30+
<span aria-hidden="true">&larr;</span>
31+
Previous
32+
</a>
33+
</li>
34+
<li class="next" ng-class="{disabled: maxTagsPage <= tagsCurrentPage}">
35+
<a href="{{getNextHref()}}">
36+
Next <span aria-hidden="true">&rarr;</span>
37+
</a>
38+
</li>
39+
</ul>
40+
</nav>

app/services/registry-services.js

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ angular.module('registry-services', ['ngResource'])
110110
isArray: true,
111111
transformResponse: function(data/*, headers*/){
112112
var res = [];
113-
console.log(data);
114113
var resp = angular.fromJson(data);
115114
for (var idx in resp.tags){
116115
res.push({
@@ -142,13 +141,97 @@ angular.module('registry-services', ['ngResource'])
142141
},
143142
});
144143
}])
145-
.factory('Image', ['$resource', function($resource){
146-
return $resource('/v1/images/:imageId/json', {}, {
147-
'query': { method:'GET', isArray: false},
144+
.factory('Manifest', ['$resource', function($resource){
145+
146+
return $resource('/v2/:repoUser/:repoName/manifests/:tagName', {}, {
147+
// Response example:
148+
// {
149+
// "schemaVersion": 1,
150+
// "name": "arthur/busybox",
151+
// "tag": "demo",
152+
// "architecture": "amd64",
153+
// "fsLayers": [
154+
// {
155+
// "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
156+
// },
157+
// {
158+
// "blobSum": "sha256:d7e8ec85c5abc60edf74bd4b8d68049350127e4102a084f22060f7321eac3586"
159+
// }
160+
// ],
161+
// "history": [
162+
// {
163+
// "v1Compatibility": "{\"id\":\"3e1018ee907f25aef8c50016296ab33624796511fdbfdbbdeca6a3ed2d0ba4e2\",\"parent\":\"176dfc9032a1ec3ac8586b383e325e1a65d1f5b5e6f46c2a55052b5aea8310f7\",\"created\":\"2016-01-12T17:47:39.251310827Z\",\"container\":\"2732d16efa11ab7da6393645e85a7f2070af94941a782a69e86457a2284f4a69\",\"container_config\":{\"Hostname\":\"ea7fe68f39fd\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) LABEL awesome=Not yet!\"],\"Image\":\"176dfc9032a1ec3ac8586b383e325e1a65d1f5b5e6f46c2a55052b5aea8310f7\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":{\"awesome\":\"Not yet!\",\"test\":\"yes\",\"working\":\"true\"}},\"docker_version\":\"1.9.1\",\"author\":\"Arthur\",\"config\":{\"Hostname\":\"ea7fe68f39fd\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"sh\"],\"Image\":\"176dfc9032a1ec3ac8586b383e325e1a65d1f5b5e6f46c2a55052b5aea8310f7\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":{\"awesome\":\"Not yet!\",\"test\":\"yes\",\"working\":\"true\"}},\"architecture\":\"amd64\",\"os\":\"linux\"}"
164+
// },
165+
// {
166+
// "v1Compatibility": "{\"id\":\"5c5fb281b01ee091a0fffa5b4a4c7fb7d358e7fb7c49c263d6d7a4e35d199fd0\",\"created\":\"2015-12-08T18:31:50.979824705Z\",\"container\":\"ea7fe68f39fd0df314e841247fb940ddef4c02ab7b5edb0ee724adc3174bc8d9\",\"container_config\":{\"Hostname\":\"ea7fe68f39fd\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ADD file:c295b0748bf05d4527f500b62ff269bfd0037f7515f1375d2ee474b830bad382 in /\"],\"Image\":\"\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"ea7fe68f39fd\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":1113436}"
167+
// }
168+
// ],
169+
// }
170+
'query': {
171+
method:'GET',
172+
isArray: false,
173+
transformResponse: function(data, headers){
174+
var res = {};
175+
var history = [];
176+
var tmp;
177+
var resp = angular.fromJson(data);
178+
var v1Compatibility = undefined;
179+
var dockerfile = [];
180+
var cmd;
181+
var instruction;
182+
183+
for (var idx in resp.history){
184+
185+
v1Compatibility = angular.fromJson(resp.history[idx].v1Compatibility);
186+
187+
if(v1Compatibility !== undefined){
188+
tmp = {
189+
id : v1Compatibility.id,
190+
os : v1Compatibility.os,
191+
docker_version: v1Compatibility.docker_version,
192+
created: v1Compatibility.created,
193+
parent: v1Compatibility.parent
194+
};
195+
if(v1Compatibility.author){
196+
tmp.author = v1Compatibility.author;
197+
}
198+
if(v1Compatibility.config && v1Compatibility.config.Labels){
199+
tmp.labels = v1Compatibility.config.Labels;
200+
}
201+
if(v1Compatibility.container_config && v1Compatibility.container_config.Cmd){
202+
cmd = v1Compatibility.container_config.Cmd
203+
instruction = cmd.join(' ').replace('/bin/sh -c #(nop) ', '').replace('/bin/sh -c ', 'RUN ')
204+
dockerfile.unshift(instruction)
205+
}
206+
history.push(tmp);
207+
}
208+
}
209+
if(history.length > 0){
210+
res = history[0];
211+
res.history = history;
212+
}
213+
res.fsLayers = resp.fsLayers;
214+
res.digest = headers('docker-content-digest');
215+
res.architecture = resp.architecture;
216+
res.dockerfile = dockerfile
217+
res.layers = dockerfile.length
218+
return res;
219+
},
220+
}
148221
});
149222
}])
150-
.factory('Ancestry', ['$resource', function($resource){
151-
return $resource('/v1/images/:imageId/ancestry', {}, {
152-
'query': { method:'GET', isArray: true},
223+
.factory('Blob', ['$resource', function($resource){
224+
return $resource('/v2/:repoUser/:repoName/blobs/:digest', {}, {
225+
226+
'query': {
227+
method:'HEAD',
228+
interceptor: {
229+
response: function(response){
230+
var res = {contentLength: parseInt(response.headers('content-length'))};
231+
return res;
232+
}
233+
}
234+
}
235+
153236
});
154-
}]);
237+
}]) ;

0 commit comments

Comments
 (0)