mirror of https://github.com/portainer/portainer
				
				
				
			feat(images): display unused images tags (#1009)
							parent
							
								
									b23943e30b
								
							
						
					
					
						commit
						bc4b0a0b35
					
				| 
						 | 
					@ -70,6 +70,17 @@
 | 
				
			||||||
        <div class="pull-right">
 | 
					        <div class="pull-right">
 | 
				
			||||||
          <input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
 | 
					          <input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					        <span class="btn-group btn-group-sm pull-right" style="margin-right: 20px;">
 | 
				
			||||||
 | 
					          <label class="btn btn-primary" ng-model="state.containersCountFilter" uib-btn-radio="undefined">
 | 
				
			||||||
 | 
					            All
 | 
				
			||||||
 | 
					          </label>
 | 
				
			||||||
 | 
					          <label class="btn btn-primary" ng-model="state.containersCountFilter" uib-btn-radio="'!' + 0">
 | 
				
			||||||
 | 
					            Used
 | 
				
			||||||
 | 
					          </label>
 | 
				
			||||||
 | 
					          <label class="btn btn-primary" ng-model="state.containersCountFilter" uib-btn-radio="0">
 | 
				
			||||||
 | 
					            Unused
 | 
				
			||||||
 | 
					          </label>
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
      </rd-widget-taskbar>
 | 
					      </rd-widget-taskbar>
 | 
				
			||||||
      <rd-widget-body classes="no-padding">
 | 
					      <rd-widget-body classes="no-padding">
 | 
				
			||||||
        <div class="table-responsive">
 | 
					        <div class="table-responsive">
 | 
				
			||||||
| 
						 | 
					@ -110,9 +121,11 @@
 | 
				
			||||||
              </tr>
 | 
					              </tr>
 | 
				
			||||||
            </thead>
 | 
					            </thead>
 | 
				
			||||||
            <tbody>
 | 
					            <tbody>
 | 
				
			||||||
              <tr dir-paginate="image in (state.filteredImages = (images | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
 | 
					              <tr dir-paginate="image in (state.filteredImages = (images | filter:{ Containers: state.containersCountFilter } | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
 | 
				
			||||||
                <td><input type="checkbox" ng-model="image.Checked" ng-change="selectItem(image)" /></td>
 | 
					                <td><input type="checkbox" ng-model="image.Checked" ng-change="selectItem(image)" /></td>
 | 
				
			||||||
                <td><a ui-sref="image({id: image.Id})">{{ image.Id|truncate:20}}</a></td>
 | 
					                <td>
 | 
				
			||||||
 | 
					                  <a class="monospaced" ui-sref="image({id: image.Id})">{{ image.Id|truncate:20}}</a>
 | 
				
			||||||
 | 
					                  <span style="margin-left: 10px;" class="label label-warning image-tag" ng-if="::image.Containers === 0">Unused</span></td>
 | 
				
			||||||
                <td>
 | 
					                <td>
 | 
				
			||||||
                  <span class="label label-primary image-tag" ng-repeat="tag in (image|repotags)">{{ tag }}</span>
 | 
					                  <span class="label label-primary image-tag" ng-repeat="tag in (image|repotags)">{{ tag }}</span>
 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -134,7 +134,7 @@
 | 
				
			||||||
              <tr dir-paginate="network in ( state.filteredNetworks = (networks | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
 | 
					              <tr dir-paginate="network in ( state.filteredNetworks = (networks | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
 | 
				
			||||||
                <td><input type="checkbox" ng-model="network.Checked" ng-change="selectItem(network)"/></td>
 | 
					                <td><input type="checkbox" ng-model="network.Checked" ng-change="selectItem(network)"/></td>
 | 
				
			||||||
                <td><a ui-sref="network({id: network.Id})">{{ network.Name|truncate:40}}</a></td>
 | 
					                <td><a ui-sref="network({id: network.Id})">{{ network.Name|truncate:40}}</a></td>
 | 
				
			||||||
                <td>{{ network.Id }}</td>
 | 
					                <td class="monospaced">{{ network.Id|truncate:20 }}</td>
 | 
				
			||||||
                <td>{{ network.Scope }}</td>
 | 
					                <td>{{ network.Scope }}</td>
 | 
				
			||||||
                <td>{{ network.Driver }}</td>
 | 
					                <td>{{ network.Driver }}</td>
 | 
				
			||||||
                <td>{{ network.IPAM.Driver }}</td>
 | 
					                <td>{{ network.IPAM.Driver }}</td>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,7 +49,7 @@
 | 
				
			||||||
      </thead>
 | 
					      </thead>
 | 
				
			||||||
      <tbody>
 | 
					      <tbody>
 | 
				
			||||||
        <tr dir-paginate="task in (filteredTasks = ( tasks | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
 | 
					        <tr dir-paginate="task in (filteredTasks = ( tasks | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
 | 
				
			||||||
        <td><a ui-sref="task({ id: task.Id })">{{ task.Id }}</a></td>
 | 
					        <td><a ui-sref="task({ id: task.Id })" class="monospaced">{{ task.Id }}</a></td>
 | 
				
			||||||
        <td><span class="label label-{{ task.Status.State|taskstatusbadge }}">{{ task.Status.State }}</span></td>
 | 
					        <td><span class="label label-{{ task.Status.State|taskstatusbadge }}">{{ task.Status.State }}</span></td>
 | 
				
			||||||
        <td ng-if="service.Mode !== 'global'">{{ task.Slot }}</td>
 | 
					        <td ng-if="service.Mode !== 'global'">{{ task.Slot }}</td>
 | 
				
			||||||
        <td>{{ task.NodeId | tasknodename: nodes }}</td>
 | 
					        <td>{{ task.NodeId | tasknodename: nodes }}</td>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -83,7 +83,7 @@
 | 
				
			||||||
          <tbody>
 | 
					          <tbody>
 | 
				
			||||||
            <tr dir-paginate="volume in (state.filteredVolumes = (volumes | filter:{dangling: state.danglingVolumesOnly} | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
 | 
					            <tr dir-paginate="volume in (state.filteredVolumes = (volumes | filter:{dangling: state.danglingVolumesOnly} | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
 | 
				
			||||||
              <td><input type="checkbox" ng-model="volume.Checked" ng-change="selectItem(volume)"/></td>
 | 
					              <td><input type="checkbox" ng-model="volume.Checked" ng-change="selectItem(volume)"/></td>
 | 
				
			||||||
              <td><a ui-sref="volume({id: volume.Id})">{{ volume.Id|truncate:25 }}</a></td>
 | 
					              <td><a ui-sref="volume({id: volume.Id})" class="monospaced">{{ volume.Id|truncate:25 }}</a></td>
 | 
				
			||||||
              <td>{{ volume.Driver }}</td>
 | 
					              <td>{{ volume.Driver }}</td>
 | 
				
			||||||
              <td>{{ volume.Mountpoint | truncate:52 }}</td>
 | 
					              <td>{{ volume.Mountpoint | truncate:52 }}</td>
 | 
				
			||||||
              <td ng-if="applicationState.application.authentication">
 | 
					              <td ng-if="applicationState.application.authentication">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ function ImageViewModel(data) {
 | 
				
			||||||
  this.Tag = data.Tag;
 | 
					  this.Tag = data.Tag;
 | 
				
			||||||
  this.Repository = data.Repository;
 | 
					  this.Repository = data.Repository;
 | 
				
			||||||
  this.Created = data.Created;
 | 
					  this.Created = data.Created;
 | 
				
			||||||
 | 
					  this.Containers = data.dataUsage.Containers;
 | 
				
			||||||
  this.Checked = false;
 | 
					  this.Checked = false;
 | 
				
			||||||
  this.RepoTags = data.RepoTags;
 | 
					  this.RepoTags = data.RepoTags;
 | 
				
			||||||
  this.VirtualSize = data.VirtualSize;
 | 
					  this.VirtualSize = data.VirtualSize;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ angular.module('portainer.rest')
 | 
				
			||||||
      method: 'GET', params: { action: 'events', since: '@since', until: '@until' },
 | 
					      method: 'GET', params: { action: 'events', since: '@since', until: '@until' },
 | 
				
			||||||
      isArray: true, transformResponse: jsonObjectsToArrayHandler
 | 
					      isArray: true, transformResponse: jsonObjectsToArrayHandler
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    auth: { method: 'POST', params: { action: 'auth' } }
 | 
					    auth: { method: 'POST', params: { action: 'auth' } },
 | 
				
			||||||
 | 
					    dataUsage: { method: 'GET', params: { action: 'system/df' } }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}]);
 | 
					}]);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
