Merge branch 'release/1.5.0'

pull/83/head 1.5.0
Anthony Lapenna 2016-07-14 12:12:13 +12:00
commit 85140c7dcf
14 changed files with 237 additions and 113 deletions

View File

@ -10,6 +10,12 @@ UI For Docker is a web interface for the Docker Remote API. The goal is to prov
* Minimal dependencies - I really want to keep this project a pure html/js app. * Minimal dependencies - I really want to keep this project a pure html/js app.
* Consistency - The web UI should be consistent with the commands found on the docker CLI. * Consistency - The web UI should be consistent with the commands found on the docker CLI.
## Supported Docker versions
The current Docker version support policy is the following: `N` to `N-2` included where `N` is the latest version.
At the moment, the following versions are supported: 1.9, 1.10 & 1.11.
## Run ## Run
### Quickstart ### Quickstart

View File

@ -144,4 +144,4 @@ angular.module('uifordocker', [
.constant('DOCKER_ENDPOINT', 'dockerapi') .constant('DOCKER_ENDPOINT', 'dockerapi')
.constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is requred. If you have a port, prefix it with a ':' i.e. :4243 .constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is requred. If you have a port, prefix it with a ':' i.e. :4243
.constant('CONFIG_ENDPOINT', '/config') .constant('CONFIG_ENDPOINT', '/config')
.constant('UI_VERSION', 'v1.4.0'); .constant('UI_VERSION', 'v1.5.0');

View File

@ -1,76 +1,131 @@
<rd-header> <rd-header>
<rd-header-title title="Home"></rd-header-title> <rd-header-title title="Home">
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
</rd-header-title>
<rd-header-content>Dashboard</rd-header-content> <rd-header-content>Dashboard</rd-header-content>
</rd-header> </rd-header>
<div class="row"> <div class="row">
<div class="col-lg-3 col-md-6 col-xs-12"> <div class="col-lg-12 col-md-12 col-xs-12" ng-if="!swarm">
<rd-widget>
<rd-widget-header icon="fa-cogs" title="Node info"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Name</td>
<td>{{ infoData.Name }}</td>
</tr>
<tr>
<td>Docker version</td>
<td>{{ infoData.ServerVersion }}</td>
</tr>
<tr>
<td>CPU</td>
<td>{{ infoData.NCPU }}</td>
</tr>
<tr>
<td>Memory</td>
<td>{{ infoData.MemTotal|humansize }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="swarm">
<rd-widget>
<rd-widget-header icon="fa-cogs" title="Cluster info"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Nodes</td>
<td>{{ infoData.SystemStatus[3][1] }}</td>
</tr>
<tr>
<td>Swarm version</td>
<td>{{ infoData.ServerVersion|swarmversion }}</td>
</tr>
<tr>
<td>Total CPU</td>
<td>{{ infoData.NCPU }}</td>
</tr>
<tr>
<td>Total memory</td>
<td>{{ infoData.MemTotal|humansize }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
<a ui-sref="containers">
<rd-widget> <rd-widget>
<rd-widget-body> <rd-widget-body>
<div class="widget-icon blue pull-left"> <div class="widget-icon blue pull-left">
<i class="fa fa-tasks"></i> <i class="fa fa-tasks"></i>
</div> </div>
<div class="pull-right">
<div><i class="fa fa-heartbeat text-icon green-icon"></i>{{ containerData.running }} running</div>
<div><i class="fa fa-heartbeat text-icon red-icon"></i>{{ containerData.stopped }} stopped</div>
</div>
<div class="title">{{ containerData.total }}</div> <div class="title">{{ containerData.total }}</div>
<div class="comment">Containers</div> <div class="comment">Containers</div>
</rd-widget-body> </rd-widget-body>
</rd-widget> </rd-widget>
</a>
</div> </div>
<div class="col-lg-3 col-md-6 col-xs-12"> <div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
<a ui-sref="images">
<rd-widget> <rd-widget>
<rd-widget-body> <rd-widget-body>
<div class="widget-icon green pull-left"> <div class="widget-icon blue pull-left">
<i class="fa fa-tasks"></i> <i class="fa fa-clone"></i>
</div> </div>
<div class="title">{{ containerData.running }}</div> <div class="pull-right">
<div class="comment">Running</div> <div><i class="fa fa-pie-chart text-icon"></i>{{ imageData.size|humansize }}</div>
</div>
<div class="title">{{ imageData.total }}</div>
<div class="comment">Images</div>
</rd-widget-body> </rd-widget-body>
</rd-widget> </rd-widget>
</a>
</div> </div>
<div class="col-lg-3 col-md-6 col-xs-12"> <div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
<a ui-sref="volumes">
<rd-widget> <rd-widget>
<rd-widget-body> <rd-widget-body>
<div class="widget-icon red pull-left"> <div class="widget-icon blue pull-left">
<i class="fa fa-tasks"></i> <i class="fa fa-cubes"></i>
</div> </div>
<div class="title">{{ containerData.stopped }}</div> <div class="pull-right" ng-if="infoData.Driver">
<div class="comment">Stopped</div> <div><i class="fa fa-hdd-o text-icon"></i>{{ infoData.Driver }} driver</div>
</div>
<div class="title">{{ volumeData.total }}</div>
<div class="comment">Volumes</div>
</rd-widget-body> </rd-widget-body>
</rd-widget> </rd-widget>
</a>
</div> </div>
<div class="col-lg-3 col-md-6 col-xs-12"> <div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
<a ui-sref="networks">
<rd-widget> <rd-widget>
<rd-widget-body> <rd-widget-body>
<div class="widget-icon gray pull-left"> <div class="widget-icon blue pull-left">
<i class="fa fa-tasks"></i> <i class="fa fa-sitemap"></i>
</div> </div>
<div class="title">{{ containerData.ghost }}</div> <div class="title">{{ networkData.total }}</div>
<div class="comment">Ghost</div> <div class="comment">Networks</div>
</rd-widget-body> </rd-widget-body>
</rd-widget> </rd-widget>
</a>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-6">
<rd-widget>
<rd-widget-header icon="fa-tasks" title="Containers created"></rd-widget-header>
<rd-widget-body>
<canvas id="containers-started-chart" width="770" height="230">
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a
href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
</canvas>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-6">
<rd-widget>
<rd-widget-header icon="fa-clone" title="Images created"></rd-widget-header>
<rd-widget-body>
<canvas id="images-created-chart" width="770" height="230">
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a
href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
</canvas>
</rd-widget-body>
</rd-widget>
</div>
</div> </div>

View File

@ -1,27 +1,22 @@
angular.module('dashboard', []) angular.module('dashboard', [])
.controller('DashboardController', ['$scope', 'Config', 'Container', 'Image', 'Settings', 'LineChart', .controller('DashboardController', ['$scope', '$q', 'Config', 'Container', 'Image', 'Network', 'Volume', 'Info',
function ($scope, Config, Container, Image, Settings, LineChart) { function ($scope, $q, Config, Container, Image, Network, Volume, Info) {
$scope.containerData = {}; $scope.containerData = {
total: 0
var buildCharts = function (data) { };
$scope.containerData.total = data.length; $scope.imageData = {
LineChart.build('#containers-started-chart', data, function (c) { total: 0
return new Date(c.Created * 1000).toLocaleDateString(); };
}); $scope.networkData = {
var s = $scope; total: 0
Image.query({}, function (d) { };
s.totalImages = d.length; $scope.volumeData = {
LineChart.build('#images-created-chart', d, function (c) { total: 0
return new Date(c.Created * 1000).toLocaleDateString();
});
});
}; };
function fetchDashboardData() { function prepareContainerData(d) {
Container.query({all: 1}, function (d) {
var running = 0; var running = 0;
var ghost = 0;
var stopped = 0; var stopped = 0;
var containers = d; var containers = d;
@ -31,19 +26,58 @@ function ($scope, Config, Container, Image, Settings, LineChart) {
for (var i = 0; i < containers.length; i++) { for (var i = 0; i < containers.length; i++) {
var item = containers[i]; var item = containers[i];
if (item.Status === "Ghost") { if (item.Status.indexOf('Up') !== -1) {
ghost += 1; running += 1;
} else if (item.Status.indexOf('Exit') !== -1) { } else if (item.Status.indexOf('Exit') !== -1) {
stopped += 1; stopped += 1;
} else {
running += 1;
} }
} }
$scope.containerData.running = running; $scope.containerData.running = running;
$scope.containerData.stopped = stopped; $scope.containerData.stopped = stopped;
$scope.containerData.ghost = ghost; $scope.containerData.total = containers.length;
}
buildCharts(containers); function prepareImageData(d) {
var images = d;
var totalImageSize = 0;
for (var i = 0; i < images.length; i++) {
var item = images[i];
totalImageSize += item.VirtualSize;
}
$scope.imageData.total = images.length;
$scope.imageData.size = totalImageSize;
}
function prepareVolumeData(d) {
var volumes = d.Volumes;
$scope.volumeData.total = volumes.length;
}
function prepareNetworkData(d) {
var networks = d;
$scope.networkData.total = networks.length;
}
function prepareInfoData(d) {
var info = d;
$scope.infoData = info;
}
function fetchDashboardData() {
$('#loadingViewSpinner').show();
$q.all([
Container.query({all: 1}).$promise,
Image.query({}).$promise,
Volume.query({}).$promise,
Network.query({}).$promise,
Info.get({}).$promise
]).then(function (d) {
prepareContainerData(d[0]);
prepareImageData(d[1]);
prepareVolumeData(d[2]);
prepareNetworkData(d[3]);
prepareInfoData(d[4]);
$('#loadingViewSpinner').hide();
}); });
} }
@ -63,6 +97,7 @@ function ($scope, Config, Container, Image, Settings, LineChart) {
}; };
Config.$promise.then(function (c) { Config.$promise.then(function (c) {
$scope.swarm = c.swarm;
hiddenLabels = c.hiddenLabels; hiddenLabels = c.hiddenLabels;
fetchDashboardData(); fetchDashboardData();
}); });

View File

@ -45,7 +45,7 @@
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<rd-widget> <rd-widget>
<rd-widget-header icon="fa-object-group" title="Cluster status"></rd-widget-header> <rd-widget-header icon="fa-object-group" title="Engine status"></rd-widget-header>
<rd-widget-body classes="no-padding"> <rd-widget-body classes="no-padding">
<table class="table"> <table class="table">
<tbody> <tbody>

View File

@ -63,7 +63,7 @@
</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><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()" /> Select</label></th>
@ -76,7 +76,7 @@
</th> </th>
<th> <th>
<a ui-sref="images" ng-click="order('RepoTags')"> <a ui-sref="images" ng-click="order('RepoTags')">
Repository Tags
<span ng-show="sortType == 'RepoTags' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span> <span ng-show="sortType == 'RepoTags' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'RepoTags' && sortReverse" class="glyphicon glyphicon-chevron-up"></span> <span ng-show="sortType == 'RepoTags' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a> </a>
@ -101,7 +101,9 @@
<tr ng-repeat="image in (state.filteredImages = (images | filter:state.filter | orderBy:sortType:sortReverse))"> <tr ng-repeat="image in (state.filteredImages = (images | filter:state.filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="image.Checked" ng-change="selectItem(image)" /></td> <td><input type="checkbox" ng-model="image.Checked" ng-change="selectItem(image)" /></td>
<td><a ui-sref="image({id: image.Id})">{{ image.Id|truncate:20}}</a></td> <td><a ui-sref="image({id: image.Id})">{{ image.Id|truncate:20}}</a></td>
<td>{{ image|repotag }}</td> <td>
<span class="label label-primary image-tag" ng-repeat="tag in (image|repotags)">{{ tag }}</span>
</td>
<td>{{ image.VirtualSize|humansize }}</td> <td>{{ image.VirtualSize|humansize }}</td>
<td>{{ image.Created|getdate }}</td> <td>{{ image.Created|getdate }}</td>
</tr> </tr>

View File

@ -25,7 +25,7 @@
</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><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()"/> Select</label></th>

View File

@ -25,7 +25,7 @@
</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><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()"/> Select</label></th>

View File

@ -8,7 +8,7 @@ angular
icon: '@' icon: '@'
}, },
transclude: true, transclude: true,
template: '<div class="widget-header"><div class="row"><div class="pull-left"><i class="fa" ng-class="icon"></i> {{title}} </div><div class="pull-right col-xs-6 col-sm-4" ng-transclude></div></div></div>', template: '<div class="widget-header"><div class="row"><span class="pull-left"><i class="fa" ng-class="icon"></i> {{title}} </span><span class="pull-right col-xs-6 col-sm-4" ng-transclude></span></div></div>',
restrict: 'E' restrict: 'E'
}; };
return directive; return directive;

View File

@ -130,23 +130,29 @@ angular.module('dockerui.filters', [])
return _.split(container.Names[0], '/')[2]; return _.split(container.Names[0], '/')[2];
}; };
}) })
.filter('swarmversion', function () {
'use strict';
return function (text) {
return _.split(text, '/')[1];
};
})
.filter('swarmhostname', function () { .filter('swarmhostname', function () {
'use strict'; 'use strict';
return function (container) { return function (container) {
return _.split(container.Names[0], '/')[1]; return _.split(container.Names[0], '/')[1];
}; };
}) })
.filter('repotag', function () { .filter('repotags', function () {
'use strict'; 'use strict';
return function (image) { return function (image) {
if (image.RepoTags && image.RepoTags.length > 0) { if (image.RepoTags && image.RepoTags.length > 0) {
var tag = image.RepoTags[0]; var tag = image.RepoTags[0];
if (tag === '<none>:<none>') { if (tag === '<none>:<none>') {
tag = ''; return [];
} }
return tag; return image.RepoTags;
} }
return ''; return [];
}; };
}) })
.filter('getdate', function () { .filter('getdate', function () {

View File

@ -165,3 +165,23 @@ input[type="radio"] {
.clickable { .clickable {
cursor: pointer; cursor: pointer;
} }
.text-icon {
margin-right: 5px;
}
.green-icon {
color: #23ae89;
}
.red-icon {
color: #ae2323;
}
.image-tag {
margin-right: 5px;
}
.widget .widget-body table tbody .image-tag {
font-size: 90% !important;
}

View File

@ -1,6 +1,6 @@
{ {
"name": "uifordocker", "name": "uifordocker",
"version": "1.4.0", "version": "1.5.0",
"homepage": "https://github.com/kevana/ui-for-docker", "homepage": "https://github.com/kevana/ui-for-docker",
"authors": [ "authors": [
"Michael Crosby <crosbymichael@gmail.com>", "Michael Crosby <crosbymichael@gmail.com>",

View File

@ -221,7 +221,7 @@ func csrfWrapper(h http.Handler) http.Handler {
} }
func main() { func main() {
kingpin.Version("1.4.0") kingpin.Version("1.5.0")
kingpin.Parse() kingpin.Parse()
configuration := Config{ configuration := Config{

View File

@ -2,7 +2,7 @@
"author": "Michael Crosby & Kevan Ahlquist", "author": "Michael Crosby & Kevan Ahlquist",
"name": "uifordocker", "name": "uifordocker",
"homepage": "https://github.com/kevana/ui-for-docker", "homepage": "https://github.com/kevana/ui-for-docker",
"version": "1.4.0", "version": "1.5.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git@github.com:kevana/ui-for-docker.git" "url": "git@github.com:kevana/ui-for-docker.git"