Merge branch 'release/1.9.3'

pull/271/head 1.9.3
Anthony Lapenna 2016-10-09 10:50:46 +13:00
commit 09a5534499
20 changed files with 117 additions and 95 deletions

View File

@ -1,79 +1,47 @@
# Portainer
The easiest way to manage Docker. <p align="center">
<img title="portainer" src='http://portainer.io/images/logo_alt.png' />
</p>
[![Microbadger version](https://images.microbadger.com/badges/version/portainer/portainer.svg)](https://microbadger.com/images/portainer/portainer "Latest version on Docker Hub") [![Microbadger version](https://images.microbadger.com/badges/version/portainer/portainer.svg)](https://microbadger.com/images/portainer/portainer "Latest version on Docker Hub")
[![Microbadger](https://images.microbadger.com/badges/image/portainer/portainer.svg)](http://microbadger.com/images/portainer/portainer "Image size") [![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) [![Documentation Status](https://readthedocs.org/projects/portainer/badge/?version=latest)](http://portainer.readthedocs.io/en/latest/?badge=latest)
[![Gitter](https://badges.gitter.im/portainer/Lobby.svg)](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![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. **_Portainer_** is a lightweight management UI which allows you to **easily** manage your Docker host or Swarm cluster.
# Usage **_Portainer_** is meant to be as **simple** to deploy as it is to use. It consists of a single container that can run on any Docker for Linux engine. A Docker for Windows version is on its way !
It's really simple to deploy it using Docker: **_Portainer_** allows you to manage your Docker containers, images, volumes, networks and more ! It is compatible with the *standalone Docker* engine and with *Docker Swarm*.
```shell ## Demo
$ docker run -d -p 9000:9000 portainer/portainer -H tcp://<DOCKER_HOST>:<DOCKER_PORT>
```
Just point it at your targeted Docker host and then access Portainer by hitting [http://localhost:9000](http://localhost:9000) with a web browser. <img src="http://portainer.io/images/screenshots/portainer.gif" width="77%"/>
If your target is a Docker Swarm cluster or a Docker cluster using *swarm mode*, just add the flag `--swarm`: You can try out the public demo instance: http://demo.portainer.io/ (login with the username **demo** and the password **tryportainer**).
```shell Please note that the public demo cluster is **reset every 15min**.
$ docker run -d -p 9000:9000 portainer/portainer -H tcp://<SWARM_HOST>:<SWARM_PORT> --swarm
```
If you don't specify any target, its default behaviour is to use a bind mount on the Docker socket so you can easily deploy it to manage your local Docker host: ## Getting started
```shell * [Deploy Portainer](https://portainer.readthedocs.io/en/latest/deployment.html)
$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer * [Documentation](https://portainer.readthedocs.io)
```
Have a look at our [documentation](http://portainer.readthedocs.io/en/stable/deployment.html) for more deployment options. ## Getting help
# Configuration * Issues: https://github.com/portainer/portainer/issues
* FAQ: https://portainer.readthedocs.io/en/latest/faq.html
* Gitter (chat): https://gitter.im/portainer/Lobby
* Slack: http://portainer.io/slack/
Portainer is easy to tune using CLI flags. ## Reporting bugs and contributing
## Hiding specific containers * Want to report a bug or request a feature? Please open [an issue](https://github.com/portainer/portainer/issues/new).
* Want to help us build **_portainer_**? Follow our [contribution guidelines](https://portainer.readthedocs.io/en/latest/contribute.html) to build it locally and make a pull request. We need all the help we can get!
Portainer allows you to hide container with a specific label by using the `-l` flag. ## Limitations
For example, take a container started with the label `owner=acme`: **_Portainer_** has full support for the following Docker versions:
```shell
$ docker run -d --label owner=acme nginx
```
Simply add the `-l owner=acme` option on the CLI when starting Portainer:
```shell
$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer -l owner=acme
```
## Use your own templates
Portainer allows you to rapidly deploy containers using `App Templates`.
By default [Portainer templates](https://raw.githubusercontent.com/portainer/templates/master/templates.json) will be used but you can also define your own templates.
Add the `--templates` flag and specify the external location of your templates when starting Portainer:
```shell
$ 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 the [templates documentation](http://portainer.readthedocs.io/en/stable/templates.html).
Check our [documentation](http://portainer.readthedocs.io/en/stable/configuration.html) for more configuration options.
# FAQ
Be sure to check our [FAQ](http://portainer.readthedocs.io/en/stable/faq.html) if you are missing some information.
# Limitations
Portainer has full support for the following Docker versions:
* Docker 1.10 to Docker 1.12 (including `swarm-mode`) * Docker 1.10 to Docker 1.12 (including `swarm-mode`)
* Docker Swarm >= 1.2.3 * Docker Swarm >= 1.2.3

View File

@ -6,7 +6,7 @@ import (
// main is the entry point of the program // main is the entry point of the program
func main() { func main() {
kingpin.Version("1.9.2") kingpin.Version("1.9.3")
var ( var (
endpoint = kingpin.Flag("host", "Dockerd endpoint").Default("unix:///var/run/docker.sock").Short('H').String() 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() 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('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('CONFIG_ENDPOINT', 'settings')
.constant('TEMPLATES_ENDPOINT', 'templates') .constant('TEMPLATES_ENDPOINT', 'templates')
.constant('UI_VERSION', 'v1.9.2'); .constant('UI_VERSION', 'v1.9.3');

View File

@ -98,6 +98,9 @@
<span ng-if="container.Ports.length == 0" >-</span> <span ng-if="container.Ports.length == 0" >-</span>
</td> </td>
</tr> </tr>
<tr ng-if="!containers">
<td colspan="8" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="containers.length == 0"> <tr ng-if="containers.length == 0">
<td colspan="8" class="text-center text-muted">No containers available.</td> <td colspan="8" class="text-center text-muted">No containers available.</td>
</tr> </tr>

View File

@ -8,7 +8,6 @@ function ($scope, Container, ContainerHelper, Info, Settings, Messages, Config)
$scope.sortReverse = false; $scope.sortReverse = false;
$scope.state.selectedItemCount = 0; $scope.state.selectedItemCount = 0;
$scope.swarm_mode = false; $scope.swarm_mode = false;
$scope.containers = [];
$scope.order = function (sortType) { $scope.order = function (sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
@ -34,6 +33,10 @@ function ($scope, Container, ContainerHelper, Info, Settings, Messages, Config)
return model; return model;
}); });
$('#loadContainersSpinner').hide(); $('#loadContainersSpinner').hide();
}, function (e) {
$('#loadContainersSpinner').hide();
Messages.error("Failure", e, "Unable to retrieve containers");
$scope.containers = [];
}); });
}; };

View File

@ -107,6 +107,9 @@
<td>{{ image.VirtualSize|humansize }}</td> <td>{{ image.VirtualSize|humansize }}</td>
<td>{{ image.Created|getisodatefromtimestamp }}</td> <td>{{ image.Created|getisodatefromtimestamp }}</td>
</tr> </tr>
<tr ng-if="!images">
<td colspan="5" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="images.length == 0"> <tr ng-if="images.length == 0">
<td colspan="5" class="text-center text-muted">No images available.</td> <td colspan="5" class="text-center text-muted">No images available.</td>
</tr> </tr>

View File

@ -5,7 +5,6 @@ function ($scope, $state, Config, Image, Messages) {
$scope.sortType = 'RepoTags'; $scope.sortType = 'RepoTags';
$scope.sortReverse = true; $scope.sortReverse = true;
$scope.state.selectedItemCount = 0; $scope.state.selectedItemCount = 0;
$scope.images = [];
$scope.config = { $scope.config = {
Image: '', Image: '',
@ -98,6 +97,7 @@ function ($scope, $state, Config, Image, Messages) {
}, function (e) { }, function (e) {
$('#loadImagesSpinner').hide(); $('#loadImagesSpinner').hide();
Messages.error("Failure", e, "Unable to retrieve images"); Messages.error("Failure", e, "Unable to retrieve images");
$scope.images = [];
}); });
} }

View File

@ -131,6 +131,9 @@
<td>{{ network.IPAM.Config[0].Subnet ? network.IPAM.Config[0].Subnet : '-' }}</td> <td>{{ network.IPAM.Config[0].Subnet ? network.IPAM.Config[0].Subnet : '-' }}</td>
<td>{{ network.IPAM.Config[0].Gateway ? network.IPAM.Config[0].Gateway : '-' }}</td> <td>{{ network.IPAM.Config[0].Gateway ? network.IPAM.Config[0].Gateway : '-' }}</td>
</tr> </tr>
<tr ng-if="!networks">
<td colspan="8" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="networks.length == 0"> <tr ng-if="networks.length == 0">
<td colspan="8" class="text-center text-muted">No networks available.</td> <td colspan="8" class="text-center text-muted">No networks available.</td>
</tr> </tr>

View File

@ -6,8 +6,6 @@ function ($scope, $state, Network, Config, Messages) {
$scope.state.advancedSettings = false; $scope.state.advancedSettings = false;
$scope.sortType = 'Name'; $scope.sortType = 'Name';
$scope.sortReverse = false; $scope.sortReverse = false;
$scope.networks = [];
$scope.config = { $scope.config = {
Name: '' Name: ''
}; };
@ -93,6 +91,7 @@ function ($scope, $state, Network, Config, Messages) {
}, function (e) { }, function (e) {
$('#loadNetworksSpinner').hide(); $('#loadNetworksSpinner').hide();
Messages.error("Failure", e, "Unable to retrieve networks"); Messages.error("Failure", e, "Unable to retrieve networks");
$scope.networks = [];
}); });
} }

View File

@ -69,6 +69,12 @@
</span> </span>
</td> </td>
</tr> </tr>
<tr ng-if="!services">
<td colspan="4" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="services.length == 0">
<td colspan="4" class="text-center text-muted">No services available.</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -1,8 +1,6 @@
angular.module('services', []) angular.module('services', [])
.controller('ServicesController', ['$scope', '$stateParams', '$state', 'Service', 'ServiceHelper', 'Messages', .controller('ServicesController', ['$scope', '$stateParams', '$state', 'Service', 'ServiceHelper', 'Messages',
function ($scope, $stateParams, $state, Service, ServiceHelper, Messages) { function ($scope, $stateParams, $state, Service, ServiceHelper, Messages) {
$scope.services = [];
$scope.state = {}; $scope.state = {};
$scope.state.selectedItemCount = 0; $scope.state.selectedItemCount = 0;
$scope.sortType = 'Name'; $scope.sortType = 'Name';
@ -77,6 +75,7 @@ function ($scope, $stateParams, $state, Service, ServiceHelper, Messages) {
}, function(e) { }, function(e) {
$('#loadServicesSpinner').hide(); $('#loadServicesSpinner').hide();
Messages.error("Failure", e, "Unable to retrieve services"); Messages.error("Failure", e, "Unable to retrieve services");
$scope.services = [];
}); });
} }

View File

@ -77,11 +77,14 @@
</rd-widget-header> </rd-widget-header>
<rd-widget-body classes="padding"> <rd-widget-body classes="padding">
<div class="template-list"> <div class="template-list">
<div ng-repeat="tpl in templates" class="container-template hvr-grow" id="template_{{ $index }}" ng-click="selectTemplate($index)"> <div ng-repeat="tpl in templates" class="container-template hvr-underline-from-center" id="template_{{ $index }}" ng-click="selectTemplate($index)">
<img class="logo" ng-src="{{ tpl.logo }}" /> <img class="logo" ng-src="{{ tpl.logo }}" />
<div class="title">{{ tpl.title }}</div> <div class="title">{{ tpl.title }}</div>
<div class="description">{{ tpl.description }}</div> <div class="description">{{ tpl.description }}</div>
</div> </div>
<div ng-if="!templates" class="text-center text-muted">
Loading...
</div>
<div ng-if="templates.length == 0" class="text-center text-muted"> <div ng-if="templates.length == 0" class="text-center text-muted">
No templates available. No templates available.
</div> </div>

View File

@ -1,13 +1,11 @@
angular.module('templates', []) angular.module('templates', [])
.controller('TemplatesController', ['$scope', '$q', '$state', '$filter', 'Config', 'Info', 'Container', 'ContainerHelper', 'Image', 'Volume', 'Network', 'Templates', 'Messages', .controller('TemplatesController', ['$scope', '$q', '$state', '$filter', 'Config', 'Info', 'Container', 'ContainerHelper', 'Image', 'Volume', 'Network', 'Templates', 'Messages',
function ($scope, $q, $state, $filter, Config, Info, Container, ContainerHelper, Image, Volume, Network, Templates, Messages) { function ($scope, $q, $state, $filter, Config, Info, Container, ContainerHelper, Image, Volume, Network, Templates, Messages) {
$scope.templates = [];
$scope.selectedTemplate = null; $scope.selectedTemplate = null;
$scope.formValues = { $scope.formValues = {
network: "", network: "",
name: "" name: ""
}; };
$scope.templates = [];
var selectedItem = -1; var selectedItem = -1;
@ -161,6 +159,7 @@ function ($scope, $q, $state, $filter, Config, Info, Container, ContainerHelper,
}, function (e) { }, function (e) {
$('#loadTemplatesSpinner').hide(); $('#loadTemplatesSpinner').hide();
Messages.error("Failure", e, "Unable to retrieve apps list"); Messages.error("Failure", e, "Unable to retrieve apps list");
$scope.templates = [];
}); });
} }

View File

@ -59,6 +59,9 @@
<td>{{ volume.Driver }}</td> <td>{{ volume.Driver }}</td>
<td>{{ volume.Mountpoint }}</td> <td>{{ volume.Mountpoint }}</td>
</tr> </tr>
<tr ng-if="!volumes">
<td colspan="4" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="volumes.length == 0"> <tr ng-if="volumes.length == 0">
<td colspan="4" class="text-center text-muted">No volumes available.</td> <td colspan="4" class="text-center text-muted">No volumes available.</td>
</tr> </tr>

View File

@ -5,8 +5,6 @@ function ($scope, $state, Volume, Messages) {
$scope.state.selectedItemCount = 0; $scope.state.selectedItemCount = 0;
$scope.sortType = 'Name'; $scope.sortType = 'Name';
$scope.sortReverse = true; $scope.sortReverse = true;
$scope.volumes = [];
$scope.config = { $scope.config = {
Name: '' Name: ''
}; };
@ -61,6 +59,7 @@ function ($scope, $state, Volume, Messages) {
}, function (e) { }, function (e) {
$('#loadVolumesSpinner').hide(); $('#loadVolumesSpinner').hide();
Messages.error("Failure", e, "Unable to retrieve volumes"); Messages.error("Failure", e, "Unable to retrieve volumes");
$scope.volumes = [];
}); });
} }
fetchVolumes(); fetchVolumes();

View File

@ -8,7 +8,7 @@ angular
icon: '=' icon: '='
}, },
transclude: true, 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>', template: '<div class="widget-header"><div class="row"><span class="pull-left"><img class="custom-header-ico" ng-src="{{icon}}"></img> <span class="text-muted"> {{title}} </span> </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

@ -217,16 +217,67 @@ input[type="radio"] {
} }
.custom-header-ico { .custom-header-ico {
max-width: 16px; max-width: 32px;
max-height: 16px; max-height: 32px;
margin-right: 2px;
}
.btn-responsive {
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
@media screen and (min-width: 1107px) {
.btn-responsive {
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
border-radius: 4px;
}
}
/* Underline From Center */
.hvr-underline-from-center {
display: inline-block;
vertical-align: middle;
-webkit-transform: translateZ(0);
transform: translateZ(0);
box-shadow: 0 0 1px rgba(0, 0, 0, 0);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-moz-osx-font-smoothing: grayscale;
position: relative;
overflow: hidden;
}
.hvr-underline-from-center:before {
content: "";
position: absolute;
z-index: -1;
left: 50%;
right: 50%;
bottom: 0;
background: #85898b;
height: 2px;
-webkit-transition-property: left, right;
transition-property: left, right;
-webkit-transition-duration: 0.3s;
transition-duration: 0.3s;
-webkit-transition-timing-function: ease-out;
transition-timing-function: ease-out;
}
.hvr-underline-from-center:hover:before, .hvr-underline-from-center:focus:before, .hvr-underline-from-center:active:before {
left: 0;
right: 0;
} }
.container-template { .container-template {
font-size: 1em; font-size: 1em;
width: 256px; width: 256px;
height: 128px; height: 128px;
margin: 10px; margin: 15px;
padding: 10px; padding: 5px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
@ -259,20 +310,5 @@ input[type="radio"] {
.container-template .description { .container-template .description {
text-align: center; text-align: center;
font-size: 0.8em; font-size: 0.8em;
} margin-bottom: 5px;
.btn-responsive {
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
@media screen and (min-width: 1107px) {
.btn-responsive {
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
border-radius: 4px;
}
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "portainer", "name": "portainer",
"version": "1.9.2", "version": "1.9.3",
"homepage": "https://github.com/portainer/portainer", "homepage": "https://github.com/portainer/portainer",
"authors": [ "authors": [
"Anthony Lapenna <anthony.lapenna at gmail dot com>" "Anthony Lapenna <anthony.lapenna at gmail dot com>"
@ -35,7 +35,6 @@
"bootstrap": "~3.3.6", "bootstrap": "~3.3.6",
"font-awesome": "~4.6.3", "font-awesome": "~4.6.3",
"filesize": "~3.3.0", "filesize": "~3.3.0",
"Hover": "2.0.2",
"jquery": "1.11.1", "jquery": "1.11.1",
"jquery.gritter": "1.7.4", "jquery.gritter": "1.7.4",
"lodash": "4.12.0", "lodash": "4.12.0",

View File

@ -88,8 +88,7 @@ module.exports = function (grunt) {
'bower_components/font-awesome/css/font-awesome.min.css', 'bower_components/font-awesome/css/font-awesome.min.css',
'bower_components/rdash-ui/dist/css/rdash.min.css', 'bower_components/rdash-ui/dist/css/rdash.min.css',
'bower_components/angular-ui-select/dist/select.min.css', 'bower_components/angular-ui-select/dist/select.min.css',
'bower_components/xterm.js/dist/xterm.css', 'bower_components/xterm.js/dist/xterm.css'
'bower_components/Hover/css/hover-min.css'
] ]
}, },
clean: { clean: {

View File

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