docker-registry-frontend/app/services/registry-services.js
Arthur De Kimpe d5dd87d1c9 Add image details support with API v2 (#84)
Revert changes on default projet file

Add detailed information for tag listing

Removed useless comments, divs, etc -> refactoring

Prevent bugs from future changes of API

Add digest attribute to Manifest query response

Add basic pagination support to tag listing

Add environment variable for tags per page

Fix bug of image history without config + disabled parent id

Fix tags pagination system - Fetch infos for all pages

add missing comma

 Fetch infos for all pages -> simpler fix

Disable apache directory listing feature

Update tag pagination system to make it feel more like repository pagination system
2016-04-11 09:46:40 +02:00

228 lines
10 KiB
JavaScript

'use strict';
// This is the main entrypoint to interact with the Docker registry.
// Helpful resources
//
// https://docs.angularjs.org/tutorial/step_11
// https://docs.angularjs.org/api/ngResource/service/$resource
angular.module('registry-services', ['ngResource'])
.factory('RegistryHost', ['$resource', function($resource){
return $resource('registry-host.json', {}, {
'query': {
method:'GET',
isArray: false,
},
});
}])
// Repository returns:
//
// {
// repos: [
// {username: 'SomeNamespace', name: 'SomeNamespace/SomeRepo1', selected: true|false},
// {username: 'SomeOtherNamespace', name: 'SomeOtherNamespace/SomeRepo2', selected: true|false},
// {username: 'SomeCompletelyDifferenNamespace', name: 'SomeCompletelyDifferenNamespace/SomeRepo3', selected: true|false}
// ],
// nextLink: '/v2/_catalog?last=SomeNamespace%F2SomeRepo&n=1'
// }
//
// The "nextLink" element is a preparation for supporting pagination
// (see https://github.com/docker/distribution/blob/master/docs/spec/api.md#pagination)
//
// On subsequent calls to "Repository()" you may pass in "n" as the number of
// elements per page as well as "last" which is the "nextLink" from the last
// call to Repository.
.factory('Repository', ['$resource', function($resource){
return $resource('/v2/_catalog?n=:n&last=:last', {}, {
'query': {
method:'GET',
isArray: false,
transformResponse: function(data, headers){
var repos = angular.fromJson(data).repositories;
// Extract the "last=" part from Link header:
//
// Link: </v2/_catalog?last=namespace%2repository&n=10>; rel="next"
//
// We only want to extrace the "last" part and store it like this
//
// lastNamespace = namespace
// lastRepository = repository
//
// TODO: Can we clean this up a bit?
var last = undefined;
var lastNamespace = undefined;
var lastRepository = undefined;
var linkHeader = headers()['link'];
//console.log('linkHeader='+linkHeader);
if (linkHeader) {
var lastUrl = ''+linkHeader.split(';')[0].replace('<','').replace('>','');
var startPos = lastUrl.search('last=');
//console.log('startPos=' + startPos);
if (startPos >= 0) {
var endPos = lastUrl.substring(startPos).search('&');
//console.log('endPos=' + endPos);
if (endPos >= 0) {
last = lastUrl.substring(startPos+'last='.length, startPos+endPos);
//console.log('last=' + last);
var parts = last.split('%2F');
//console.log('parts=' + parts);
if (parts.length == 2) {
lastNamespace = parts[0];
lastRepository = parts[1];
}
}
}
}
var ret = {
repos: [],
lastNamespace: lastNamespace,
lastRepository: lastRepository
};
angular.forEach(repos, function(value/*, key*/) {
ret.repos.push({
username: ''+value.split('/')[0],
name: value,
selected: false
});
});
return ret;
}
},
'delete': {
url: '/v2/repositories/:repoUser/:repoName/',
method: 'DELETE',
}
});
}])
.factory('Tag', ['$resource', function($resource){
// TODO: rename :repo to repoUser/repoString for convenience.
// https://github.com/docker/distribution/blob/master/docs/spec/api.md#listing-image-tags
return $resource('/v2/:repoUser/:repoName/tags/list', {}, {
// Response example:
// {"name":"kkleine/docker-registry-frontend","tags":["v2", "v1-deprecated"]}
'query': {
method:'GET',
isArray: true,
transformResponse: function(data/*, headers*/){
var res = [];
var resp = angular.fromJson(data);
for (var idx in resp.tags){
res.push({
name: resp.tags[idx],
imageId: 'ImageIDOf'+resp.tags[idx],
selected: false
});
}
return res;
},
},
'delete': {
url: '/v1/repositories/:repoUser/:repoName/tags/:tagName',
method: 'DELETE',
},
'exists': {
url: '/v1/repositories/:repoUser/:repoName/tags/:tagName',
method: 'GET',
transformResponse: function(data/*, headers*/){
// data will be the image ID if successful or an error object.
data = angular.isString(angular.fromJson(data));
return data;
},
},
// Usage: Tag.save({repoUser:'someuser', repoName: 'someRepo', tagName: 'someTagName'}, imageId);
'save': {
method:'PUT',
url: '/v1/repositories/:repoUser/:repoName/tags/:tagName',
},
});
}])
.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;
},
}
});
}])
// 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;
}
}
});
}]) */ ;