Merge branch 'release/1.9.1'

pull/262/head 1.9.1
Anthony Lapenna 2016-10-02 16:26:19 +13:00
commit e88b22bd45
21 changed files with 96 additions and 88 deletions

View File

@ -3,6 +3,7 @@
The easiest way to manage Docker.
[![Microbadger](https://images.microbadger.com/badges/image/portainer/portainer.svg)](http://microbadger.com/images/portainer/portainer "Image size")
[![Documentation Status](https://readthedocs.org/projects/portainer/badge/?version=stable)](http://portainer.readthedocs.io/en/stable/?badge=stable)
[![Gitter](https://badges.gitter.im/portainer/Lobby.svg)](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
Portainer is a lightweight management UI which allows you to **easily** manage your Docker host or Swarm cluster.
@ -29,7 +30,7 @@ If you don't specify any target, its default behaviour is to use a bind mount on
$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer
```
Have a look at our [wiki](https://github.com/portainer/portainer/wiki/Deployment) for more deployment options.
Have a look at our [documentation](http://portainer.readthedocs.io/en/stable/deployment.html) for more deployment options.
# Configuration
@ -61,13 +62,13 @@ Add the `--templates` flag and specify the external location of your templates w
$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer --templates http://my-host.my-domain/templates.json
```
For more information about hosting your own template definitions and the format, see: https://github.com/portainer/templates
For more information about hosting your own template definitions and the format, see the [templates documentation](http://portainer.readthedocs.io/en/stable/templates.html).
Have a look at our [wiki](https://github.com/portainer/portainer/wiki/Configuration) for more configuration options.
Check our [documentation](http://portainer.readthedocs.io/en/stable/configuration.html) for more configuration options.
# FAQ
Be sure to check our [FAQ](https://github.com/portainer/portainer/wiki/FAQ) if you are missing some information.
Be sure to check our [FAQ](http://portainer.readthedocs.io/en/stable/faq.html) if you are missing some information.
# Limitations

View File

@ -6,7 +6,7 @@ import (
// main is the entry point of the program
func main() {
kingpin.Version("1.9.0")
kingpin.Version("1.9.1")
var (
endpoint = kingpin.Flag("host", "Dockerd endpoint").Default("unix:///var/run/docker.sock").Short('H').String()
addr = kingpin.Flag("bind", "Address and port to serve Portainer").Default(":9000").Short('p').String()

View File

@ -188,4 +188,4 @@ angular.module('portainer', [
.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', 'settings')
.constant('TEMPLATES_ENDPOINT', 'templates')
.constant('UI_VERSION', 'v1.9.0');
.constant('UI_VERSION', 'v1.9.1');

View File

@ -9,7 +9,7 @@ function ($scope, $stateParams, Settings, Container, Exec, $timeout, Messages) {
// Ensure the socket is closed before leaving the view
$scope.$on('$stateChangeStart', function (event, next, current) {
if (socket !== null) {
if (socket && socket !== null) {
socket.close();
}
});

View File

@ -41,8 +41,8 @@
<th>
<a ui-sref="containers" ng-click="order('Status')">
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>
<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>
<th>
@ -93,7 +93,7 @@
<td ng-if="swarm && !swarm_mode">{{ container.hostIP }}</td>
<td>
<a ng-if="container.Ports.length > 0" ng-repeat="p in container.Ports" class="image-tag" ng-href="http://{{p.host}}:{{p.public}}" target="_blank">
<i class="fa fa-external-link" aria-hidden="true"></i> {{ p.private }}
<i class="fa fa-external-link" aria-hidden="true"></i> {{p.public}}:{{ p.private }}
</a>
<span ng-if="container.Ports.length == 0" >-</span>
</td>

View File

@ -41,7 +41,7 @@
<tbody>
<tr>
<td>Nodes</td>
<td>{{ infoData.SystemStatus[3][1] }}</td>
<td>{{ infoData.SystemStatus[0][1] == 'primary' ? infoData.SystemStatus[3][1] : infoData.SystemStatus[4][1] }}</td>
</tr>
<tr>
<td>Swarm version</td>
@ -53,7 +53,7 @@
</tr>
<tr>
<td>Total memory</td>
<td>{{ infoData.MemTotal|humansize }}</td>
<td>{{ infoData.MemTotal|humansize: 2 }}</td>
</tr>
</tbody>
</table>

View File

@ -87,7 +87,7 @@ function (Settings, $scope, Messages, $timeout, Container, ContainerTop, $stateP
},
{
scaleLabel: function (valueObj) {
return humansizeFilter(parseInt(valueObj.value, 10));
return humansizeFilter(parseInt(valueObj.value, 10), 2);
},
responsive: true
//scaleOverride: true,
@ -100,7 +100,7 @@ function (Settings, $scope, Messages, $timeout, Container, ContainerTop, $stateP
datasets: [networkRxDataset, networkTxDataset]
}, {
scaleLabel: function (valueObj) {
return humansizeFilter(parseInt(valueObj.value, 10));
return humansizeFilter(parseInt(valueObj.value, 10), 2);
},
responsive: true
});

View File

@ -42,8 +42,8 @@
</tr>
<tr>
<td>Total memory</td>
<td ng-if="!swarm_mode">{{ info.MemTotal|humansize }}</td>
<td ng-if="swarm_mode">{{ totalMemory|humansize }}</td>
<td ng-if="!swarm_mode">{{ info.MemTotal|humansize: 2 }}</td>
<td ng-if="swarm_mode">{{ totalMemory|humansize: 2 }}</td>
</tr>
<tr ng-if="!swarm_mode">
<td>Operating system</td>

View File

@ -47,7 +47,8 @@ function ($scope, Info, Version, Node) {
// Swarm filters
$scope.swarm[systemStatus[2][0]] = systemStatus[2][1];
// Swarm node count
var node_count = parseInt(systemStatus[3][1], 10);
var nodes = systemStatus[0][1] === 'primary' ? systemStatus[3][1] : systemStatus[4][1];
var node_count = parseInt(nodes, 10);
$scope.swarm[systemStatus[3][0]] = node_count;
$scope.swarm.Status = [];
@ -55,9 +56,10 @@ function ($scope, Info, Version, Node) {
}
function extractNodesInfo(info, node_count) {
// First information for node1 available at element #4 of SystemStatus
// First information for node1 available at element #4 of SystemStatus if connected to a primary
// If connected to a replica, information for node1 is available at element #5
// The next 10 elements are information related to the node
var node_offset = 4;
var node_offset = info[0][1] === 'primary' ? 4 : 5;
for (i = 0; i < node_count; i++) {
extractNodeInfo(info, node_offset);
node_offset += 9;

View File

@ -7,31 +7,11 @@
<rd-header-content>Templates</rd-header-content>
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-rocket" title="Available templates">
<div class="pull-right">
<i id="loadTemplatesSpinner" class="fa fa-cog fa-2x fa-spin" style="margin-top: 5px;"></i>
</div>
</rd-widget-header>
<rd-widget-body classes="padding">
<div class="template-list">
<div ng-repeat="tpl in templates" class="container-template hvr-grow" id="template_{{ $index }}" ng-click="selectTemplate($index)">
<img class="logo" ng-src="{{ tpl.logo }}" />
<div class="title">{{ tpl.title }}</div>
<div class="description">{{ tpl.description }}</div>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row" ng-if="selectedTemplate">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-cogs" title="Configuration"></rd-widget-header>
<rd-widget-custom-header icon="selectedTemplate.logo" title="selectedTemplate.image">
</rd-widget-custom-header>
<rd-widget-body classes="padding">
<form class="form-horizontal">
<div class="form-group" ng-if="globalNetworkCount === 0 && !swarm_mode">
@ -62,15 +42,21 @@
<div ng-repeat="var in selectedTemplate.env" ng-if="!var.set" class="form-group">
<label for="field_{{ $index }}" class="col-sm-2 control-label text-left">{{ var.label }}</label>
<div class="col-sm-10">
<select ng-if="!swarm && var.type === 'container'" ng-options="container|containername for container in runningContainers" class="selectpicker form-control" ng-model="var.value">
<select ng-if="(!swarm || swarm && swarm_mode) && var.type === 'container'" ng-options="container|containername for container in runningContainers" class="selectpicker form-control" ng-model="var.value">
<option selected disabled hidden value="">Select a container</option>
</select>
<select ng-if="swarm && var.type === 'container'" ng-options="container|swarmcontainername for container in runningContainers" class="selectpicker form-control" ng-model="var.value">
<select ng-if="swarm && !swarm_mode && var.type === 'container'" ng-options="container|swarmcontainername for container in runningContainers" class="selectpicker form-control" ng-model="var.value">
<option selected disabled hidden value="">Select a container</option>
</select>
<input ng-if="!var.type || !var.type === 'container'" type="text" class="form-control" ng-model="var.value" id="field_{{ $index }}">
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-default btn-sm" ng-disabled="!formValues.network" ng-click="createTemplate()">Create</button>
<i id="createContainerSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>
</form>
</rd-widget-body>
</rd-widget>
@ -78,10 +64,26 @@
</div>
<div class="row" ng-if="selectedTemplate">
<div class="col-lg-12 col-md-12 col-xs-12" style="text-align: center;">
<div>
<i id="createContainerSpinner" class="fa fa-cog fa-3x fa-spin" style="margin-bottom: 5px; display: none;"></i>
</div>
<button type="button" class="btn btn-default btn-lg" ng-disabled="!formValues.network" ng-click="createTemplate()">Create</button>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-rocket" title="Available templates">
<div class="pull-right">
<i id="loadTemplatesSpinner" class="fa fa-cog fa-2x fa-spin" style="margin-top: 5px;"></i>
</div>
</rd-widget-header>
<rd-widget-body classes="padding">
<div class="template-list">
<div ng-repeat="tpl in templates" class="container-template hvr-grow" id="template_{{ $index }}" ng-click="selectTemplate($index)">
<img class="logo" ng-src="{{ tpl.logo }}" />
<div class="title">{{ tpl.title }}</div>
<div class="description">{{ tpl.description }}</div>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>

View File

@ -0,0 +1,15 @@
angular
.module('portainer')
.directive('rdWidgetCustomHeader', function rdWidgetCustomHeader() {
var directive = {
requires: '^rdWidget',
scope: {
title: '=',
icon: '='
},
transclude: true,
template: '<div class="widget-header"><div class="row"><span class="pull-left"><img class="custom-header-ico" ng-src="{{icon}}"></img> <span class="small text-muted"> {{title}} </span> </span><span class="pull-right col-xs-6 col-sm-4" ng-transclude></span></div></div>',
restrict: 'E'
};
return directive;
});

View File

@ -123,15 +123,13 @@ angular.module('portainer.filters', [])
})
.filter('humansize', function () {
'use strict';
return function (bytes) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) {
return 'n/a';
return function (bytes, round) {
if (!round) {
round = 1;
}
if (bytes || bytes === 0) {
return filesize(bytes, {base: 10, round: round});
}
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 () {

View File

@ -105,6 +105,9 @@ function createEventDetails(event) {
case 'unpause':
details = 'Container ' + eventAttr.name + ' unpaused';
break;
case 'attach':
details = 'Container ' + eventAttr.name + ' attached';
break;
default:
if (event.Action.indexOf('exec_create') === 0) {
details = 'Exec instance created';
@ -159,6 +162,12 @@ function createEventDetails(event) {
case 'destroy':
details = 'Volume ' + event.Actor.ID + ' deleted';
break;
case 'mount':
details = 'Volume ' + event.Actor.ID + ' mounted';
break;
case 'unmount':
details = 'Volume ' + event.Actor.ID + ' unmounted';
break;
default:
details = 'Unsupported event';
}

View File

@ -216,6 +216,11 @@ input[type="radio"] {
flex-wrap: wrap;
}
.custom-header-ico {
max-width: 16px;
max-height: 16px;
}
.container-template {
font-size: 1em;
width: 256px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 880 B

View File

@ -1,6 +1,6 @@
{
"name": "portainer",
"version": "1.9.0",
"version": "1.9.1",
"homepage": "https://github.com/portainer/portainer",
"authors": [
"Anthony Lapenna <anthony.lapenna at gmail dot com>"
@ -34,13 +34,14 @@
"angular-ui-select": "~0.17.1",
"bootstrap": "~3.3.6",
"font-awesome": "~4.6.3",
"filesize": "~3.3.0",
"Hover": "2.0.2",
"jquery": "1.11.1",
"jquery.gritter": "1.7.4",
"lodash": "4.12.0",
"rdash-ui": "1.0.*",
"moment": "~2.14.1",
"xterm.js": "~1.0.0"
"xterm.js": "~1.1.3"
},
"resolutions": {
"angular": "1.5.5"

View File

@ -71,6 +71,7 @@ module.exports = function (grunt) {
'bower_components/bootstrap/dist/js/bootstrap.min.js',
'bower_components/Chart.js/Chart.min.js',
'bower_components/lodash/dist/lodash.min.js',
'bower_components/filesize/lib/filesize.min.js',
'bower_components/moment/min/moment.min.js',
'bower_components/xterm.js/src/xterm.js',
'assets/js/jquery.gritter.js', // Using custom version to fix error in minified build due to "use strict"
@ -274,7 +275,7 @@ module.exports = function (grunt) {
command: [
'docker stop portainer',
'docker rm portainer',
'docker run -d -p 9000:9000 -v /tmp/portainer:/data --name portainer portainer -H tcp://10.0.7.10:4000 --swarm -d /data'
'docker run -d -p 9000:9000 -v /tmp/portainer:/data --name portainer portainer -H tcp://10.0.7.10:2375 --swarm -d /data'
].join(';')
},
runSsl: {

View File

@ -59,13 +59,13 @@
<li class="sidebar-list">
<a ui-sref="volumes">Volumes <span class="menu-icon fa fa-cubes"></span></a>
</li>
<li class="sidebar-list" ng-if="swarm_mode || !swarm">
<li class="sidebar-list" ng-if="!swarm">
<a ui-sref="events">Events <span class="menu-icon fa fa-history"></span></a>
</li>
<li class="sidebar-list" ng-if="(swarm && !swarm_mode) || (swarm_mode && swarm_manager)">
<a ui-sref="swarm">Swarm <span class="menu-icon fa fa-object-group"></span></a>
</li>
<li class="sidebar-list" ng-if="swarm_mode || !swarm">
<li class="sidebar-list" ng-if="!swarm">
<a ui-sref="docker">Docker <span class="menu-icon fa fa-cogs"></span></a>
</li>
</ul>

View File

@ -2,7 +2,7 @@
"author": "Portainer.io",
"name": "portainer",
"homepage": "http://portainer.io",
"version": "1.9.0",
"version": "1.9.1",
"repository": {
"type": "git",
"url": "git@github.com:portainer/portainer.git"

View File

@ -82,32 +82,6 @@ describe('filters', function () {
}));
});
describe('humansize', function () {
it('should return n/a when size is zero', inject(function (humansizeFilter) {
expect(humansizeFilter(0)).toBe('n/a');
}));
it('should handle Bytes values', inject(function (humansizeFilter) {
expect(humansizeFilter(512)).toBe('512 Bytes');
}));
it('should handle KB values', inject(function (humansizeFilter) {
expect(humansizeFilter(5 * 1024)).toBe('5 KB');
}));
it('should handle MB values', inject(function (humansizeFilter) {
expect(humansizeFilter(5 * 1024 * 1024)).toBe('5.0 MB');
}));
it('should handle GB values', inject(function (humansizeFilter) {
expect(humansizeFilter(5 * 1024 * 1024 * 1024)).toBe('5.00 GB');
}));
it('should handle TB values', inject(function (humansizeFilter) {
expect(humansizeFilter(5 * 1024 * 1024 * 1024 * 1024)).toBe('5.000 TB');
}));
});
describe('containername', function () {
it('should strip the leading slash from container name', inject(function (containernameFilter) {
var container = {