Fix UI rendering issue when creating/deleting tags from the UI (#3269)
### Description of Changes Tag operations in UI would not be rendered properly when using the paginated tags endpoint. When a user would create/delete a tag from the repo-panel-tags, `digest` would be called. This caused the `$scope.repository.tags` to be removed. To fix this: * Bind the tags directly to the scope instead of the repository * Change references to scope.repository.tags to use scope.repositoryTags ---
This commit is contained in:
parent
bb01e08d44
commit
8b25d5b77b
7 changed files with 69 additions and 37 deletions
|
@ -347,7 +347,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tag-operations-dialog" repository="repository"
|
<div class="tag-operations-dialog" repository="repository" repository-tags="repositoryTags"
|
||||||
image-loader="imageLoader"
|
image-loader="imageLoader"
|
||||||
action-handler="tagActionHandler"
|
action-handler="tagActionHandler"
|
||||||
labels-changed="handleLabelsChanged(manifest_digest)"></div>
|
labels-changed="handleLabelsChanged(manifest_digest)"></div>
|
||||||
|
|
|
@ -71,6 +71,7 @@
|
||||||
<div class="tag-specific-images-view"
|
<div class="tag-specific-images-view"
|
||||||
tag="tagToCreate"
|
tag="tagToCreate"
|
||||||
repository="repository"
|
repository="repository"
|
||||||
|
repository-tags="repositoryTags"
|
||||||
image-cutoff="toTagImage"
|
image-cutoff="toTagImage"
|
||||||
style="margin: 10px; margin-top: 20px; margin-bottom: -10px;"
|
style="margin: 10px; margin-top: 20px; margin-bottom: -10px;"
|
||||||
ng-show="isAnotherImageTag(toTagImage, tagToCreate)"
|
ng-show="isAnotherImageTag(toTagImage, tagToCreate)"
|
||||||
|
@ -143,7 +144,7 @@
|
||||||
Are you sure you want to delete tag
|
Are you sure you want to delete tag
|
||||||
<span class="label label-default tag">{{ deleteTagInfo.tag }}</span>?
|
<span class="label label-default tag">{{ deleteTagInfo.tag }}</span>?
|
||||||
|
|
||||||
<div class="tag-specific-images-view" tag="deleteTagInfo.tag" repository="repository"
|
<div class="tag-specific-images-view" tag="deleteTagInfo.tag" repository="repository" repository-tags="repositoryTags"
|
||||||
image-loader="imageLoader" style="margin-top: 20px">
|
image-loader="imageLoader" style="margin-top: 20px">
|
||||||
The following images and any other images not referenced by a tag will be <span ng-if="repository.tag_expiration_s == 0">deleted</span><span ng-if="repository.tag_expiration_s != 0">unavailable and deleted in {{ getFormattedTimespan(repository.tag_expiration_s) }}</span>:
|
The following images and any other images not referenced by a tag will be <span ng-if="repository.tag_expiration_s == 0">deleted</span><span ng-if="repository.tag_expiration_s != 0">unavailable and deleted in {{ getFormattedTimespan(repository.tag_expiration_s) }}</span>:
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,6 +10,7 @@ angular.module('quay').directive('repoPanelTags', function () {
|
||||||
restrict: 'C',
|
restrict: 'C',
|
||||||
scope: {
|
scope: {
|
||||||
'repository': '=repository',
|
'repository': '=repository',
|
||||||
|
'repositoryTags': '=repositoryTags',
|
||||||
'selectedTags': '=selectedTags',
|
'selectedTags': '=selectedTags',
|
||||||
'historyFilter': '=historyFilter',
|
'historyFilter': '=historyFilter',
|
||||||
'imagesResource': '=imagesResource',
|
'imagesResource': '=imagesResource',
|
||||||
|
@ -64,14 +65,14 @@ angular.module('quay').directive('repoPanelTags', function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
var setTagState = function() {
|
var setTagState = function() {
|
||||||
if (!$scope.repository || !$scope.selectedTags) { return; }
|
if (!$scope.repositoryTags || !$scope.selectedTags) { return; }
|
||||||
|
|
||||||
// Build a list of all the tags, with extending information.
|
// Build a list of all the tags, with extending information.
|
||||||
var allTags = [];
|
var allTags = [];
|
||||||
for (var tag in $scope.repository.tags) {
|
for (var tag in $scope.repositoryTags) {
|
||||||
if (!$scope.repository.tags.hasOwnProperty(tag)) { continue; }
|
if (!$scope.repositoryTags.hasOwnProperty(tag)) { continue; }
|
||||||
|
|
||||||
var tagData = $scope.repository.tags[tag];
|
var tagData = $scope.repositoryTags[tag];
|
||||||
var tagInfo = $.extend(tagData, {
|
var tagInfo = $.extend(tagData, {
|
||||||
'name': tag,
|
'name': tag,
|
||||||
'last_modified_datetime': TableService.getReversedTimestamp(tagData.last_modified),
|
'last_modified_datetime': TableService.getReversedTimestamp(tagData.last_modified),
|
||||||
|
@ -233,17 +234,21 @@ angular.module('quay').directive('repoPanelTags', function () {
|
||||||
if (!selectedTags || !$scope.repository || !$scope.imageMap) { return; }
|
if (!selectedTags || !$scope.repository || !$scope.imageMap) { return; }
|
||||||
|
|
||||||
$scope.checkedTags.setChecked(selectedTags.map(function(tag) {
|
$scope.checkedTags.setChecked(selectedTags.map(function(tag) {
|
||||||
return $scope.repository.tags[tag];
|
return $scope.repositoryTags[tag];
|
||||||
}));
|
}));
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
$scope.$watch('repository', function(updatedRepoObject, previousRepoObject) {
|
$scope.$watch('repository', function(updatedRepoObject, previousRepoObject) {
|
||||||
if (updatedRepoObject.tags === previousRepoObject.tags) { return; }
|
|
||||||
|
|
||||||
// Process each of the tags.
|
// Process each of the tags.
|
||||||
setTagState();
|
setTagState();
|
||||||
loadRepoSignatures();
|
loadRepoSignatures();
|
||||||
}, true);
|
});
|
||||||
|
|
||||||
|
$scope.$watch('repositoryTags', function(tags) {
|
||||||
|
// Process each of the tags.
|
||||||
|
setTagState();
|
||||||
|
loadRepoSignatures();
|
||||||
|
});
|
||||||
|
|
||||||
$scope.loadImageVulnerabilities = function(image_id, imageData) {
|
$scope.loadImageVulnerabilities = function(image_id, imageData) {
|
||||||
VulnerabilityService.loadImageVulnerabilities($scope.repository, image_id, function(resp) {
|
VulnerabilityService.loadImageVulnerabilities($scope.repository, image_id, function(resp) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
|
||||||
restrict: 'C',
|
restrict: 'C',
|
||||||
scope: {
|
scope: {
|
||||||
'repository': '=repository',
|
'repository': '=repository',
|
||||||
|
'repositoryTags': '=repositoryTags',
|
||||||
'actionHandler': '=actionHandler',
|
'actionHandler': '=actionHandler',
|
||||||
'imageLoader': '=imageLoader',
|
'imageLoader': '=imageLoader',
|
||||||
'tagChanged': '&tagChanged',
|
'tagChanged': '&tagChanged',
|
||||||
|
@ -20,22 +21,44 @@ angular.module('quay').directive('tagOperationsDialog', function () {
|
||||||
$scope.addingTag = false;
|
$scope.addingTag = false;
|
||||||
$scope.changeTagsExpirationInfo = null;
|
$scope.changeTagsExpirationInfo = null;
|
||||||
|
|
||||||
var markChanged = function(added, removed) {
|
var reloadTags = function(page, tags, added, removed) {
|
||||||
// Reload the repository.
|
var params = {
|
||||||
$scope.repository.get().then(function(resp) {
|
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||||
$scope.repository = resp;
|
'limit': 100,
|
||||||
$scope.imageLoader.reset()
|
'page': page,
|
||||||
|
'onlyActiveTags': true
|
||||||
|
};
|
||||||
|
|
||||||
|
ApiService.listRepoTags(null, params).then(function(resp) {
|
||||||
|
var newTags = resp.tags.reduce(function(result, item, index, array) {
|
||||||
|
var tag_name = item['name'];
|
||||||
|
result[tag_name] = item;
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
$.extend(tags, newTags);
|
||||||
|
|
||||||
|
if (resp.has_additional) {
|
||||||
|
loadPaginatedRepositoryTags(page + 1, tags, added, removed);
|
||||||
|
} else {
|
||||||
|
$scope.repositoryTags = tags;
|
||||||
|
$scope.imageLoader.reset();
|
||||||
|
|
||||||
// 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() {
|
$timeout(function() {
|
||||||
$scope.tagChanged({
|
$scope.tagChanged({
|
||||||
'data': { 'added': added, 'removed': removed }
|
'data': { 'added': added, 'removed': removed }
|
||||||
});
|
});
|
||||||
}, 1);
|
}, 1);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var markChanged = function(added, removed) {
|
||||||
|
// Reload the tags
|
||||||
|
tags = {};
|
||||||
|
reloadTags(1, tags, added, removed);
|
||||||
|
};
|
||||||
|
|
||||||
$scope.alertOnTagOpsDisabled = function() {
|
$scope.alertOnTagOpsDisabled = function() {
|
||||||
if ($scope.repository.tag_operations_disabled) {
|
if ($scope.repository.tag_operations_disabled) {
|
||||||
$('#tagOperationsDisabledModal').modal('show');
|
$('#tagOperationsDisabledModal').modal('show');
|
||||||
|
@ -46,17 +69,17 @@ angular.module('quay').directive('tagOperationsDialog', function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.isAnotherImageTag = function(image, tag) {
|
$scope.isAnotherImageTag = function(image, tag) {
|
||||||
if (!$scope.repository.tags) { return; }
|
if (!$scope.repositoryTags) { return; }
|
||||||
|
|
||||||
var found = $scope.repository.tags[tag];
|
var found = $scope.repositoryTags[tag];
|
||||||
if (found == null) { return false; }
|
if (found == null) { return false; }
|
||||||
return found.image_id != image;
|
return found.image_id != image;
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.isOwnedTag = function(image, tag) {
|
$scope.isOwnedTag = function(image, tag) {
|
||||||
if (!$scope.repository.tags) { return; }
|
if (!$scope.repositoryTags) { return; }
|
||||||
|
|
||||||
var found = $scope.repository.tags[tag];
|
var found = $scope.repositoryTags[tag];
|
||||||
if (found == null) { return false; }
|
if (found == null) { return false; }
|
||||||
return found.image_id == image;
|
return found.image_id == image;
|
||||||
};
|
};
|
||||||
|
@ -227,7 +250,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
|
||||||
actions.push({
|
actions.push({
|
||||||
'action': 'delete',
|
'action': 'delete',
|
||||||
'label': label
|
'label': label
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
|
||||||
restrict: 'C',
|
restrict: 'C',
|
||||||
scope: {
|
scope: {
|
||||||
'repository': '=repository',
|
'repository': '=repository',
|
||||||
|
'repositoryTags': '=repositoryTags',
|
||||||
'tag': '=tag',
|
'tag': '=tag',
|
||||||
'imageLoader': '=imageLoader',
|
'imageLoader': '=imageLoader',
|
||||||
'imageCutoff': '=imageCutoff'
|
'imageCutoff': '=imageCutoff'
|
||||||
|
@ -22,7 +23,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
|
||||||
|
|
||||||
$scope.getImageListingClasses = function(image) {
|
$scope.getImageListingClasses = function(image) {
|
||||||
var classes = '';
|
var classes = '';
|
||||||
if (!$scope.repository) {
|
if (!$scope.repositoryTags) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
|
||||||
classes += 'child ';
|
classes += 'child ';
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentTag = $scope.repository.tags[$scope.tag];
|
var currentTag = $scope.repositoryTags[$scope.tag];
|
||||||
if (currentTag && image.id == currentTag.image_id) {
|
if (currentTag && image.id == currentTag.image_id) {
|
||||||
classes += 'tag-image ';
|
classes += 'tag-image ';
|
||||||
}
|
}
|
||||||
|
@ -39,12 +40,12 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
var refresh = function() {
|
var refresh = function() {
|
||||||
if (!$scope.repository || !$scope.tag || !$scope.imageLoader) {
|
if (!$scope.repositoryTags || !$scope.tag || !$scope.imageLoader) {
|
||||||
$scope.tagSpecificImages = [];
|
$scope.tagSpecificImages = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tag = $scope.repository.tags[$scope.tag];
|
var tag = $scope.repositoryTags[$scope.tag];
|
||||||
if (!tag) {
|
if (!tag) {
|
||||||
$scope.tagSpecificImages = [];
|
$scope.tagSpecificImages = [];
|
||||||
return;
|
return;
|
||||||
|
@ -58,6 +59,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.$watch('repository', refresh);
|
$scope.$watch('repository', refresh);
|
||||||
|
$scope.$watch('repositoryTags', refresh);
|
||||||
$scope.$watch('tag', refresh);
|
$scope.$watch('tag', refresh);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,7 +30,8 @@
|
||||||
'repository': null,
|
'repository': null,
|
||||||
'imageLoader': imageLoader,
|
'imageLoader': imageLoader,
|
||||||
'builds': null,
|
'builds': null,
|
||||||
'historyFilter': ''
|
'historyFilter': '',
|
||||||
|
'repositoryTags': null
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.repositoryTags = {};
|
$scope.repositoryTags = {};
|
||||||
|
@ -41,19 +42,20 @@
|
||||||
UserService.updateUserIn($scope);
|
UserService.updateUserIn($scope);
|
||||||
|
|
||||||
// Watch the repository to filter any tags removed.
|
// Watch the repository to filter any tags removed.
|
||||||
$scope.$watch('viewScope.repository', function(repository) {
|
$scope.$watch('viewScope.repositoryTags', function(repository) {
|
||||||
if (!repository) { return; }
|
if (!repository) { return; }
|
||||||
$scope.viewScope.selectedTags = filterTags($scope.viewScope.selectedTags);
|
$scope.viewScope.selectedTags = filterTags($scope.viewScope.selectedTags);
|
||||||
});
|
});
|
||||||
|
|
||||||
var filterTags = function(tags) {
|
var filterTags = function(tags) {
|
||||||
return (tags || []).filter(function(tag) {
|
return (tags || []).filter(function(tag) {
|
||||||
return !!$scope.viewScope.repository.tags[tag];
|
return !!$scope.viewScope.repositoryTags[tag];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var loadRepositoryTags = function() {
|
var loadRepositoryTags = function() {
|
||||||
loadPaginatedRepositoryTags(1);
|
loadPaginatedRepositoryTags(1);
|
||||||
|
$scope.viewScope.repositoryTags = $scope.repositoryTags;
|
||||||
};
|
};
|
||||||
|
|
||||||
var loadPaginatedRepositoryTags = function(page) {
|
var loadPaginatedRepositoryTags = function(page) {
|
||||||
|
@ -82,6 +84,7 @@
|
||||||
var loadRepository = function() {
|
var loadRepository = function() {
|
||||||
// Mark the images to be reloaded.
|
// Mark the images to be reloaded.
|
||||||
$scope.viewScope.images = null;
|
$scope.viewScope.images = null;
|
||||||
|
loadRepositoryTags();
|
||||||
|
|
||||||
var params = {
|
var params = {
|
||||||
'repository': $scope.namespace + '/' + $scope.name,
|
'repository': $scope.namespace + '/' + $scope.name,
|
||||||
|
@ -91,9 +94,6 @@
|
||||||
|
|
||||||
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
||||||
if (repo != undefined) {
|
if (repo != undefined) {
|
||||||
loadRepositoryTags();
|
|
||||||
repo.tags = $scope.repositoryTags;
|
|
||||||
|
|
||||||
$scope.repository = repo;
|
$scope.repository = repo;
|
||||||
$scope.viewScope.repository = repo;
|
$scope.viewScope.repository = repo;
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,7 @@
|
||||||
<cor-tab-pane id="tags">
|
<cor-tab-pane id="tags">
|
||||||
<div class="repo-panel-tags"
|
<div class="repo-panel-tags"
|
||||||
repository="viewScope.repository"
|
repository="viewScope.repository"
|
||||||
|
repository-tags="viewScope.repositoryTags"
|
||||||
image-loader="viewScope.imageLoader"
|
image-loader="viewScope.imageLoader"
|
||||||
selected-tags="viewScope.selectedTags"
|
selected-tags="viewScope.selectedTags"
|
||||||
history-filter="viewScope.historyFilter"
|
history-filter="viewScope.historyFilter"
|
||||||
|
|
Reference in a new issue