From d2b9e0d65a02d69212faa3abe567b400bdfb02f7 Mon Sep 17 00:00:00 2001 From: Joseph Schorr <jschorr@gmail.com> Date: Thu, 9 Jan 2014 15:15:06 -0500 Subject: [PATCH] Shows the images that will be deleted when removing a tag --- static/css/quay.css | 55 +++++++++++++++++++++++++ static/js/controllers.js | 73 ++++++++++++++++++++++++++++++++++ static/partials/view-repo.html | 19 ++++++++- 3 files changed, 145 insertions(+), 2 deletions(-) diff --git a/static/css/quay.css b/static/css/quay.css index 47cb4b372..ff86a9c68 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -1210,6 +1210,61 @@ p.editable:hover i { border: 0px; } +#confirmdeleteTagModal .image-listings { + margin: 10px; +} + +#confirmdeleteTagModal .image-listings .image-listing { + margin: 4px; + padding: 2px; + position: relative; +} + +#confirmdeleteTagModal .image-listings .image-listing .image-listing-id { + display: inline-block; + margin-left: 20px; +} + +#confirmdeleteTagModal .image-listings .image-listing .image-listing-line { + border-left: 2px solid steelblue; + display: inline-block; + position: absolute; + top: -2px; + bottom: 8px; + left: 6px; + width: 1px; + z-index: 1; +} + +#confirmdeleteTagModal .image-listings .image-listing.tag-image .image-listing-line { + top: 8px; +} + +#confirmdeleteTagModal .image-listings .image-listing.child .image-listing-line { + bottom: -2px; +} + +#confirmdeleteTagModal .image-listings .image-listing .image-listing-circle { + position: absolute; + top: 8px; + + border-radius: 50%; + border: 2px solid steelblue; + width: 10px; + height: 10px; + display: inline-block; + background: white; + z-index: 2; +} + +#confirmdeleteTagModal .image-listings .image-listing.tag-image .image-listing-circle { + background: steelblue; +} + +#confirmdeleteTagModal .more-changes { + margin-left: 16px; +} + .repo .header { margin-bottom: 10px; position: relative; diff --git a/static/js/controllers.js b/static/js/controllers.js index 800d25232..b4433590f 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -194,6 +194,60 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo } }; + $scope.tagSpecificImages = function(tagName) { + if (!tagName) { return []; } + + var tag = $scope.repo.tags[tagName]; + if (!tag) { return []; } + + if ($scope.specificImages && $scope.specificImages[tagName]) { + return $scope.specificImages[tagName]; + } + + var getIdsForTag = function(currentTag) { + var ancestors = currentTag.image.ancestors.split('/'); + var dbid = currentTag.image.dbid; + var ids = {}; + + ids[dbid] = true; + for (var i = 0; i < ancestors.length; ++i) { + if (ancestors[i]) { + ids[ancestors[i]] = true; + } + } + return ids; + }; + + + // Remove any IDs that match other tags. + var toDelete = getIdsForTag(tag); + for (var currentTagName in $scope.repo.tags) { + var currentTag = $scope.repo.tags[currentTagName]; + if (currentTag != tag) { + for (var dbid in getIdsForTag(currentTag)) { + delete toDelete[dbid]; + } + } + } + + // 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.dbid]) { + images.push(image); + } + } + + images.sort(function(a, b) { + return b.dbid - a.dbid; + }); + + $scope.specificImages[tagName] = images; + return images; + }; + + $scope.askDeleteTag = function(tagName) { if (!$scope.repo.can_admin) { return; } @@ -257,6 +311,22 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo } }; + $scope.getFirstTextLine = getFirstTextLine; + + $scope.getImageListingClasses = function(image, tagName) { + var classes = ''; + if (image.ancestors.length > 1) { + classes += 'child '; + } + + var currentTag = $scope.repo.tags[tagName]; + if (image.dbid == currentTag.image.dbid) { + classes += 'tag-image '; + } + + return classes; + }; + $scope.getTagCount = function(repo) { if (!repo) { return 0; } var count = 0; @@ -389,6 +459,9 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo var listImages = function() { var params = {'repository': namespace + '/' + name}; $scope.imageHistory = ApiService.listRepositoryImagesAsResource(params).get(function(resp) { + $scope.images = resp.images; + $scope.specificImages = []; + // Dispose of any existing tree. if ($scope.tree) { $scope.tree.dispose(); diff --git a/static/partials/view-repo.html b/static/partials/view-repo.html index 7166cdefc..b28e9e266 100644 --- a/static/partials/view-repo.html +++ b/static/partials/view-repo.html @@ -213,8 +213,23 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre> {{ tagToDelete }} </span>? - <br><br> - Doing so will delete any images not attached to another tag. + <div ng-show="tagSpecificImages(tagToDelete).length" style="margin-top: 20px"> + The following images will also be deleted: + <div class="image-listings"> + <div class="image-listing" ng-repeat="image in tagSpecificImages(tagToDelete) | limitTo:5" + ng-class="getImageListingClasses(image, tagToDelete)"> + <!--<i class="fa fa-archive"></i>--> + <span class="image-listing-circle"></span> + <span class="image-listing-line"></span> + <span class="context-tooltip image-listing-id" bs-tooltip="getFirstTextLine(image.comment)"> + {{ image.id.substr(0, 12) }} + </span> + </div> + </div> + <div class="more-changes" ng-show="tagSpecificImages(tagToDelete).length > 5"> + And {{ tagSpecificImages(tagToDelete).length - 5 }} more... + </div> + </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" ng-click="deleteTag(tagToDelete)">Delete Tag</button>