diff --git a/app/components/containers/containers.html b/app/components/containers/containers.html index eddb0771d..d18d5ae95 100644 --- a/app/components/containers/containers.html +++ b/app/components/containers/containers.html @@ -33,10 +33,17 @@ </rd-widget-taskbar> <rd-widget-body classes="no-padding"> <div class="table-responsive"> - <table class="table"> + <table class="table table-hover"> <thead> <tr> - <th><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()" /> Select</label></th> + <th></th> + <th> + <a ui-sref="containers" ng-click="order('State')"> + State + <span ng-show="sortType == 'State' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span> + <span ng-show="sortType == 'State' && sortReverse" class="glyphicon glyphicon-chevron-up"></span> + </a> + </th> <th> <a ui-sref="containers" ng-click="order('Names')"> Name @@ -44,6 +51,20 @@ <span ng-show="sortType == 'Names' && sortReverse" class="glyphicon glyphicon-chevron-up"></span> </a> </th> + <th> + <a ui-sref="containers" ng-click="order('IP')"> + IP Address + <span ng-show="sortType == 'IP' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span> + <span ng-show="sortType == 'IP' && sortReverse" class="glyphicon glyphicon-chevron-up"></span> + </a> + </th> + <th ng-if="swarm"> + <a ui-sref="containers" ng-click="order('Host')"> + Host + <span ng-show="sortType == 'Host' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span> + <span ng-show="sortType == 'Host' && sortReverse" class="glyphicon glyphicon-chevron-up"></span> + </a> + </th> <th> <a ui-sref="containers" ng-click="order('Image')"> Image @@ -58,30 +79,18 @@ <span ng-show="sortType == 'Command' && sortReverse" class="glyphicon glyphicon-chevron-up"></span> </a> </th> - <th> - <a ui-sref="containers" ng-click="order('Created')"> - Created - <span ng-show="sortType == 'Created' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span> - <span ng-show="sortType == 'Created' && sortReverse" class="glyphicon glyphicon-chevron-up"></span> - </a> - </th> - <th> - <a ui-sref="containers" ng-click="order('Status')"> - Status - <span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span> - <span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span> - </a> - </th> </tr> </thead> <tbody> <tr ng-repeat="container in (state.filteredContainers = ( containers | filter:state.filter | orderBy:sortType:sortReverse))"> <td><input type="checkbox" ng-model="container.Checked" ng-change="selectItem(container)"/></td> - <td><a ui-sref="container({id: container.Id})">{{ container|containername}}</a></td> + <td><span class="label label-{{ container.State|containerstatusbadge }}">{{ container.State }}</span></td> + <td ng-if="swarm"><a ui-sref="container({id: container.Id})">{{ container|swarmcontainername}}</a></td> + <td ng-if="!swarm"><a ui-sref="container({id: container.Id})">{{ container|containername}}</a></td> + <td>{{ container.IP ? container.IP : '-' }}</td> + <td ng-if="swarm">{{ container|swarmhostname}}</td> <td><a ui-sref="image({id: container.Image})">{{ container.Image }}</a></td> - <td>{{ container.Command|truncate:40 }}</td> - <td>{{ container.Created|getdate }}</td> - <td><span class="label label-{{ container.Status|statusbadge }}">{{ container.Status }}</span></td> + <td>{{ container.Command|truncate:60 }}</td> </tr> </tbody> </table> diff --git a/app/components/containers/containersController.js b/app/components/containers/containersController.js index c16e101f2..39f6a2b92 100644 --- a/app/components/containers/containersController.js +++ b/app/components/containers/containersController.js @@ -4,9 +4,8 @@ function ($scope, Container, Settings, Messages, ViewSpinner, Config) { $scope.state = {}; $scope.state.displayAll = Settings.displayAll; - $scope.sortType = 'Created'; + $scope.sortType = 'State'; $scope.sortReverse = true; - $scope.state.toggle = false; $scope.state.selectedItemCount = 0; $scope.order = function (sortType) { @@ -91,18 +90,6 @@ function ($scope, Container, Settings, Messages, ViewSpinner, Config) { } }; - $scope.toggleSelectAll = function () { - $scope.state.selectedItem = $scope.state.toggle; - angular.forEach($scope.state.filteredContainers, function (i) { - i.Checked = $scope.state.toggle; - }); - if ($scope.state.toggle) { - $scope.state.selectedItemCount = $scope.state.filteredContainers.length; - } else { - $scope.state.selectedItemCount = 0; - } - }; - $scope.toggleGetAll = function () { Settings.displayAll = $scope.state.displayAll; update({all: Settings.displayAll ? 1 : 0}); @@ -151,9 +138,10 @@ function ($scope, Container, Settings, Messages, ViewSpinner, Config) { }); }; - var hiddenLabels; + $scope.swarm = false; Config.$promise.then(function (c) { hiddenLabels = c.hiddenLabels; + $scope.swarm = c.swarm; update({all: Settings.displayAll ? 1 : 0}); }); }]); diff --git a/app/components/swarm/swarm.html b/app/components/swarm/swarm.html index 19c621a2b..cf4a186fe 100644 --- a/app/components/swarm/swarm.html +++ b/app/components/swarm/swarm.html @@ -137,7 +137,7 @@ <td>{{ node.ip }}</td> <td>{{ node.containers }}</td> <td>{{ node.version }}</td> - <td><span class="label label-{{ node.status|statusbadge }}">{{ node.status }}</span></td> + <td><span class="label label-{{ node.status|nodestatusbadge }}">{{ node.status }}</span></td> </tr> </tbody> </table> diff --git a/app/shared/filters.js b/app/shared/filters.js index daf5bda64..ea08f0057 100644 --- a/app/shared/filters.js +++ b/app/shared/filters.js @@ -1,128 +1,149 @@ angular.module('dockerui.filters', []) - .filter('truncate', function () { - 'use strict'; - return function (text, length, end) { - if (isNaN(length)) { - length = 10; - } +.filter('truncate', function () { + 'use strict'; + return function (text, length, end) { + if (isNaN(length)) { + length = 10; + } - if (end === undefined) { - end = '...'; - } + if (end === undefined) { + end = '...'; + } - if (text.length <= length || text.length - end.length <= length) { - return text; - } - else { - return String(text).substring(0, length - end.length) + end; - } - }; - }) - .filter('statusbadge', function () { - 'use strict'; - return function (text) { - if (text === 'Ghost') { - return 'important'; - } else if (text === 'Unhealthy') { - return 'danger'; - } else if (text.indexOf('Exit') !== -1 && text !== 'Exit 0') { - return 'warning'; - } - return 'success'; - }; - }) - .filter('trimcontainername', function () { - 'use strict'; - return function (name) { - if (name) { - return (name.indexOf('/') === 0 ? name.replace('/','') : name); - } - return ''; - }; - }) - .filter('getstatetext', function () { - 'use strict'; - return function (state) { - if (state === undefined) { - return ''; - } - if (state.Ghost && state.Running) { - return 'Ghost'; - } - if (state.Running && state.Paused) { - return 'Running (Paused)'; - } - if (state.Running) { - return 'Running'; - } - return 'Stopped'; - }; - }) - .filter('getstatelabel', function () { - 'use strict'; - return function (state) { - if (state === undefined) { - return 'label-default'; - } + if (text.length <= length || text.length - end.length <= length) { + return text; + } + else { + return String(text).substring(0, length - end.length) + end; + } + }; +}) +.filter('containerstatusbadge', function () { + 'use strict'; + return function (text) { + if (text === 'paused') { + return 'warning'; + } else if (text === 'created') { + return 'info'; + } else if (text === 'exited') { + return 'danger'; + } + return 'success'; + }; +}) +.filter('nodestatusbadge', function () { + 'use strict'; + return function (text) { + if (text === 'Unhealthy') { + return 'danger'; + } + return 'success'; + }; +}) +.filter('trimcontainername', function () { + 'use strict'; + return function (name) { + if (name) { + return (name.indexOf('/') === 0 ? name.replace('/','') : name); + } + return ''; + }; +}) +.filter('getstatetext', function () { + 'use strict'; + return function (state) { + if (state === undefined) { + return ''; + } + if (state.Ghost && state.Running) { + return 'Ghost'; + } + if (state.Running && state.Paused) { + return 'Running (Paused)'; + } + if (state.Running) { + return 'Running'; + } + return 'Stopped'; + }; +}) +.filter('getstatelabel', function () { + 'use strict'; + return function (state) { + if (state === undefined) { + return 'label-default'; + } - if (state.Ghost && state.Running) { - return 'label-important'; - } - if (state.Running) { - return 'label-success'; - } - return 'label-default'; - }; - }) - .filter('humansize', function () { - 'use strict'; - return function (bytes) { - var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; - if (bytes === 0) { - return 'n/a'; - } - var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10); - var value = bytes / Math.pow(1024, i); - var decimalPlaces = (i < 1) ? 0 : (i - 1); - return value.toFixed(decimalPlaces) + ' ' + sizes[[i]]; - }; - }) - .filter('containername', function () { - 'use strict'; - return function (container) { - var name = container.Names[0]; - return name.substring(1, name.length); - }; - }) - .filter('repotag', function () { - 'use strict'; - return function (image) { - if (image.RepoTags && image.RepoTags.length > 0) { - var tag = image.RepoTags[0]; - if (tag === '<none>:<none>') { - tag = ''; - } - return tag; - } - return ''; - }; - }) - .filter('getdate', function () { - 'use strict'; - return function (data) { - //Multiply by 1000 for the unix format - var date = new Date(data * 1000); - return date.toDateString(); - }; - }) - .filter('errorMsg', function () { - return function (object) { - var idx = 0; - var msg = ''; - while (object[idx] && typeof(object[idx]) === 'string') { - msg += object[idx]; - idx++; - } - return msg; - }; - }); + if (state.Ghost && state.Running) { + return 'label-important'; + } + if (state.Running) { + return 'label-success'; + } + return 'label-default'; + }; +}) +.filter('humansize', function () { + 'use strict'; + return function (bytes) { + var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + if (bytes === 0) { + return 'n/a'; + } + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10); + var value = bytes / Math.pow(1024, i); + var decimalPlaces = (i < 1) ? 0 : (i - 1); + return value.toFixed(decimalPlaces) + ' ' + sizes[[i]]; + }; +}) +.filter('containername', function () { + 'use strict'; + return function (container) { + var name = container.Names[0]; + return name.substring(1, name.length); + }; +}) +.filter('swarmcontainername', function () { + 'use strict'; + return function (container) { + return _.split(container.Names[0], '/')[2]; + }; +}) +.filter('swarmhostname', function () { + 'use strict'; + return function (container) { + return _.split(container.Names[0], '/')[1]; + }; +}) +.filter('repotag', function () { + 'use strict'; + return function (image) { + if (image.RepoTags && image.RepoTags.length > 0) { + var tag = image.RepoTags[0]; + if (tag === '<none>:<none>') { + tag = ''; + } + return tag; + } + return ''; + }; +}) +.filter('getdate', function () { + 'use strict'; + return function (data) { + //Multiply by 1000 for the unix format + var date = new Date(data * 1000); + return date.toDateString(); + }; +}) +.filter('errorMsg', function () { + return function (object) { + var idx = 0; + var msg = ''; + while (object[idx] && typeof(object[idx]) === 'string') { + msg += object[idx]; + idx++; + } + return msg; + }; +}); diff --git a/app/shared/viewmodel.js b/app/shared/viewmodel.js index 617d497d5..901a2fd86 100644 --- a/app/shared/viewmodel.js +++ b/app/shared/viewmodel.js @@ -10,11 +10,10 @@ function ImageViewModel(data) { function ContainerViewModel(data) { this.Id = data.Id; + this.State = data.State; + this.Names = data.Names; + this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress; this.Image = data.Image; this.Command = data.Command; - this.Created = data.Created; - this.SizeRw = data.SizeRw; - this.Status = data.Status; this.Checked = false; - this.Names = data.Names; } diff --git a/test/unit/app/shared/filters.spec.js b/test/unit/app/shared/filters.spec.js index 8005572f7..db832cd8f 100644 --- a/test/unit/app/shared/filters.spec.js +++ b/test/unit/app/shared/filters.spec.js @@ -15,20 +15,6 @@ describe('filters', function () { })); }); - describe('statusbadge', function () { - it('should be "important" when input is "Ghost"', inject(function (statusbadgeFilter) { - expect(statusbadgeFilter('Ghost')).toBe('important'); - })); - - it('should be "success" when input is "Exit 0"', inject(function (statusbadgeFilter) { - expect(statusbadgeFilter('Exit 0')).toBe('success'); - })); - - it('should be "warning" when exit code is non-zero', inject(function (statusbadgeFilter) { - expect(statusbadgeFilter('Exit 1')).toBe('warning'); - })); - }); - describe('getstatetext', function () { it('should return an empty string when state is undefined', inject(function (getstatetextFilter) { @@ -352,4 +338,4 @@ describe('filters', function () { expect(errorMsgFilter(response)).toBe(message); })); }); -}); \ No newline at end of file +});