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>