angular.module('portainer.services')
 | 
					angular.module('portainer.services')
 | 
				
			||||||
.factory('ImageService', ['$q', 'Image', 'ImageHelper', 'RegistryService', 'HttpRequestHelper', function ImageServiceFactory($q, Image, ImageHelper, RegistryService, HttpRequestHelper) {
 | 
					.factory('ImageService', ['$q', 'Image', 'ImageHelper', 'RegistryService', 'HttpRequestHelper', 'SystemService', function ImageServiceFactory($q, Image, ImageHelper, RegistryService, HttpRequestHelper, SystemService) {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
  var service = {};
 | 
					  var service = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,9 +22,17 @@ angular.module('portainer.services')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  service.images = function() {
 | 
					  service.images = function() {
 | 
				
			||||||
    var deferred = $q.defer();
 | 
					    var deferred = $q.defer();
 | 
				
			||||||
    Image.query({}).$promise
 | 
					    
 | 
				
			||||||
 | 
					    $q.all({
 | 
				
			||||||
 | 
					      dataUsage: SystemService.dataUsage(),
 | 
				
			||||||
 | 
					      images: Image.query({}).$promise
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
    .then(function success(data) {
 | 
					    .then(function success(data) {
 | 
				
			||||||
      var images = data.map(function (item) {
 | 
					      var images = data.images.map(function(item) {
 | 
				
			||||||
 | 
					        item.dataUsage = data.dataUsage.Images.find(function(usage) {
 | 
				
			||||||
 | 
					           return item.Id === usage.Id;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        return new ImageViewModel(item);
 | 
					        return new ImageViewModel(item);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      deferred.resolve(images);
 | 
					      deferred.resolve(images);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,6 +40,10 @@ angular.module('portainer.services')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return deferred.promise;
 | 
					    return deferred.promise;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  service.dataUsage = function () {
 | 
				
			||||||
 | 
					    return System.dataUsage().$promise;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return service;
 | 
					  return service;
 | 
				
			||||||
}]);
 | 
					}]);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -513,3 +513,8 @@ ul.sidebar .sidebar-list .sidebar-sublist a.active {
 | 
				
			||||||
  opacity: 0.9;
 | 
					  opacity: 0.9;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
/*!toaster override*/
 | 
					/*!toaster override*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.monospaced {
 | 
				
			||||||
 | 
					  font-family: monospace;
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue