1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-19 21:39:40 +02:00

refactor(image): refactor the code used in image and image details controller (#705)

This commit is contained in:
Anthony Lapenna 2017-03-20 12:01:35 +01:00 committed by GitHub
parent c2e63070e6
commit 24b51a7e87
8 changed files with 245 additions and 167 deletions

View file

@ -7,7 +7,7 @@
</rd-header-content> </rd-header-content>
</rd-header> </rd-header>
<div class="row" ng-if="RepoTags.length > 0"> <div class="row" ng-if="image.RepoTags.length > 0">
<div class="col-lg-12 col-md-12 col-xs-12"> <div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa fa-tags" title="Image tags"></rd-widget-header> <rd-widget-header icon="fa fa-tags" title="Image tags"></rd-widget-header>
@ -15,7 +15,7 @@
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
<div class="pull-left" ng-repeat="tag in RepoTags" style="display:table"> <div class="pull-left" ng-repeat="tag in image.RepoTags" style="display:table">
<div class="input-group col-md-1" style="padding:0 15px"> <div class="input-group col-md-1" style="padding:0 15px">
<span class="input-group-addon">{{ tag }}</span> <span class="input-group-addon">{{ tag }}</span>
<span class="input-group-btn"> <span class="input-group-btn">
@ -25,7 +25,7 @@
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Pull from registry" ng-click="pullImage(tag)"> <a data-toggle="tooltip" class="btn btn-primary interactive" title="Pull from registry" ng-click="pullImage(tag)">
<span class="fa fa-download white-icon" aria-hidden="true"></span> <span class="fa fa-download white-icon" aria-hidden="true"></span>
</a> </a>
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Remove tag" ng-click="removeImage(tag)"> <a data-toggle="tooltip" class="btn btn-primary interactive" title="Remove tag" ng-click="removeTag(tag)">
<span class="fa fa-trash-o white-icon" aria-hidden="true"></span> <span class="fa fa-trash-o white-icon" aria-hidden="true"></span>
</a> </a>
</span> </span>
@ -36,7 +36,9 @@
<div class="form-group"> <div class="form-group">
<div class="col-sm-12"> <div class="col-sm-12">
<span class="small text-muted"> <span class="small text-muted">
Note: you can click on the upload icon to push or on the download icon to pull an image and on the trash icon to delete a tag Note: you can click on the upload icon <span class="fa fa-upload" aria-hidden="true"></span> to push an image
or on the download icon <span class="fa fa-download" aria-hidden="true"></span> to pull an image
or on the trash icon <span class="fa fa-trash-o" aria-hidden="true"></span> to delete a tag.
</span> </span>
</div> </div>
</div> </div>
@ -133,33 +135,33 @@
<tbody> <tbody>
<tr> <tr>
<td>CMD</td> <td>CMD</td>
<td><code>{{ image.ContainerConfig.Cmd|command }}</code></td> <td><code>{{ image.Command|command }}</code></td>
</tr> </tr>
<tr ng-if="image.ContainerConfig.Entrypoint"> <tr ng-if="image.Entrypoint">
<td>ENTRYPOINT</td> <td>ENTRYPOINT</td>
<td><code>{{ image.ContainerConfig.Entrypoint|command }}</code></td> <td><code>{{ image.Entrypoint|command }}</code></td>
</tr> </tr>
<tr ng-if="image.ContainerConfig.ExposedPorts"> <tr ng-if="image.ExposedPorts.length > 0">
<td>EXPOSE</td> <td>EXPOSE</td>
<td> <td>
<span class="label label-default space-right" ng-repeat="port in exposedPorts"> <span class="label label-default space-right" ng-repeat="port in image.ExposedPorts">
{{ port }} {{ port }}
</span> </span>
</td> </td>
</tr> </tr>
<tr ng-if="image.ContainerConfig.Volumes"> <tr ng-if="image.Volumes.length > 0">
<td>VOLUME</td> <td>VOLUME</td>
<td> <td>
<span class="label label-default space-right" ng-repeat="volume in volumes"> <span class="label label-default space-right" ng-repeat="volume in image.Volumes">
{{ volume }} {{ volume }}
</span> </span>
</td> </td>
</tr> </tr>
<tr> <tr ng-if="image.Env.length > 0">
<td>ENV</td> <td>ENV</td>
<td> <td>
<table class="table table-bordered table-condensed"> <table class="table table-bordered table-condensed">
<tr ng-repeat="var in image.ContainerConfig.Env"> <tr ng-repeat="var in image.Env">
<td>{{ var|key: '=' }}</td> <td>{{ var|key: '=' }}</td>
<td>{{ var|value: '=' }}</td> <td>{{ var|value: '=' }}</td>
</tr> </tr>

View file

@ -1,111 +1,109 @@
angular.module('image', []) angular.module('image', [])
.filter('onlylabel', function(){ .controller('ImageController', ['$scope', '$stateParams', '$state', 'ImageService', 'Messages',
return function(tag){ function ($scope, $stateParams, $state, ImageService, Messages) {
return tag.substr(tag.indexOf(":")+1); $scope.config = {
Image: '',
Registry: ''
}; };
})
.controller('ImageController', ['$scope', '$stateParams', '$state', 'Image', 'ImageService', 'ImageHelper', 'Messages',
function ($scope, $stateParams, $state, Image, ImageService, ImageHelper, Messages) {
$scope.RepoTags = [];
$scope.config = {
Image: '',
Registry: ''
};
// Get RepoTags from the /images/query endpoint instead of /image/json, $scope.tagImage = function() {
// for backwards compatibility with Docker API versions older than 1.21 $('#loadingViewSpinner').show();
function getRepoTags(imageId) { var image = $scope.config.Image;
Image.query({}, function (d) { var registry = $scope.config.Registry;
d.forEach(function(image) {
if (image.Id === imageId && image.RepoTags[0] !== '<none>:<none>') {
$scope.RepoTags = image.RepoTags;
}
});
});
}
$scope.tagImage = function() { ImageService.tagImage($stateParams.id, image, registry)
$('#loadingViewSpinner').show(); .then(function success(data) {
var image = $scope.config.Image; Messages.send('Image successfully tagged');
var registry = $scope.config.Registry; $state.go('image', {id: $stateParams.id}, {reload: true});
var imageConfig = ImageHelper.createImageConfigForCommit(image, registry); })
Image.tag({id: $stateParams.id, tag: imageConfig.tag, repo: imageConfig.repo}, function (d) { .catch(function error(err) {
Messages.send('Image successfully tagged'); Messages.error("Failure", err, "Unable to tag image");
$('#loadingViewSpinner').hide(); })
$state.go('image', {id: $stateParams.id}, {reload: true}); .finally(function final() {
}, function(e) { $('#loadingViewSpinner').hide();
$('#loadingViewSpinner').hide(); });
Messages.error("Failure", e, "Unable to tag image"); };
});
};
$scope.pushImage = function(tag) { $scope.pushImage = function(tag) {
$('#loadingViewSpinner').show(); $('#loadingViewSpinner').show();
Image.push({tag: tag}, function (d) { ImageService.pushImage(tag)
if (d[d.length-1].error) { .then(function success() {
Messages.error("Unable to push image", {}, d[d.length-1].error); Messages.send('Image successfully pushed');
} else { })
Messages.send('Image successfully pushed'); .catch(function error(err) {
} Messages.error("Failure", err, "Unable to push image tag");
$('#loadingViewSpinner').hide(); })
}, function (e) { .finally(function final() {
$('#loadingViewSpinner').hide(); $('#loadingViewSpinner').hide();
Messages.error("Failure", e, "Unable to push image"); });
}); };
};
$scope.pullImage = function(tag) { $scope.pullImage = function(tag) {
var items = tag.split(":"); $('#loadingViewSpinner').show();
var image = items[0]; var image = $scope.config.Image;
tag = items[1]; var registry = $scope.config.Registry;
$('#loadingViewSpinner').show();
ImageService.pullImage({fromImage: image, tag: tag})
.then(function success(data) {
Messages.send('Image successfully pulled');
})
.catch(function error(error){
Messages.error("Failure", error, "Unable to pull image");
})
.finally(function final() {
$('#loadingViewSpinner').hide();
});
};
$scope.removeImage = function (id) { ImageService.pullImage(image, registry)
$('#loadingViewSpinner').show(); .then(function success(data) {
Image.remove({id: id}, function (d) { Messages.send('Image successfully pulled', image);
if (d[0].message) { })
$('#loadingViewSpinner').hide(); .catch(function error(err){
Messages.error("Unable to remove image", {}, d[0].message); Messages.error("Failure", err, "Unable to pull image");
} else { })
// If last message key is 'Deleted' or if it's 'Untagged' and there is only one tag associated to the image .finally(function final() {
// then assume the image is gone and send to images page $('#loadingViewSpinner').hide();
if (d[d.length-1].Deleted || (d[d.length-1].Untagged && $scope.RepoTags.length === 1)) { });
Messages.send('Image successfully deleted'); };
$state.go('images', {}, {reload: true});
} else {
Messages.send('Tag successfully deleted');
$state.go('image', {id: $stateParams.id}, {reload: true});
}
}
}, function (e) {
$('#loadingViewSpinner').hide();
Messages.error("Failure", e, 'Unable to remove image');
});
};
$('#loadingViewSpinner').show(); $scope.removeTag = function(id) {
Image.get({id: $stateParams.id}, function (d) { $('#loadingViewSpinner').show();
$scope.image = d; ImageService.deleteImage(id, false)
if (d.RepoTags) { .then(function success() {
$scope.RepoTags = d.RepoTags; if ($scope.image.RepoTags.length === 1) {
} else { Messages.send('Image successfully deleted', id);
getRepoTags(d.Id); $state.go('images', {}, {reload: true});
} } else {
$('#loadingViewSpinner').hide(); Messages.send('Tag successfully deleted', id);
$scope.exposedPorts = d.ContainerConfig.ExposedPorts ? Object.keys(d.ContainerConfig.ExposedPorts) : []; $state.go('image', {id: $stateParams.id}, {reload: true});
$scope.volumes = d.ContainerConfig.Volumes ? Object.keys(d.ContainerConfig.Volumes) : []; }
}, function (e) { })
Messages.error("Failure", e, "Unable to retrieve image info"); .catch(function error(err) {
}); Messages.error("Failure", err, 'Unable to remove image');
})
.finally(function final() {
$('#loadingViewSpinner').hide();
});
};
$scope.removeImage = function (id) {
$('#loadingViewSpinner').show();
ImageService.deleteImage(id, false)
.then(function success() {
Messages.send('Image successfully deleted', id);
$state.go('images', {}, {reload: true});
})
.catch(function error(err) {
Messages.error("Failure", err, 'Unable to remove image');
})
.finally(function final() {
$('#loadingViewSpinner').hide();
});
};
function retrieveImageDetails() {
$('#loadingViewSpinner').show();
ImageService.image($stateParams.id)
.then(function success(data) {
$scope.image = data;
})
.catch(function error(err) {
Messages.error("Failure", err, "Unable to retrieve image details");
$state.go('images');
})
.finally(function final() {
$('#loadingViewSpinner').hide();
});
}
retrieveImageDetails();
}]); }]);

