Never load the full repo image list
Always make smaller queries per tag to ensure we scale better Fixes #754
This commit is contained in:
parent
43720b27e7
commit
4f41f79fa8
15 changed files with 268 additions and 254 deletions
|
@ -2,13 +2,15 @@
|
|||
* An element which displays the changes visualization panel for a repository view.
|
||||
*/
|
||||
angular.module('quay').directive('repoPanelChanges', function () {
|
||||
var RepositoryImageTracker = function(repository, images) {
|
||||
var RepositoryImageTracker = function(repository, imageLoader) {
|
||||
this.repository = repository;
|
||||
this.images = images;
|
||||
this.imageLoader = imageLoader;
|
||||
|
||||
// Build a map of image ID -> image.
|
||||
var images = imageLoader.images;
|
||||
var imageIDMap = {};
|
||||
this.images.map(function(image) {
|
||||
|
||||
images.forEach(function(image) {
|
||||
imageIDMap[image.id] = image;
|
||||
});
|
||||
|
||||
|
@ -91,12 +93,13 @@ angular.module('quay').directive('repoPanelChanges', function () {
|
|||
'selectedTags': '=selectedTags',
|
||||
|
||||
'imagesResource': '=imagesResource',
|
||||
'images': '=images',
|
||||
'imageLoader': '=imageLoader',
|
||||
|
||||
'isEnabled': '=isEnabled'
|
||||
},
|
||||
controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) {
|
||||
$scope.tagNames = [];
|
||||
$scope.loading = true;
|
||||
|
||||
$scope.$watch('selectedTags', function(selectedTags) {
|
||||
if (!selectedTags) { return; }
|
||||
|
@ -110,18 +113,17 @@ angular.module('quay').directive('repoPanelChanges', function () {
|
|||
$scope.currentImage = null;
|
||||
$scope.currentTag = null;
|
||||
|
||||
if ($scope.tracker) {
|
||||
refreshTree();
|
||||
} else {
|
||||
$scope.loading = true;
|
||||
$scope.imageLoader.loadImages($scope.selectedTagsSlice, function() {
|
||||
$scope.loading = false;
|
||||
updateImages();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var updateImages = function() {
|
||||
if (!$scope.repository || !$scope.images || !$scope.isEnabled) { return; }
|
||||
|
||||
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
|
||||
if (!$scope.repository || !$scope.imageLoader || !$scope.isEnabled) { return; }
|
||||
|
||||
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.imageLoader);
|
||||
if ($scope.selectedTagsSlice && $scope.selectedTagsSlice.length) {
|
||||
refreshTree();
|
||||
}
|
||||
|
@ -131,22 +133,25 @@ angular.module('quay').directive('repoPanelChanges', function () {
|
|||
$scope.$watch('repository', update);
|
||||
$scope.$watch('isEnabled', update);
|
||||
|
||||
$scope.$watch('images', updateImages);
|
||||
|
||||
$scope.updateState = function() {
|
||||
update();
|
||||
};
|
||||
|
||||
var refreshTree = function() {
|
||||
if (!$scope.repository || !$scope.images || !$scope.isEnabled) { return; }
|
||||
if (!$scope.repository || !$scope.imageLoader || !$scope.isEnabled) { return; }
|
||||
if ($scope.selectedTagsSlice.length < 1) { return; }
|
||||
|
||||
$('#image-history-container').empty();
|
||||
|
||||
var getTagsForImage = function(image) {
|
||||
return $scope.imageLoader.getTagsForImage(image);
|
||||
};
|
||||
|
||||
var tree = new ImageHistoryTree(
|
||||
$scope.repository.namespace,
|
||||
$scope.repository.name,
|
||||
$scope.images,
|
||||
$scope.imageLoader.images,
|
||||
getTagsForImage,
|
||||
UtilService.getFirstMarkdownLineAsText,
|
||||
$scope.getTimeSince,
|
||||
ImageMetadataService.getEscapedFormattedCommand,
|
||||
|
@ -194,8 +199,6 @@ angular.module('quay').directive('repoPanelChanges', function () {
|
|||
};
|
||||
|
||||
$scope.handleTagChanged = function(data) {
|
||||
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
|
||||
|
||||
data.removed.map(function(tag) {
|
||||
$scope.currentImage = null;
|
||||
$scope.currentTag = null;
|
||||
|
@ -206,7 +209,7 @@ angular.module('quay').directive('repoPanelChanges', function () {
|
|||
$scope.currentTag = tag;
|
||||
});
|
||||
|
||||
refreshTree();
|
||||
update();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ angular.module('quay').directive('repoPanelTags', function () {
|
|||
'repository': '=repository',
|
||||
'selectedTags': '=selectedTags',
|
||||
'imagesResource': '=imagesResource',
|
||||
'images': '=images',
|
||||
'imageLoader': '=imageLoader',
|
||||
|
||||
'isEnabled': '=isEnabled',
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ angular.module('quay').directive('imageInfoSidebar', function () {
|
|||
scope: {
|
||||
'tracker': '=tracker',
|
||||
'image': '=image',
|
||||
'imageLoader': '=imageLoader',
|
||||
|
||||
'tagSelected': '&tagSelected',
|
||||
'addTagRequested': '&addTagRequested'
|
||||
|
@ -25,6 +26,10 @@ angular.module('quay').directive('imageInfoSidebar', function () {
|
|||
return Date.parse(dateString);
|
||||
};
|
||||
|
||||
$scope.getTags = function(imageData) {
|
||||
return $scope.imageLoader.getTagsForImage(imageData);
|
||||
};
|
||||
|
||||
$scope.getFormattedCommand = ImageMetadataService.getFormattedCommand;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -11,46 +11,31 @@ angular.module('quay').directive('tagOperationsDialog', function () {
|
|||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'images': '=images',
|
||||
'actionHandler': '=actionHandler',
|
||||
|
||||
'getImages': '&getImages',
|
||||
'imageLoader': '=imageLoader',
|
||||
'tagChanged': '&tagChanged'
|
||||
},
|
||||
controller: function($scope, $element, $timeout, ApiService) {
|
||||
$scope.addingTag = false;
|
||||
$scope.imagesInternal = [];
|
||||
|
||||
$scope.$watch('images', function(images) {
|
||||
if (!images) { return; }
|
||||
$scope.imagesInternal = images;
|
||||
});
|
||||
|
||||
var markChanged = function(added, removed) {
|
||||
// Reload the repository and the images.
|
||||
// Reload the repository.
|
||||
$scope.repository.get().then(function(resp) {
|
||||
$scope.repository = resp;
|
||||
$scope.imageLoader.reset()
|
||||
|
||||
var params = {
|
||||
'repository': resp.namespace + '/' + resp.name
|
||||
};
|
||||
|
||||
ApiService.listRepositoryImages(null, params).then(function(resp) {
|
||||
$scope.images = resp.images;
|
||||
|
||||
// Note: We need the timeout here so that Angular can $digest the images change
|
||||
// on the parent scope before the tagChanged callback occurs.
|
||||
$timeout(function() {
|
||||
$scope.tagChanged({
|
||||
'data': { 'added': added, 'removed': removed }
|
||||
});
|
||||
}, 1);
|
||||
})
|
||||
// Note: We need the timeout here so that Angular can $digest the images change
|
||||
// on the parent scope before the tagChanged callback occurs.
|
||||
$timeout(function() {
|
||||
$scope.tagChanged({
|
||||
'data': { 'added': added, 'removed': removed }
|
||||
});
|
||||
}, 1);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isAnotherImageTag = function(image, tag) {
|
||||
if (!$scope.repository || !$scope.imagesInternal) { return; }
|
||||
if (!$scope.repository) { return; }
|
||||
|
||||
var found = $scope.repository.tags[tag];
|
||||
if (found == null) { return false; }
|
||||
|
@ -58,7 +43,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
|
|||
};
|
||||
|
||||
$scope.isOwnedTag = function(image, tag) {
|
||||
if (!$scope.repository || !$scope.imagesInternal) { return; }
|
||||
if (!$scope.repository) { return; }
|
||||
|
||||
var found = $scope.repository.tags[tag];
|
||||
if (found == null) { return false; }
|
||||
|
@ -149,70 +134,39 @@ angular.module('quay').directive('tagOperationsDialog', function () {
|
|||
}, errorHandler);
|
||||
};
|
||||
|
||||
var lazyLoadImages = function(callback) {
|
||||
if ($scope.imagesInternal.length) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
var isLoading = true;
|
||||
$timeout(function() {
|
||||
if (isLoading) {
|
||||
$('#loadingImagesModal').modal({});
|
||||
}
|
||||
}, 500);
|
||||
|
||||
var cb = function(images) {
|
||||
isLoading = false;
|
||||
$('#loadingImagesModal').modal('hide');
|
||||
$scope.imagesInternal = images;
|
||||
callback();
|
||||
};
|
||||
|
||||
$scope.getImages({'callback': cb});
|
||||
};
|
||||
|
||||
$scope.actionHandler = {
|
||||
'askDeleteTag': function(tag) {
|
||||
lazyLoadImages(function() {
|
||||
$scope.deleteTagInfo = {
|
||||
'tag': tag
|
||||
};
|
||||
});
|
||||
$scope.deleteTagInfo = {
|
||||
'tag': tag
|
||||
};
|
||||
},
|
||||
|
||||
'askDeleteMultipleTags': function(tags) {
|
||||
lazyLoadImages(function() {
|
||||
$scope.deleteMultipleTagsInfo = {
|
||||
'tags': tags
|
||||
};
|
||||
});
|
||||
$scope.deleteMultipleTagsInfo = {
|
||||
'tags': tags
|
||||
};
|
||||
},
|
||||
|
||||
'askAddTag': function(image) {
|
||||
lazyLoadImages(function() {
|
||||
$scope.tagToCreate = '';
|
||||
$scope.toTagImage = image;
|
||||
$scope.addingTag = false;
|
||||
$scope.addTagForm.$setPristine();
|
||||
$element.find('#createOrMoveTagModal').modal('show');
|
||||
});
|
||||
$scope.tagToCreate = '';
|
||||
$scope.toTagImage = image;
|
||||
$scope.addingTag = false;
|
||||
$scope.addTagForm.$setPristine();
|
||||
$element.find('#createOrMoveTagModal').modal('show');
|
||||
},
|
||||
|
||||
'askRevertTag': function(tag, image_id) {
|
||||
lazyLoadImages(function() {
|
||||
if (tag.image_id == image_id) {
|
||||
bootbox.alert('This is the current image for the tag');
|
||||
return;
|
||||
}
|
||||
if (tag.image_id == image_id) {
|
||||
bootbox.alert('This is the current image for the tag');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.revertTagInfo = {
|
||||
'tag': tag,
|
||||
'image_id': image_id
|
||||
};
|
||||
$scope.revertTagInfo = {
|
||||
'tag': tag,
|
||||
'image_id': image_id
|
||||
};
|
||||
|
||||
$element.find('#revertTagModal').modal('show');
|
||||
});
|
||||
$element.find('#revertTagModal').modal('show');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,13 +12,12 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
|
|||
scope: {
|
||||
'repository': '=repository',
|
||||
'tag': '=tag',
|
||||
'images': '=images',
|
||||
'imageLoader': '=imageLoader',
|
||||
'imageCutoff': '=imageCutoff'
|
||||
},
|
||||
controller: function($scope, $element, UtilService) {
|
||||
$scope.getFirstTextLine = UtilService.getFirstMarkdownLineAsText;
|
||||
|
||||
$scope.hasImages = false;
|
||||
$scope.loading = false;
|
||||
$scope.tagSpecificImages = [];
|
||||
|
||||
$scope.getImageListingClasses = function(image) {
|
||||
|
@ -35,39 +34,8 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
|
|||
return classes;
|
||||
};
|
||||
|
||||
var forAllTagImages = function(tag, callback, opt_cutoff) {
|
||||
if (!tag) { return; }
|
||||
|
||||
if (!$scope.imageByDockerId) {
|
||||
$scope.imageByDockerId = [];
|
||||
for (var i = 0; i < $scope.images.length; ++i) {
|
||||
var currentImage = $scope.images[i];
|
||||
$scope.imageByDockerId[currentImage.id] = currentImage;
|
||||
}
|
||||
}
|
||||
|
||||
var tag_image = $scope.imageByDockerId[tag.image_id];
|
||||
if (!tag_image) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(tag_image);
|
||||
|
||||
var ancestors = tag_image.ancestors.split('/').reverse();
|
||||
for (var i = 0; i < ancestors.length; ++i) {
|
||||
var image = $scope.imageByDockerId[ancestors[i]];
|
||||
if (image) {
|
||||
if (image == opt_cutoff) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(image);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var refresh = function() {
|
||||
if (!$scope.repository || !$scope.tag || !$scope.images) {
|
||||
if (!$scope.repository || !$scope.tag || !$scope.imageLoader) {
|
||||
$scope.tagSpecificImages = [];
|
||||
return;
|
||||
}
|
||||
|
@ -78,49 +46,15 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
|
|||
return;
|
||||
}
|
||||
|
||||
var getIdsForTag = function(currentTag) {
|
||||
var ids = {};
|
||||
forAllTagImages(currentTag, function(image) {
|
||||
ids[image.id] = true;
|
||||
}, $scope.imageCutoff);
|
||||
return ids;
|
||||
};
|
||||
|
||||
// Remove any IDs that match other tags.
|
||||
var toDelete = getIdsForTag(tag);
|
||||
for (var currentTagName in $scope.repository.tags) {
|
||||
var currentTag = $scope.repository.tags[currentTagName];
|
||||
if (currentTag != tag) {
|
||||
for (var id in getIdsForTag(currentTag)) {
|
||||
delete toDelete[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the matching list of images.
|
||||
var images = [];
|
||||
for (var i = 0; i < $scope.images.length; ++i) {
|
||||
var image = $scope.images[i];
|
||||
if (toDelete[image.id]) {
|
||||
images.push(image);
|
||||
}
|
||||
}
|
||||
|
||||
images.sort(function(a, b) {
|
||||
var result = new Date(b.created) - new Date(a.created);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return b.sort_index - a.sort_index;
|
||||
$scope.loading = true;
|
||||
$scope.imageLoader.getTagSpecificImages($scope.tag, function(images) {
|
||||
$scope.loading = false;
|
||||
$scope.tagSpecificImages = images;
|
||||
});
|
||||
|
||||
$scope.tagSpecificImages = images;
|
||||
};
|
||||
|
||||
$scope.$watch('repository', refresh);
|
||||
$scope.$watch('tag', refresh);
|
||||
$scope.$watch('images', refresh);
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
|
|
Reference in a new issue