feat(ui): new containers view (#25)

feat(ui): new containers view
pull/26/head
Anthony Lapenna 2016-06-29 21:04:29 +12:00 committed by GitHub
parent 813c14d93c
commit 66ae15b4fb
6 changed files with 182 additions and 179 deletions

View File

@ -33,10 +33,17 @@
</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">
<table class="table"> <table class="table table-hover">
<thead> <thead>
<tr> <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> <th>
<a ui-sref="containers" ng-click="order('Names')"> <a ui-sref="containers" ng-click="order('Names')">
Name Name
@ -44,6 +51,20 @@
<span ng-show="sortType == 'Names' && sortReverse" class="glyphicon glyphicon-chevron-up"></span> <span ng-show="sortType == 'Names' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a> </a>
</th> </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> <th>
<a ui-sref="containers" ng-click="order('Image')"> <a ui-sref="containers" ng-click="order('Image')">
Image Image
@ -58,30 +79,18 @@
<span ng-show="sortType == 'Command' && sortReverse" class="glyphicon glyphicon-chevron-up"></span> <span ng-show="sortType == 'Command' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a> </a>
</th> </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> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="container in (state.filteredContainers = ( containers | filter:state.filter | orderBy:sortType:sortReverse))"> <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><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><a ui-sref="image({id: container.Image})">{{ container.Image }}</a></td>
<td>{{ container.Command|truncate:40 }}</td> <td>{{ container.Command|truncate:60 }}</td>
<td>{{ container.Created|getdate }}</td>
<td><span class="label label-{{ container.Status|statusbadge }}">{{ container.Status }}</span></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -4,9 +4,8 @@ function ($scope, Container, Settings, Messages, ViewSpinner, Config) {
$scope.state = {}; $scope.state = {};
$scope.state.displayAll = Settings.displayAll; $scope.state.displayAll = Settings.displayAll;
$scope.sortType = 'Created'; $scope.sortType = 'State';
$scope.sortReverse = true; $scope.sortReverse = true;
$scope.state.toggle = false;
$scope.state.selectedItemCount = 0; $scope.state.selectedItemCount = 0;
$scope.order = function (sortType) { $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 () { $scope.toggleGetAll = function () {
Settings.displayAll = $scope.state.displayAll; Settings.displayAll = $scope.state.displayAll;
update({all: Settings.displayAll ? 1 : 0}); 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) { Config.$promise.then(function (c) {
hiddenLabels = c.hiddenLabels; hiddenLabels = c.hiddenLabels;
$scope.swarm = c.swarm;
update({all: Settings.displayAll ? 1 : 0}); update({all: Settings.displayAll ? 1 : 0});
}); });
}]); }]);

View File

