diff --git a/app/components/image/image.html b/app/components/image/image.html index eeff8d7eb..dac34024b 100644 --- a/app/components/image/image.html +++ b/app/components/image/image.html @@ -1,97 +1,115 @@ - + + + - Images > {{ id }} + Images > {{ image.Id }} +
+
+ + + +
+ {{ tag }} + + + + +
+
+ Note: you can click on the trash icon to delete a tag +
+
+
+
+
- - -
- -
-
{{ id }}
-
Image ID
-
-
-
-
-
-
- - -
- -
-
-
- -
-
-
- Actions -
-
-
-
-
-
-
- - - - - + - + - - - - - - - + + - - + + - - - - - - - - - - - - - + + + + + + +
Created{{ image.Created | date: 'medium'}}
TagsID -
    -
  • {{ tag }} - -
  • -
+ {{ image.Id }} +
Parent {{ image.Parent }}
Size (Virtual Size){{ image.Size|humansize }} ({{ image.VirtualSize|humansize }})
Hostname{{ image.ContainerConfig.Hostname }}Size{{ image.VirtualSize|humansize }}
User{{ image.ContainerConfig.User }}Created{{ image.Created|getisodate }}
Cmd{{ image.ContainerConfig.Cmd }}
Volumes{{ image.ContainerConfig.Volumes }}
Volumes from{{ image.ContainerConfig.VolumesFrom }}
Built withBuild Docker {{ image.DockerVersion }} on {{ image.Os}}, {{ image.Architecture }}
Author{{ image.Author }}
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
CMD{{ image.ContainerConfig.Cmd|command }}
ENTRYPOINT{{ image.ContainerConfig.Entrypoint|command }}
EXPOSE + + {{ port }} + +
VOLUME + + {{ volume }} + +
ENV + + + + + +
{{ var|key }}{{ var|value }}
+
diff --git a/app/components/image/imageController.js b/app/components/image/imageController.js index e1710a62f..a249b9905 100644 --- a/app/components/image/imageController.js +++ b/app/components/image/imageController.js @@ -1,33 +1,10 @@ angular.module('image', []) -.controller('ImageController', ['$scope', '$q', '$stateParams', '$state', 'Image', 'Container', 'Messages', 'LineChart', -function ($scope, $q, $stateParams, $state, Image, Container, Messages, LineChart) { - $scope.tagInfo = {repo: '', version: '', force: false}; - $scope.id = ''; - $scope.repoTags = []; +.controller('ImageController', ['$scope', '$stateParams', '$state', 'Image', 'Messages', +function ($scope, $stateParams, $state, Image, Messages) { + $scope.RepoTags = []; - $scope.removeImage = function (id) { - Image.remove({id: id}, function (d) { - d.forEach(function(msg){ - var key = Object.keys(msg)[0]; - Messages.send(key, msg[key]); - }); - // If last message key is 'Deleted' then assume the image is gone and send to images page - if (d[d.length-1].Deleted) { - $state.go('images', {}, {reload: true}); - } else { - $state.go('image', {id: $scope.id}, {reload: true}); - } - }, function (e) { - $scope.error = e.data; - $('#error-message').show(); - }); - }; - - /** - * Get RepoTags from the /images/query endpoint instead of /image/json, - * for backwards compatibility with Docker API versions older than 1.21 - * @param {string} imageId - */ + // 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) { @@ -38,21 +15,45 @@ function ($scope, $q, $stateParams, $state, Image, Container, Messages, LineChar }); } + $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("Unable to remove image", e.data); + }); + }; + + $('#loadingViewSpinner').show(); Image.get({id: $stateParams.id}, function (d) { $scope.image = d; - $scope.id = d.Id; if (d.RepoTags) { $scope.RepoTags = d.RepoTags; } else { - getRepoTags($scope.id); + 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) { if (e.status === 404) { - $('.detail').hide(); - $scope.error = "Image not found.
" + $stateParams.id; + Messages.error("Unable to find image", $stateParams.id); } else { - $scope.error = e.data; + Messages.error("Unable to retrieve image info", e.data); } - $('#error-message').show(); }); }]); diff --git a/app/shared/filters.js b/app/shared/filters.js index ae0cb58b1..c668b109f 100644 --- a/app/shared/filters.js +++ b/app/shared/filters.js @@ -94,7 +94,6 @@ angular.module('uifordocker.filters', []) if (state === undefined) { return 'label-default'; } - if (state.Ghost && state.Running) { return 'label-important'; } @@ -169,6 +168,32 @@ angular.module('uifordocker.filters', []) return moment.unix(timestamp).format('YYYY-MM-DD HH:mm:ss'); }; }) +.filter('getisodate', function () { + 'use strict'; + return function (date) { + return moment(date).format('YYYY-MM-DD HH:mm:ss'); + }; +}) +.filter('command', function () { + 'use strict'; + return function (command) { + if (command) { + return command.join(' '); + } + }; +}) +.filter('key', function () { + 'use strict'; + return function (pair) { + return pair.slice(0, pair.indexOf('=')); + }; +}) +.filter('value', function () { + 'use strict'; + return function (pair) { + return pair.slice(pair.indexOf('=') + 1); + }; +}) .filter('errorMsg', function () { return function (object) { var idx = 0; diff --git a/app/shared/responseHandlers.js b/app/shared/responseHandlers.js new file mode 100644 index 000000000..a0e44306c --- /dev/null +++ b/app/shared/responseHandlers.js @@ -0,0 +1,25 @@ +// Events query API return a list of JSON object. +// This handler wrap the JSON objects in an array. +function queryEventsHandler(data) { + var str = "[" + data.replace(/\n/g, " ").replace(/\}\s*\{/g, "}, {") + "]"; + return angular.fromJson(str); +} + +// Image create API return a list of JSON object. +// This handler wrap the JSON objects in an array. +function createImageHandler(data) { + var str = "[" + data.replace(/\n/g, " ").replace(/\}\s*\{/g, "}, {") + "]"; + return angular.fromJson(str); +} + +// Image delete API returns an array on success and an object on error. +// This handler creates an array from an object in case of error. +function deleteImageHandler(data) { + var response = angular.fromJson(data); + if (!Array.isArray(response)) { + var arr = []; + arr.push(response); + return arr; + } + return response; +} diff --git a/app/shared/services.js b/app/shared/services.js index d5cee721b..c320812ac 100644 --- a/app/shared/services.js +++ b/app/shared/services.js @@ -92,28 +92,28 @@ angular.module('uifordocker.services', ['ngResource', 'ngSanitize']) get: {method: 'GET', params: {action: 'json'}}, search: {method: 'GET', params: {action: 'search'}}, history: {method: 'GET', params: {action: 'history'}, isArray: true}, - create: { - method: 'POST', isArray: true, transformResponse: [function f(data) { - var str = "[" + data.replace(/\n/g, " ").replace(/\}\s*\{/g, "}, {") + "]"; - return angular.fromJson(str); - }], - params: {action: 'create', fromImage: '@fromImage', tag: '@tag'} - }, insert: {method: 'POST', params: {id: '@id', action: 'insert'}}, push: {method: 'POST', params: {id: '@id', action: 'push'}}, tag: {method: 'POST', params: {id: '@id', action: 'tag', force: 0, repo: '@repo', tag: '@tag'}}, - remove: {method: 'DELETE', params: {id: '@id'}, isArray: true}, - inspect: {method: 'GET', params: {id: '@id', action: 'json'}} + inspect: {method: 'GET', params: {id: '@id', action: 'json'}}, + create: { + method: 'POST', params: {action: 'create', fromImage: '@fromImage', tag: '@tag'}, + isArray: true, transformResponse: createImageHandler, + }, + remove: { + method: 'DELETE', params: {id: '@id'}, + isArray: true, transformResponse: deleteImageHandler + } }); }]) .factory('Events', ['$resource', 'Settings', function EventFactory($resource, Settings) { 'use strict'; // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/monitor-docker-s-events return $resource(Settings.url + '/events', {}, { - query: {method: 'GET', params: {since: '@since', until: '@until'}, isArray: true, transformResponse: [function f(data) { - var str = "[" + data.replace(/\n/g, " ").replace(/\}\s*\{/g, "}, {") + "]"; - return angular.fromJson(str); - }]} + query: { + method: 'GET', params: {since: '@since', until: '@until'}, + isArray: true, transformResponse: queryEventsHandler, + } }); }]) .factory('Version', ['$resource', 'Settings', function VersionFactory($resource, Settings) { diff --git a/assets/css/app.css b/assets/css/app.css index ecfd4e977..3750f277c 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -170,18 +170,26 @@ input[type="radio"] { margin-right: 5px; } -.green-icon { +.fa.green-icon { color: #23ae89; } -.red-icon { +.fa.red-icon { color: #ae2323; } +.fa.white-icon { + color: white; +} + .image-tag { margin-right: 5px; } +.label.tag { + margin-right: 5px; +} + .widget .widget-body table tbody .image-tag { font-size: 90% !important; } @@ -190,3 +198,7 @@ input[type="radio"] { width: 100%; padding: 10px 5px; } + +.interactive { + cursor: pointer; +}