View file

@ -1,6 +1,6 @@
angular.module('images', []) angular.module('images', [])
.controller('ImagesController', ['$scope', '$state', 'Config', 'Image', 'ImageHelper', 'Messages', 'Pagination', 'ModalService', .controller('ImagesController', ['$scope', '$state', 'Config', 'ImageService', 'Messages', 'Pagination', 'ModalService',
function ($scope, $state, Config, Image, ImageHelper, Messages, Pagination, ModalService) { function ($scope, $state, Config, ImageService, Messages, Pagination, ModalService) {
$scope.state = {}; $scope.state = {};
$scope.state.pagination_count = Pagination.getPaginationCount('images'); $scope.state.pagination_count = Pagination.getPaginationCount('images');
$scope.sortType = 'RepoTags'; $scope.sortType = 'RepoTags';
@ -42,20 +42,15 @@ function ($scope, $state, Config, Image, ImageHelper, Messages, Pagination, Moda
$('#pullImageSpinner').show(); $('#pullImageSpinner').show();
var image = $scope.config.Image; var image = $scope.config.Image;
var registry = $scope.config.Registry; var registry = $scope.config.Registry;
var imageConfig = ImageHelper.createImageConfigForContainer(image, registry); ImageService.pullImage(image, registry)
Image.create(imageConfig, function (data) { .then(function success(data) {
var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error'); $state.reload();
if (err) { })
var detail = data[data.length - 1]; .catch(function error(err) {
$('#pullImageSpinner').hide(); Messages.error("Failure", err, "Unable to pull image");
Messages.error('Error', {}, detail.error); })
} else { .finally(function final() {
$('#pullImageSpinner').hide();
$state.reload();
}
}, function (e) {
$('#pullImageSpinner').hide(); $('#pullImageSpinner').hide();
Messages.error("Failure", e, "Unable to pull image");
}); });
}; };
@ -79,18 +74,16 @@ function ($scope, $state, Config, Image, ImageHelper, Messages, Pagination, Moda
angular.forEach($scope.images, function (i) { angular.forEach($scope.images, function (i) {
if (i.Checked) { if (i.Checked) {
counter = counter + 1; counter = counter + 1;
Image.remove({id: i.Id, force: force}, function (d) { ImageService.deleteImage(i.Id, force)
if (d[0].message) { .then(function success(data) {
$('#loadImagesSpinner').hide(); Messages.send("Image deleted", i.Id);
Messages.error("Unable to remove image", {}, d[0].message); var index = $scope.images.indexOf(i);
} else { $scope.images.splice(index, 1);
Messages.send("Image deleted", i.Id); })
var index = $scope.images.indexOf(i); .catch(function error(err) {
$scope.images.splice(index, 1); Messages.error("Failure", err, 'Unable to remove image');
} })
complete(); .finally(function final() {
}, function (e) {
Messages.error("Failure", e, 'Unable to remove image');
complete(); complete();
}); });
} }
@ -98,19 +91,19 @@ function ($scope, $state, Config, Image, ImageHelper, Messages, Pagination, Moda
}; };
function fetchImages() { function fetchImages() {
Image.query({}, function (d) { $('#loadImagesSpinner').show();
$scope.images = d.map(function (item) { ImageService.images()
return new ImageViewModel(item); .then(function success(data) {
}); $scope.images = data;
$('#loadImagesSpinner').hide(); })
}, function (e) { .catch(function error(err) {
$('#loadImagesSpinner').hide(); Messages.error("Failure", err, "Unable to retrieve images");
Messages.error("Failure", e, "Unable to retrieve images");
$scope.images = []; $scope.images = [];
})
.finally(function final() {
$('#loadImagesSpinner').hide();
}); });
} }
Config.$promise.then(function (c) { fetchImages();
fetchImages();
});
}]); }]);

View file

@ -45,14 +45,14 @@ function ($scope, $q, $state, $anchorScroll, Config, ContainerService, Container
volumeResourceControlQueries.push(ResourceControlService.setVolumeResourceControl(Authentication.getUserDetails().ID, volume.Name)); volumeResourceControlQueries.push(ResourceControlService.setVolumeResourceControl(Authentication.getUserDetails().ID, volume.Name));
}); });
} }
TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration.container, template, data); TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration, template, data);
return $q.all(volumeResourceControlQueries) return $q.all(volumeResourceControlQueries)
.then(function success() { .then(function success() {
return ImageService.pullImage(templateConfiguration.image); return ImageService.pullImage(template.Image, template.Registry);
}); });
}) })
.then(function success(data) { .then(function success(data) {
return ContainerService.createAndStartContainer(templateConfiguration.container); return ContainerService.createAndStartContainer(templateConfiguration);
}) })
.then(function success(data) { .then(function success(data) {
Messages.send('Container Started', data.Id); Messages.send('Container Started', data.Id);

View file

@ -0,0 +1,19 @@
function ImageDetailsViewModel(data) {
this.Id = data.Id;
this.Tag = data.Tag;
this.Parent = data.Parent;
this.Repository = data.Repository;
this.Created = data.Created;
this.Checked = false;
this.RepoTags = data.RepoTags;
this.VirtualSize = data.VirtualSize;
this.DockerVersion = data.DockerVersion;
this.Os = data.Os;
this.Architecture = data.Architecture;
this.Author = data.Author;
this.Command = data.ContainerConfig.Cmd;
this.Entrypoint = data.ContainerConfig.Entrypoint ? data.ContainerConfig.Entrypoint : '';
this.ExposedPorts = data.ContainerConfig.ExposedPorts ? Object.keys(data.ContainerConfig.ExposedPorts) : [];
this.Volumes = data.ContainerConfig.Volumes ? Object.keys(data.ContainerConfig.Volumes) : [];
this.Env = data.ContainerConfig.Env ? data.ContainerConfig.Env : [];
}

View file

@ -1,10 +1,43 @@
angular.module('portainer.services') angular.module('portainer.services')
.factory('ImageService', ['$q', 'Image', function ImageServiceFactory($q, Image) { .factory('ImageService', ['$q', 'Image', 'ImageHelper', function ImageServiceFactory($q, Image, ImageHelper) {
'use strict'; 'use strict';
var service = {}; var service = {};
service.pullImage = function(imageConfiguration) { service.image = function(imageId) {
var deferred = $q.defer(); var deferred = $q.defer();
Image.get({id: imageId}).$promise
.then(function success(data) {
if (data.message) {
deferred.reject({ msg: data.message });
} else {
var image = new ImageDetailsViewModel(data);
deferred.resolve(image);
}
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to retrieve image details', err: err });
});
return deferred.promise;
};
service.images = function() {
var deferred = $q.defer();
Image.query({}).$promise
.then(function success(data) {
var images = data.map(function (item) {
return new ImageViewModel(item);
});
deferred.resolve(images);
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to retrieve images', err: err });
});
return deferred.promise;
};
service.pullImage = function(image, registry) {
var deferred = $q.defer();
var imageConfiguration = ImageHelper.createImageConfigForContainer(image, registry);
Image.create(imageConfiguration).$promise Image.create(imageConfiguration).$promise
.then(function success(data) { .then(function success(data) {
var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error'); var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error');
@ -20,5 +53,43 @@ angular.module('portainer.services')
}); });
return deferred.promise; return deferred.promise;
}; };
service.tagImage = function(id, image, registry) {
var imageConfig = ImageHelper.createImageConfigForCommit(image, registry);
return Image.tag({id: id, tag: imageConfig.tag, repo: imageConfig.repo}).$promise;
};
service.deleteImage = function(id, forceRemoval) {
var deferred = $q.defer();
Image.remove({id: id, force: forceRemoval}).$promise
.then(function success(data) {
if (data[0].message) {
deferred.reject({ msg: data[0].message });
} else {
deferred.resolve();
}
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to remove image', err: err });
});
return deferred.promise;
};
service.pushImage = function(tag) {
var deferred = $q.defer();
Image.push({tag: tag}).$promise
.then(function success(data) {
if (data[data.length - 1].error) {
deferred.reject({ msg: data[data.length - 1].error });
} else {
deferred.resolve();
}
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to push image tag', err: err });
});
return deferred.promise;
};
return service; return service;
}]); }]);

View file

@ -22,6 +22,8 @@ angular.module('portainer.services')
msg = e.message; msg = e.message;
} else if (e.data && e.data.length > 0 && e.data[0].message) { } else if (e.data && e.data.length > 0 && e.data[0].message) {
msg = e.data[0].message; msg = e.data[0].message;
} else if (e.msg) {
msg = e.msg;
} }
$.gritter.add({ $.gritter.add({
title: $sanitize(title), title: $sanitize(title),

View file

@ -21,17 +21,10 @@ angular.module('portainer.services')
}; };
service.createTemplateConfiguration = function(template, containerName, network, containerMapping) { service.createTemplateConfiguration = function(template, containerName, network, containerMapping) {
var imageConfiguration = service.createImageConfiguration(template); var imageConfiguration = ImageHelper.createImageConfigForContainer(template.Image, template.Registry);
var containerConfiguration = service.createContainerConfiguration(template, containerName, network, containerMapping); var containerConfiguration = service.createContainerConfiguration(template, containerName, network, containerMapping);
containerConfiguration.Image = imageConfiguration.fromImage + ':' + imageConfiguration.tag; containerConfiguration.Image = imageConfiguration.fromImage + ':' + imageConfiguration.tag;
return { return containerConfiguration;
container: containerConfiguration,
image: imageConfiguration
};
};
service.createImageConfiguration = function(template) {
return ImageHelper.createImageConfigForContainer(template.Image, template.Registry);
}; };
service.createContainerConfiguration = function(template, containerName, network, containerMapping) { service.createContainerConfiguration = function(template, containerName, network, containerMapping) {