diff --git a/app/components/image/image.html b/app/components/image/image.html index 5cf83602a..8dbc35b7a 100644 --- a/app/components/image/image.html +++ b/app/components/image/image.html @@ -7,7 +7,7 @@ -
+
@@ -15,7 +15,7 @@
-
+
{{ tag }} @@ -25,7 +25,7 @@ - + @@ -36,7 +36,9 @@
- 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 to push an image + or on the download icon to pull an image + or on the trash icon to delete a tag.
@@ -133,33 +135,33 @@ CMD - {{ image.ContainerConfig.Cmd|command }} + {{ image.Command|command }} - + ENTRYPOINT - {{ image.ContainerConfig.Entrypoint|command }} + {{ image.Entrypoint|command }} - + EXPOSE - + {{ port }} - + VOLUME - + {{ volume }} - + ENV - + diff --git a/app/components/image/imageController.js b/app/components/image/imageController.js index fc663e23e..8514ff1bb 100644 --- a/app/components/image/imageController.js +++ b/app/components/image/imageController.js @@ -1,111 +1,109 @@ angular.module('image', []) -.filter('onlylabel', function(){ - return function(tag){ - return tag.substr(tag.indexOf(":")+1); +.controller('ImageController', ['$scope', '$stateParams', '$state', 'ImageService', 'Messages', +function ($scope, $stateParams, $state, ImageService, Messages) { + $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, - // for backwards compatibility with Docker API versions older than 1.21 - function getRepoTags(imageId) { - Image.query({}, function (d) { - d.forEach(function(image) { - if (image.Id === imageId && image.RepoTags[0] !== ':') { - $scope.RepoTags = image.RepoTags; - } - }); - }); - } + $scope.tagImage = function() { + $('#loadingViewSpinner').show(); + var image = $scope.config.Image; + var registry = $scope.config.Registry; - $scope.tagImage = function() { - $('#loadingViewSpinner').show(); - var image = $scope.config.Image; - var registry = $scope.config.Registry; - var imageConfig = ImageHelper.createImageConfigForCommit(image, registry); - Image.tag({id: $stateParams.id, tag: imageConfig.tag, repo: imageConfig.repo}, function (d) { - Messages.send('Image successfully tagged'); - $('#loadingViewSpinner').hide(); - $state.go('image', {id: $stateParams.id}, {reload: true}); - }, function(e) { - $('#loadingViewSpinner').hide(); - Messages.error("Failure", e, "Unable to tag image"); - }); - }; + ImageService.tagImage($stateParams.id, image, registry) + .then(function success(data) { + Messages.send('Image successfully tagged'); + $state.go('image', {id: $stateParams.id}, {reload: true}); + }) + .catch(function error(err) { + Messages.error("Failure", err, "Unable to tag image"); + }) + .finally(function final() { + $('#loadingViewSpinner').hide(); + }); + }; - $scope.pushImage = function(tag) { - $('#loadingViewSpinner').show(); - Image.push({tag: tag}, function (d) { - if (d[d.length-1].error) { - Messages.error("Unable to push image", {}, d[d.length-1].error); - } else { - Messages.send('Image successfully pushed'); - } - $('#loadingViewSpinner').hide(); - }, function (e) { - $('#loadingViewSpinner').hide(); - Messages.error("Failure", e, "Unable to push image"); - }); - }; + $scope.pushImage = function(tag) { + $('#loadingViewSpinner').show(); + ImageService.pushImage(tag) + .then(function success() { + Messages.send('Image successfully pushed'); + }) + .catch(function error(err) { + Messages.error("Failure", err, "Unable to push image tag"); + }) + .finally(function final() { + $('#loadingViewSpinner').hide(); + }); + }; - $scope.pullImage = function(tag) { - var items = tag.split(":"); - var image = items[0]; - tag = items[1]; - $('#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.pullImage = function(tag) { + $('#loadingViewSpinner').show(); + var image = $scope.config.Image; + var registry = $scope.config.Registry; - $scope.removeImage = function (id) { - $('#loadingViewSpinner').show(); - Image.remove({id: id}, function (d) { - if (d[0].message) { - $('#loadingViewSpinner').hide(); - Messages.error("Unable to remove image", {}, d[0].message); - } else { - // If last message key is 'Deleted' or if it's 'Untagged' and there is only one tag associated to the image - // then assume the image is gone and send to images page - 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'); - }); - }; + ImageService.pullImage(image, registry) + .then(function success(data) { + Messages.send('Image successfully pulled', image); + }) + .catch(function error(err){ + Messages.error("Failure", err, "Unable to pull image"); + }) + .finally(function final() { + $('#loadingViewSpinner').hide(); + }); + }; - $('#loadingViewSpinner').show(); - Image.get({id: $stateParams.id}, function (d) { - $scope.image = d; - if (d.RepoTags) { - $scope.RepoTags = d.RepoTags; - } else { - getRepoTags(d.Id); - } - $('#loadingViewSpinner').hide(); - $scope.exposedPorts = d.ContainerConfig.ExposedPorts ? Object.keys(d.ContainerConfig.ExposedPorts) : []; - $scope.volumes = d.ContainerConfig.Volumes ? Object.keys(d.ContainerConfig.Volumes) : []; - }, function (e) { - Messages.error("Failure", e, "Unable to retrieve image info"); - }); + $scope.removeTag = function(id) { + $('#loadingViewSpinner').show(); + ImageService.deleteImage(id, false) + .then(function success() { + if ($scope.image.RepoTags.length === 1) { + Messages.send('Image successfully deleted', id); + $state.go('images', {}, {reload: true}); + } else { + Messages.send('Tag successfully deleted', id); + $state.go('image', {id: $stateParams.id}, {reload: true}); + } + }) + .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(); }]); diff --git a/app/components/images/imagesController.js b/app/components/images/imagesController.js index 44752330e..350b77c2a 100644 --- a/app/components/images/imagesController.js +++ b/app/components/images/imagesController.js @@ -1,6 +1,6 @@ angular.module('images', []) -.controller('ImagesController', ['$scope', '$state', 'Config', 'Image', 'ImageHelper', 'Messages', 'Pagination', 'ModalService', -function ($scope, $state, Config, Image, ImageHelper, Messages, Pagination, ModalService) { +.controller('ImagesController', ['$scope', '$state', 'Config', 'ImageService', 'Messages', 'Pagination', 'ModalService', +function ($scope, $state, Config, ImageService, Messages, Pagination, ModalService) { $scope.state = {}; $scope.state.pagination_count = Pagination.getPaginationCount('images'); $scope.sortType = 'RepoTags'; @@ -42,20 +42,15 @@ function ($scope, $state, Config, Image, ImageHelper, Messages, Pagination, Moda $('#pullImageSpinner').show(); var image = $scope.config.Image; var registry = $scope.config.Registry; - var imageConfig = ImageHelper.createImageConfigForContainer(image, registry); - Image.create(imageConfig, function (data) { - var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error'); - if (err) { - var detail = data[data.length - 1]; - $('#pullImageSpinner').hide(); - Messages.error('Error', {}, detail.error); - } else { - $('#pullImageSpinner').hide(); - $state.reload(); - } - }, function (e) { + ImageService.pullImage(image, registry) + .then(function success(data) { + $state.reload(); + }) + .catch(function error(err) { + Messages.error("Failure", err, "Unable to pull image"); + }) + .finally(function final() { $('#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) { if (i.Checked) { counter = counter + 1; - Image.remove({id: i.Id, force: force}, function (d) { - if (d[0].message) { - $('#loadImagesSpinner').hide(); - Messages.error("Unable to remove image", {}, d[0].message); - } else { - Messages.send("Image deleted", i.Id); - var index = $scope.images.indexOf(i); - $scope.images.splice(index, 1); - } - complete(); - }, function (e) { - Messages.error("Failure", e, 'Unable to remove image'); + ImageService.deleteImage(i.Id, force) + .then(function success(data) { + Messages.send("Image deleted", i.Id); + var index = $scope.images.indexOf(i); + $scope.images.splice(index, 1); + }) + .catch(function error(err) { + Messages.error("Failure", err, 'Unable to remove image'); + }) + .finally(function final() { complete(); }); } @@ -98,19 +91,19 @@ function ($scope, $state, Config, Image, ImageHelper, Messages, Pagination, Moda }; function fetchImages() { - Image.query({}, function (d) { - $scope.images = d.map(function (item) { - return new ImageViewModel(item); - }); - $('#loadImagesSpinner').hide(); - }, function (e) { - $('#loadImagesSpinner').hide(); - Messages.error("Failure", e, "Unable to retrieve images"); + $('#loadImagesSpinner').show(); + ImageService.images() + .then(function success(data) { + $scope.images = data; + }) + .catch(function error(err) { + Messages.error("Failure", err, "Unable to retrieve images"); $scope.images = []; + }) + .finally(function final() { + $('#loadImagesSpinner').hide(); }); } - Config.$promise.then(function (c) { - fetchImages(); - }); + fetchImages(); }]); diff --git a/app/components/templates/templatesController.js b/app/components/templates/templatesController.js index 8d501d30c..1db8927af 100644 --- a/app/components/templates/templatesController.js +++ b/app/components/templates/templatesController.js @@ -45,14 +45,14 @@ function ($scope, $q, $state, $anchorScroll, Config, ContainerService, Container volumeResourceControlQueries.push(ResourceControlService.setVolumeResourceControl(Authentication.getUserDetails().ID, volume.Name)); }); } - TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration.container, template, data); + TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration, template, data); return $q.all(volumeResourceControlQueries) .then(function success() { - return ImageService.pullImage(templateConfiguration.image); + return ImageService.pullImage(template.Image, template.Registry); }); }) .then(function success(data) { - return ContainerService.createAndStartContainer(templateConfiguration.container); + return ContainerService.createAndStartContainer(templateConfiguration); }) .then(function success(data) { Messages.send('Container Started', data.Id); diff --git a/app/models/imageDetails.js b/app/models/imageDetails.js new file mode 100644 index 000000000..df7da6183 --- /dev/null +++ b/app/models/imageDetails.js @@ -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 : []; +} diff --git a/app/services/imageService.js b/app/services/imageService.js index bba649998..a8a29cac5 100644 --- a/app/services/imageService.js +++ b/app/services/imageService.js @@ -1,10 +1,43 @@ angular.module('portainer.services') -.factory('ImageService', ['$q', 'Image', function ImageServiceFactory($q, Image) { +.factory('ImageService', ['$q', 'Image', 'ImageHelper', function ImageServiceFactory($q, Image, ImageHelper) { 'use strict'; var service = {}; - service.pullImage = function(imageConfiguration) { + service.image = function(imageId) { 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 .then(function success(data) { var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error'); @@ -20,5 +53,43 @@ angular.module('portainer.services') }); 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; }]); diff --git a/app/services/messages.js b/app/services/messages.js index 3caf63ba6..7ed10b87d 100644 --- a/app/services/messages.js +++ b/app/services/messages.js @@ -22,6 +22,8 @@ angular.module('portainer.services') msg = e.message; } else if (e.data && e.data.length > 0 && e.data[0].message) { msg = e.data[0].message; + } else if (e.msg) { + msg = e.msg; } $.gritter.add({ title: $sanitize(title), diff --git a/app/services/templateService.js b/app/services/templateService.js index 79958eb1c..fb1c86414 100644 --- a/app/services/templateService.js +++ b/app/services/templateService.js @@ -21,17 +21,10 @@ angular.module('portainer.services') }; 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); containerConfiguration.Image = imageConfiguration.fromImage + ':' + imageConfiguration.tag; - return { - container: containerConfiguration, - image: imageConfiguration - }; - }; - - service.createImageConfiguration = function(template) { - return ImageHelper.createImageConfigForContainer(template.Image, template.Registry); + return containerConfiguration; }; service.createContainerConfiguration = function(template, containerName, network, containerMapping) {
{{ var|key: '=' }} {{ var|value: '=' }}