@ -137,7 +137,7 @@
<td>{{ node.ip }}</td> <td>{{ node.ip }}</td>
<td>{{ node.containers }}</td> <td>{{ node.containers }}</td>
<td>{{ node.version }}</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> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -1,128 +1,149 @@
angular.module('dockerui.filters', []) angular.module('dockerui.filters', [])
.filter('truncate', function () { .filter('truncate', function () {
'use strict'; 'use strict';
return function (text, length, end) { return function (text, length, end) {
if (isNaN(length)) { if (isNaN(length)) {
length = 10; length = 10;
} }
if (end === undefined) { if (end === undefined) {
end = '...'; end = '...';
} }
if (text.length <= length || text.length - end.length <= length) { if (text.length <= length || text.length - end.length <= length) {
return text; return text;
} }
else { else {
return String(text).substring(0, length - end.length) + end; return String(text).substring(0, length - end.length) + end;
} }
}; };
}) })
.filter('statusbadge', function () { .filter('containerstatusbadge', function () {
'use strict'; 'use strict';
return function (text) { return function (text) {
if (text === 'Ghost') { if (text === 'paused') {
return 'important'; return 'warning';
} else if (text === 'Unhealthy') { } else if (text === 'created') {
return 'danger'; return 'info';
} else if (text.indexOf('Exit') !== -1 && text !== 'Exit 0') { } else if (text === 'exited') {
return 'warning'; return 'danger';
} }
return 'success'; return 'success';
}; };
}) })
.filter('trimcontainername', function () { .filter('nodestatusbadge', function () {
'use strict'; 'use strict';
return function (name) { return function (text) {
if (name) { if (text === 'Unhealthy') {
return (name.indexOf('/') === 0 ? name.replace('/','') : name); return 'danger';
} }
return ''; return 'success';
}; };
}) })
.filter('getstatetext', function () { .filter('trimcontainername', function () {
'use strict'; 'use strict';
return function (state) { return function (name) {
if (state === undefined) { if (name) {
return ''; return (name.indexOf('/') === 0 ? name.replace('/','') : name);
} }
if (state.Ghost && state.Running) { return '';
return 'Ghost'; };
} })
if (state.Running && state.Paused) { .filter('getstatetext', function () {
return 'Running (Paused)'; 'use strict';
} return function (state) {
if (state.Running) { if (state === undefined) {
return 'Running'; return '';
} }
return 'Stopped'; if (state.Ghost && state.Running) {
}; return 'Ghost';
}) }
.filter('getstatelabel', function () { if (state.Running && state.Paused) {
'use strict'; return 'Running (Paused)';
return function (state) { }
if (state === undefined) { if (state.Running) {
return 'label-default'; return 'Running';
} }
return 'Stopped';
};
})
.filter('getstatelabel', function () {
'use strict';
return function (state) {
if (state === undefined) {
return 'label-default';
}
if (state.Ghost && state.Running) { if (state.Ghost && state.Running) {
return 'label-important'; return 'label-important';
} }
if (state.Running) { if (state.Running) {
return 'label-success'; return 'label-success';
} }
return 'label-default'; return 'label-default';
}; };
}) })
.filter('humansize', function () { .filter('humansize', function () {
'use strict'; 'use strict';
return function (bytes) { return function (bytes) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) { if (bytes === 0) {
return 'n/a'; return 'n/a';
} }
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10); var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10);
var value = bytes / Math.pow(1024, i); var value = bytes / Math.pow(1024, i);
var decimalPlaces = (i < 1) ? 0 : (i - 1); var decimalPlaces = (i < 1) ? 0 : (i - 1);
return value.toFixed(decimalPlaces) + ' ' + sizes[[i]]; return value.toFixed(decimalPlaces) + ' ' + sizes[[i]];
}; };
}) })
.filter('containername', function () { .filter('containername', function () {
'use strict'; 'use strict';
return function (container) { return function (container) {
var name = container.Names[0]; var name = container.Names[0];
return name.substring(1, name.length); return name.substring(1, name.length);
}; };
}) })
.filter('repotag', function () { .filter('swarmcontainername', function () {
'use strict'; 'use strict';
return function (image) { return function (container) {
if (image.RepoTags && image.RepoTags.length > 0) { return _.split(container.Names[0], '/')[2];
var tag = image.RepoTags[0]; };
if (tag === '<none>:<none>') { })
tag = ''; .filter('swarmhostname', function () {
} 'use strict';
return tag; return function (container) {
} return _.split(container.Names[0], '/')[1];
return ''; };
}; })
}) .filter('repotag', function () {
.filter('getdate', function () { 'use strict';
'use strict'; return function (image) {
return function (data) { if (image.RepoTags && image.RepoTags.length > 0) {
//Multiply by 1000 for the unix format var tag = image.RepoTags[0];
var date = new Date(data * 1000); if (tag === '<none>:<none>') {
return date.toDateString(); tag = '';
}; }
}) return tag;
.filter('errorMsg', function () { }
return function (object) { return '';
var idx = 0; };
var msg = ''; })
while (object[idx] && typeof(object[idx]) === 'string') { .filter('getdate', function () {
msg += object[idx]; 'use strict';
idx++; return function (data) {
} //Multiply by 1000 for the unix format
return msg; 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;
};
});

View File

@ -10,11 +10,10 @@ function ImageViewModel(data) {
function ContainerViewModel(data) { function ContainerViewModel(data) {
this.Id = data.Id; 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.Image = data.Image;
this.Command = data.Command; this.Command = data.Command;
this.Created = data.Created;
this.SizeRw = data.SizeRw;
this.Status = data.Status;
this.Checked = false; this.Checked = false;
this.Names = data.Names;
} }

View File

@ -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 () { describe('getstatetext', function () {
it('should return an empty string when state is undefined', inject(function (getstatetextFilter) { 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); expect(errorMsgFilter(response)).toBe(message);
})); }));
}); });
}); });