Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extended Registry API v2 Support (images details, labels support, tag list) #84

Merged
merged 2 commits into from
Apr 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ By default 20 repositories will be listed per page. To adjust this number, to
let's say 50 pass `-e ENV_DEFAULT_REPOSITORIES_PER_PAGE=50` to your `docker run`
command.

# Default tags per page

By default 10 tags will be listed per page. To adjust this number, to
let's say 5 pass `-e ENV_DEFAULT_TAGS_PER_PAGE=5` to your `docker run`
command. Note that providing a big number will result in a heavy load on browsers.

# Contributions are welcome!

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].
Expand Down
1 change: 1 addition & 0 deletions apache-site.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

# See https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-configure-your-server-to-work-with-html5mode
<Directory /var/www/html>
Options -Indexes
RewriteEngine on

# Don't rewrite files or directories
Expand Down
2 changes: 1 addition & 1 deletion app/app-mode.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"browseOnly": true, "defaultRepositoriesPerPage": 20}
{"browseOnly": true, "defaultRepositoriesPerPage": 20 , "defaultTagsPerPage":10}
6 changes: 5 additions & 1 deletion app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,18 @@ angular
templateUrl: 'repository/repository-detail.html',
controller: 'RepositoryDetailController',
}).
when('/repository/:repositoryUser/:repositoryName/:tagsPerPage?/:tagPage?', {
templateUrl: 'repository/repository-detail.html',
controller: 'RepositoryDetailController'
}).
when('/repository/:repositoryUser/:repositoryName/tags/:searchName?', {
templateUrl: 'repository/repository-detail.html',
controller: 'RepositoryController',
}).
when('/about', {
templateUrl: 'about.html',
}).
when('/tag/:repositoryUser/:repositoryName/:tagName/:imageId', {
when('/tag/:repositoryUser/:repositoryName/:tagName/', {
templateUrl: 'tag/tag-detail.html',
controller: 'TagController',
}).
Expand Down
35 changes: 24 additions & 11 deletions app/image/image-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,39 @@
* Controller of the docker-registry-frontend
*/
angular.module('image-controller', ['registry-services', 'app-mode-services'])
.controller('ImageController', ['$scope', '$route', '$routeParams', '$location', '$log', '$filter', 'Image', 'Ancestry', 'AppMode',
function($scope, $route, $routeParams, $location, $log, $filter, Image, Ancestry, AppMode){
$scope.imageId = $route.current.params.imageId;
$scope.imageDetails = Image.query( {imageId: $scope.imageId} );
$scope.imageAncestry = Ancestry.query( {imageId: $scope.imageId} );
.controller('ImageController', ['$scope', '$route', '$routeParams', '$location', '$log', '$filter', 'Manifest', 'AppMode',
function($scope, $route, $routeParams, $location, $log, $filter, Manifest, AppMode){


$scope.appMode = AppMode.query();
$scope.totalImageSize = 0;
$scope.imageDetails = Manifest.query({repoUser: $scope.repositoryUser, repoName: $scope.repositoryName, tagName: $scope.tagName});




// This is not totally working right now (problem with big layers)
/**
* Calculates the total download size for the image based on
* it's ancestry.
* it's layers.
*/
/*
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you effectively delete code by commenting it out, please simply remove it instead of keeping it. I left it there to pick it up when I have time. Now that you've implemented it, there's now need to keep the old code around unless there's something you want to implement in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That(s because I already started to implement it but anyway there are a lot of comments to remove I agree.

$scope.totalImageSize = null;
$scope.calculateTotalImageSize = function() {
$scope.totalImageSize = 0;
angular.forEach($scope.imageAncestry, function (id, key) {
/* We have to use the $promise object here to be sure the result is accessible */
Image.get( {imageId: id} ).$promise.then(function (result) {
if (!isNaN(result.Size-0)) {
$scope.totalImageSize += result.Size;
var size;
angular.forEach($scope.imageDetails.fsLayers, function (id, key) {

Blob.query({repoUser: $scope.repositoryUser, repoName: $scope.repositoryName, digest: id.blobSum}).$promise.then( function(data, headers){
size = data;
console.log(data)
console.log(size)
if(!isNaN(data.contentLength-0)){
$scope.totalImageSize += data.contentLength;
}
});
});
};
*/

}]);
51 changes: 29 additions & 22 deletions app/image/image-details-directive.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ <h2>
<tab-heading>
General information
</tab-heading>
<form class="form-horizontal" role="form">
<form class="form-horizontal" role="form">
<div class="form-group">
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-user"></span> Author</label>
<div class="col-sm-10">
Expand All @@ -28,7 +28,7 @@ <h2>
</p>
</div>
</div>
<div class="form-group">
<div class="form-group" >
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-calendar"></span> Created</label>
<div class="col-sm-10">
<p class="form-control-static">
Expand All @@ -52,22 +52,21 @@ <h2>
<div class="form-group">
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-qrcode"></span> ID</label>
<div class="col-sm-10">
<p class="form-control-static"><a href="image/{{imageDetails.id}}">{{imageDetails.id | limitTo: 12}}</a></p>
<p class="form-control-static">
{{imageDetails.id | limitTo: 12}}
</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-arrow-up"></span> Parent's ID</label>
<!-- <div class="form-group">
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-arrow-up"></span> Parent's layer ID</label>
<div class="col-sm-10">
<p class="form-control-static"><a href="image/{{imageDetails.parent}}">{{imageDetails.parent | limitTo: 12}}</a></p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-compressed"></span> Size <small>(not including base image sizes)</small></label>
<div class="col-sm-10">
<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>
<p class="form-control-static">
{{imageDetails.parentLayer | limitTo: 12}}
</p>
</div>
</div>
<div class="form-group">
</div> -->

<!-- <div class="form-group">
<label class="col-sm-2 control-label"><span class="glyphicon glyphicon-compressed"></span> Size <small>(including base image sizes)</small></label>
<div class="col-sm-10">
<p class="form-control-static">
Expand All @@ -79,19 +78,27 @@ <h2>
</button>
</p>
</div>
</div>
</div> -->
</form>
</tab>
<tab>
<tab-heading>
Image Ancestry
Labels
</tab-heading>
<div class="list-group">
<a ng-repeat="img in imageAncestry" href="image/{{img}}" class="list-group-item" ng-class="{active: imageDetails.id==img}">
<span class="glyphicon" ng-class="{'glyphicon-arrow-down': ($first&&!$last)||$middle}"></span>
{{img | limitTo:12}}
</a>
</div>
<table class="table">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="(key, value) in imageDetails.labels">
<td>{{key}}</td>
<td>{{value}}</td>
</tr>
</tbody>
</table>
</tab>
</tabset>

2 changes: 1 addition & 1 deletion app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ <h3 class="text-muted">Docker Registry Frontend</h3>
<script src="bower_components/moment/moment.js"></script>
<script src="bower_components/angular-moment/angular-moment.js"></script>
<script src="bower_components/angular-smart-table/dist/smart-table.min.js"></script>
<script src="bower_components/angular-filter/dist/angular-filter.js"></script>
<script src="bower_components/angular-filter/dist/angular-filter.min.js"></script>
<script src="bower_components/angular-bootstrap-checkbox/angular-bootstrap-checkbox.js"></script>
<!-- endbower -->
<!-- endbuild -->
Expand Down
15 changes: 15 additions & 0 deletions app/repository/repository-detail-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,22 @@ angular.module('repository-detail-controller', ['registry-services', 'app-mode-s
$scope.repository = $scope.repositoryUser + '/' + $scope.repositoryName;

$scope.appMode = AppMode.query();
$scope.maxTagsPage = undefined;

// Method used to disable next & previous links
$scope.getNextHref = function (){
if($scope.maxTagsPage > $scope.tagsCurrentPage){
var nextPageNumber = $scope.tagsCurrentPage + 1;
return '/repository/'+$scope.repository+'/'+ $scope.tagsPerPage +'/' +nextPageNumber;
}
return '#'
}
$scope.getFirstHref = function (){
if($scope.tagsCurrentPage > 1){
return '/repository/'+$scope.repository+'/' + $scope.tagsPerPage +'/1';
}
return '#'
}
// selected repos
$scope.selectedRepositories = [];

Expand Down
35 changes: 35 additions & 0 deletions app/repository/repository-detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,38 @@ <h1>
</h1>

<tag-list></tag-list>
<nav>
<ul class="pager">
<li class="previous" ng-class="{disabled: tagsCurrentPage <= 1}">
<a href="{{getFirstHref()}}" >
<span aria-hidden="true">&larr;</span>
First Page
</a>
</li>
<li>
<div class="btn-group" role="group">
<a href="#" class="btn btn-default dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-book" aria-hidden="true"> </span> / page:
<span ng-show="tagsPerPage">{{tagsPerPage}}</span>
<span ng-show="!tagsPerPage">all</span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="repository/{{repository}}">Show all</a></li>
<li role="separator" class="divider"></li>
<li><a href="repository/{{repository}}/10/1">10</a></li>
<li><a href="repository/{{repository}}/20/1">20</a></li>
<li><a href="repository/{{repository}}/40/1">40</a></li>
<li><a href="repository/{{repository}}/60/1">60</a></li>
<li><a href="repository/{{repository}}/80/1">80</a></li>
<li><a href="repository/{{repository}}/100/1">100</a></li>
</ul>
</div>
</li>
<li class="next" ng-class="{disabled: maxTagsPage <= tagsCurrentPage || !tagsPerPage}">
<a href="{{getNextHref()}}">
Next <span aria-hidden="true">&rarr;</span>
</a>
</li>
</ul>
</nav>
5 changes: 3 additions & 2 deletions app/repository/repository-list-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ angular.module('repository-list-controller', ['registry-services', 'app-mode-ser
$scope.repositoryName = $route.current.params.repositoryName;
$scope.repository = $scope.repositoryUser + '/' + $scope.repositoryName;

$scope.appMode = AppMode.query();

$scope.appMode = AppMode.query( function (result){
$scope.defaultTagsPerPage = result.defaultTagsPerPage
});
// How to query the repository
$scope.reposPerPage = $route.current.params.reposPerPage;
$scope.lastNamespace = $route.current.params.lastNamespace;
Expand Down
2 changes: 1 addition & 1 deletion app/repository/repository-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ <h1>Repositories</h1>
<!--<input type="checkbox" name="selectedRepos[]" value="{{repo.name}}" ng-model="repo.selected" ng-hide="appMode.browseOnly">-->
</td>
<td class="grow">
<a href="repository/{{repo.name}}"><!--<span class="glyphicon glyphicon-book"></span>--> {{repo.name|trim:username+'/'}}</a>
<a href="repository/{{repo.name}}/{{defaultTagsPerPage}}"><!--<span class="glyphicon glyphicon-book"></span>--> {{repo.name|trim:username+'/'}}</a>
</td>
</tr>
</tbody>
Expand Down
89 changes: 81 additions & 8 deletions app/services/registry-services.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ angular.module('registry-services', ['ngResource'])
isArray: true,
transformResponse: function(data/*, headers*/){
var res = [];
console.log(data);
var resp = angular.fromJson(data);
for (var idx in resp.tags){
res.push({
Expand Down Expand Up @@ -142,13 +141,87 @@ angular.module('registry-services', ['ngResource'])
},
});
}])
.factory('Image', ['$resource', function($resource){
return $resource('/v1/images/:imageId/json', {}, {
'query': { method:'GET', isArray: false},
.factory('Manifest', ['$resource', function($resource){

return $resource('/v2/:repoUser/:repoName/manifests/:tagName', {}, {
// Response example:
// {
// "schemaVersion": 1,
// "name": "arthur/busybox",
// "tag": "demo",
// "architecture": "amd64",
// "fsLayers": [
// {
// "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
// },
// {
// "blobSum": "sha256:d7e8ec85c5abc60edf74bd4b8d68049350127e4102a084f22060f7321eac3586"
// }
// ],
// "history": [
// {
// "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\"}"
// },
// {
// "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}"
// }
// ],
// }
'query': {
method:'GET',
isArray: false,
transformResponse: function(data, headers){
var res = {};
var history = [];
var tmp;
var resp = angular.fromJson(data);
var v1Compatibility = undefined;

for (var idx in resp.history){

v1Compatibility = angular.fromJson(resp.history[idx].v1Compatibility);

if(v1Compatibility !== undefined){
tmp = {
id : v1Compatibility.id,
os : v1Compatibility.os,
docker_version: v1Compatibility.docker_version,
created: v1Compatibility.created,
// parentLayer: v1Compatibility.parent
};
if(v1Compatibility.author){
tmp.author = v1Compatibility.author;
}
if(v1Compatibility.config && v1Compatibility.config.Labels){
tmp.labels = v1Compatibility.config.Labels;
}
history.push(tmp);
}
}
if(history.length > 0){
res = history[0];
res.history = history;
}
res.fsLayers = resp.fsLayers;
res.digest = headers('docker-content-digest');
res.architecture = resp.architecture;
return res;
},
}
});
}])
.factory('Ancestry', ['$resource', function($resource){
return $resource('/v1/images/:imageId/ancestry', {}, {
'query': { method:'GET', isArray: true},
// This is not totally working right now (problem with big layers)
/*
.factory('Blob', ['$resource', function($resource){
return $resource('/v2/:repoUser/:repoName/blobs/:digest', {}, {

'query': {
method:'HEAD',
interceptor: function(data, headers){
var res = {contentLength: parseInt(headers('content-length'))};
return res;
}
}

});
}]);
}]) */ ;
Loading