Merge branch 'develop' into feat2240-host-view

pull/2255/head
Chaim Lev-Ari 2018-09-27 09:50:30 +03:00
commit c780d52bcf
19 changed files with 77 additions and 80 deletions

View File

@ -77,14 +77,14 @@ The subject contains succinct description of the change:
## Contribution process ## Contribution process
Our contribution process is described below. Some of the steps can be visualized inside Github via specific `contrib/` labels, such as `contrib/func-review-in-progress` or `contrib/tech-review-approved`. Our contribution process is described below. Some of the steps can be visualized inside Github via specific `status/` labels, such as `status/1-functional-review` or `status/2-technical-review`.
### Bug report ### Bug report
![portainer_bugreport_workflow](https://user-images.githubusercontent.com/5485061/43569306-5571b3a0-9637-11e8-8559-786cfc82a14f.png) ![portainer_bugreport_workflow](https://user-images.githubusercontent.com/5485061/45727219-50190a00-bbf5-11e8-9fe8-3a563bb8d5d7.png)
### Feature request ### Feature request
The feature request process is similar to the bug report process but has an extra functional validation before the technical validation. The feature request process is similar to the bug report process but has an extra functional validation before the technical validation as well as a documentation validation before the testing phase.
![portainer_featurerequest_workflow](https://user-images.githubusercontent.com/5485061/43569315-5d30a308-9637-11e8-8292-3c62b5612925.png) ![portainer_featurerequest_workflow](https://user-images.githubusercontent.com/5485061/45727229-5ad39f00-bbf5-11e8-9550-16ba66c50615.png)

View File

@ -178,7 +178,7 @@ func (m *Migrator) Migrate() error {
} }
} }
// 1.19.2-dev // Portainer 1.19.2
if m.currentDBVersion < 14 { if m.currentDBVersion < 14 {
err := m.updateResourceControlsToDBVersion14() err := m.updateResourceControlsToDBVersion14()
if err != nil { if err != nil {

View File

@ -15,6 +15,7 @@ import (
const ( const (
errInvalidResponseStatus = portainer.Error("Invalid response status (expecting 200)") errInvalidResponseStatus = portainer.Error("Invalid response status (expecting 200)")
defaultHTTPTimeout = 5
) )
// HTTPClient represents a client to send HTTP requests. // HTTPClient represents a client to send HTTP requests.
@ -26,7 +27,7 @@ type HTTPClient struct {
func NewHTTPClient() *HTTPClient { func NewHTTPClient() *HTTPClient {
return &HTTPClient{ return &HTTPClient{
&http.Client{ &http.Client{
Timeout: time.Second * 5, Timeout: time.Second * time.Duration(defaultHTTPTimeout),
}, },
} }
} }
@ -67,10 +68,16 @@ func (client *HTTPClient) ExecuteAzureAuthenticationRequest(credentials *portain
} }
// Get executes a simple HTTP GET to the specified URL and returns // Get executes a simple HTTP GET to the specified URL and returns
// the content of the response body. // the content of the response body. Timeout can be specified via the timeout parameter,
func Get(url string) ([]byte, error) { // will default to defaultHTTPTimeout if set to 0.
func Get(url string, timeout int) ([]byte, error) {
if timeout == 0 {
timeout = defaultHTTPTimeout
}
client := &http.Client{ client := &http.Client{
Timeout: time.Second * 3, Timeout: time.Second * time.Duration(timeout),
} }
response, err := client.Get(url) response, err := client.Get(url)

View File

@ -16,7 +16,7 @@ type motdResponse struct {
func (handler *Handler) motd(w http.ResponseWriter, r *http.Request) { func (handler *Handler) motd(w http.ResponseWriter, r *http.Request) {
motd, err := client.Get(portainer.MessageOfTheDayURL) motd, err := client.Get(portainer.MessageOfTheDayURL, 0)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return

View File

@ -26,7 +26,7 @@ func (handler *Handler) templateList(w http.ResponseWriter, r *http.Request) *ht
} }
} else { } else {
var templateData []byte var templateData []byte
templateData, err = client.Get(settings.TemplatesURL) templateData, err = client.Get(settings.TemplatesURL, 0)
if err != nil { if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve external templates", err} return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve external templates", err}
} }

View File

@ -22,11 +22,13 @@ type Service struct{}
func searchUser(username string, conn *ldap.Conn, settings []portainer.LDAPSearchSettings) (string, error) { func searchUser(username string, conn *ldap.Conn, settings []portainer.LDAPSearchSettings) (string, error) {
var userDN string var userDN string
found := false found := false
usernameEscaped := ldap.EscapeFilter(username)
for _, searchSettings := range settings { for _, searchSettings := range settings {
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
searchSettings.BaseDN, searchSettings.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&%s(%s=%s))", searchSettings.Filter, searchSettings.UserNameAttribute, username), fmt.Sprintf("(&%s(%s=%s))", searchSettings.Filter, searchSettings.UserNameAttribute, usernameEscaped),
[]string{"dn"}, []string{"dn"},
nil, nil,
) )
@ -134,12 +136,13 @@ func (*Service) GetUserGroups(username string, settings *portainer.LDAPSettings)
// Get a list of group names for specified user from LDAP/AD // Get a list of group names for specified user from LDAP/AD
func getGroups(userDN string, conn *ldap.Conn, settings []portainer.LDAPGroupSearchSettings) []string { func getGroups(userDN string, conn *ldap.Conn, settings []portainer.LDAPGroupSearchSettings) []string {
groups := make([]string, 0) groups := make([]string, 0)
userDNEscaped := ldap.EscapeFilter(userDN)
for _, searchSettings := range settings { for _, searchSettings := range settings {
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
searchSettings.GroupBaseDN, searchSettings.GroupBaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&%s(%s=%s))", searchSettings.GroupFilter, searchSettings.GroupAttribute, userDN), fmt.Sprintf("(&%s(%s=%s))", searchSettings.GroupFilter, searchSettings.GroupAttribute, userDNEscaped),
[]string{"cn"}, []string{"cn"},
nil, nil,
) )

View File

@ -639,7 +639,7 @@ type (
const ( const (
// APIVersion is the version number of the Portainer API // APIVersion is the version number of the Portainer API
APIVersion = "1.19.2-dev" APIVersion = "1.20-dev"
// DBVersion is the version number of the Portainer database // DBVersion is the version number of the Portainer database
DBVersion = 14 DBVersion = 14
// MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved // MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved

View File

@ -54,7 +54,7 @@ info:
**NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8). **NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8).
version: "1.19.2-dev" version: "1.20-dev"
title: "Portainer API" title: "Portainer API"
contact: contact:
email: "info@portainer.io" email: "info@portainer.io"
@ -2816,7 +2816,7 @@ definitions:
description: "Is analytics enabled" description: "Is analytics enabled"
Version: Version:
type: "string" type: "string"
example: "1.19.2-dev" example: "1.20-dev"
description: "Portainer API version" description: "Portainer API version"
PublicSettingsInspectResponse: PublicSettingsInspectResponse:
type: "object" type: "object"

View File

@ -1,36 +1,26 @@
angular angular
.module('portainer.docker') .module('portainer.docker')
.controller('ContainerRestartPolicyController', [ .controller('ContainerRestartPolicyController', [function ContainerRestartPolicyController() {
function ContainerRestartPolicyController() { var ctrl = this;
var ctrl = this;
this.state = { this.state = {
editMode :false, editModel : {}
editModel :{} };
};
ctrl.toggleEdit = toggleEdit; ctrl.save = save;
ctrl.save = save;
function toggleEdit() { function save() {
ctrl.state.editMode = true; if (ctrl.state.editModel.name === ctrl.name && ctrl.state.editModel.maximumRetryCount === ctrl.maximumRetryCount) {
ctrl.state.editModel = { return;
name: ctrl.name,
maximumRetryCount: ctrl.maximumRetryCount
};
}
function save() {
if (ctrl.state.editModel.name === ctrl.name &&
ctrl.state.editModel.maximumRetryCount === ctrl.maximumRetryCount) {
ctrl.state.editMode = false;
return;
}
ctrl.updateRestartPolicy(ctrl.state.editModel)
.then(function onUpdateSucceed() {
ctrl.state.editMode = false;
});
}
} }
]); ctrl.updateRestartPolicy(ctrl.state.editModel);
}
this.$onInit = function() {
ctrl.state.editModel = {
name: ctrl.name ? ctrl.name : 'no',
maximumRetryCount: ctrl.maximumRetryCount
};
};
}
]);

View File

@ -1,23 +1,5 @@
<div> <div>
<table class="table table-bordered table-condensed" ng-if="!$ctrl.state.editMode"> <table class="table table-bordered table-condensed">
<tr>
<td class="col-md-3">
<a href="" data-toggle="tooltip" title="Edit restart policy" ng-click="$ctrl.toggleEdit()">
<i class="fa fa-edit"></i>
</a>
<span>Name</span>
</td>
<td>{{$ctrl.name }}</td>
</tr>
<tr ng-if="$ctrl.name === 'on-failure'">
<td class="col-md-3">Maximum Retry Count</td>
<td>
{{ $ctrl.maximumRetryCount }}
</td>
</tr>
</table>
<table class="table table-bordered table-condensed" ng-if="$ctrl.state.editMode">
<tr> <tr>
<td class="col-md-3"> <td class="col-md-3">
<span>Name</span> <span>Name</span>
@ -31,15 +13,14 @@
</select> </select>
</td> </td>
<td class="col-md-2"> <td class="col-md-2">
<button class="btn btn-success" ng-click="$ctrl.save()">Save</button> <button class="btn btn-sm btn-primary" ng-click="$ctrl.save()">Update</button>
</td> </td>
</tr> </tr>
<tr ng-if="$ctrl.state.editModel.name === 'on-failure'"> <tr ng-if="$ctrl.state.editModel.name === 'on-failure'">
<td class="col-md-3">Maximum Retry Count</td> <td class="col-md-3">Maximum Retry Count</td>
<td> <td colspan="2">
<input type="number" class="form-control" ng-model="$ctrl.state.editModel.maximumRetryCount" /> <input type="number" class="form-control" ng-model="$ctrl.state.editModel.maximumRetryCount" />
</td> </td>
<td class="col-md-2"></td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@ -65,7 +65,7 @@
</td> </td>
<td> <td>
<div class="btn-group btn-group-xs" role="group" aria-label="..." > <div class="btn-group btn-group-xs" role="group" aria-label="..." >
<a style="margin: 0 2.5px;" ui-sref="docker.containers.container.logs({ id: item.Container.Id, nodeName: item.Container.NodeName })" title="Logs" ng-if="$ctrl.agentProxy"><i class="fa fa-file-alt space-right" aria-hidden="true"></i></a> <a style="margin: 0 2.5px;" ui-sref="docker.containers.container.logs({ id: item.Container.Id, nodeName: item.Container.NodeName })" title="Logs" ng-if="$ctrl.agentProxy && item.Container.Id"><i class="fa fa-file-alt space-right" aria-hidden="true"></i></a>
<a style="margin: 0 2.5px;" ui-sref="docker.tasks.task.logs({ id: item.Id })" title="Logs" ng-if="!$ctrl.agentProxy && $ctrl.showTaskLogsButton && item.Status.State|taskhaslogs"><i class="fa fa-file-alt space-right" aria-hidden="true"></i></a> <a style="margin: 0 2.5px;" ui-sref="docker.tasks.task.logs({ id: item.Id })" title="Logs" ng-if="!$ctrl.agentProxy && $ctrl.showTaskLogsButton && item.Status.State|taskhaslogs"><i class="fa fa-file-alt space-right" aria-hidden="true"></i></a>
<a style="margin: 0 2.5px;" ui-sref="docker.containers.container.console({ id: item.Container.Id, nodeName: item.Container.NodeName })" title="Console" ng-if="$ctrl.agentProxy && item.Status.State === 'running'"><i class="fa fa-terminal space-right" aria-hidden="true"></i></a> <a style="margin: 0 2.5px;" ui-sref="docker.containers.container.console({ id: item.Container.Id, nodeName: item.Container.NodeName })" title="Console" ng-if="$ctrl.agentProxy && item.Status.State === 'running'"><i class="fa fa-terminal space-right" aria-hidden="true"></i></a>
</div> </div>

View File

@ -226,7 +226,7 @@
<tr> <tr>
<td>Restart policies</td> <td>Restart policies</td>
<td> <td>
<container-restart-policy <container-restart-policy ng-if="container"
name="container.HostConfig.RestartPolicy.Name" name="container.HostConfig.RestartPolicy.Name"
maximum-retry-count="container.HostConfig.RestartPolicy.MaximumRetryCount" maximum-retry-count="container.HostConfig.RestartPolicy.MaximumRetryCount"
update-restart-policy="updateRestartPolicy(name, maximumRetryCount)"> update-restart-policy="updateRestartPolicy(name, maximumRetryCount)">

View File

@ -323,6 +323,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co
Name: restartPolicy, Name: restartPolicy,
MaximumRetryCount: maximumRetryCount MaximumRetryCount: maximumRetryCount
}; };
Notifications.success('Restart policy updated');
} }
function notifyOnError(err) { function notifyOnError(err) {

View File

@ -1,6 +1,6 @@
angular.module('portainer.app') angular.module('portainer.app')
.controller('CreateEndpointController', ['$q', '$scope', '$state', '$filter', 'EndpointService', 'GroupService', 'TagService', 'Notifications', .controller('CreateEndpointController', ['$q', '$scope', '$state', '$filter', 'clipboard', 'EndpointService', 'GroupService', 'TagService', 'Notifications',
function ($q, $scope, $state, $filter, EndpointService, GroupService, TagService, Notifications) { function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, TagService, Notifications) {
$scope.state = { $scope.state = {
EnvironmentType: 'docker', EnvironmentType: 'docker',
@ -19,6 +19,12 @@ function ($q, $scope, $state, $filter, EndpointService, GroupService, TagService
Tags: [] Tags: []
}; };
$scope.copyAgentCommand = function() {
clipboard.copyText('curl -L https://portainer.io/download/agent-stack.yml -o agent-stack.yml && docker stack deploy --compose-file=agent-stack.yml portainer-agent');
$('#copyNotification').show();
$('#copyNotification').fadeOut(2000);
};
$scope.addDockerEndpoint = function() { $scope.addDockerEndpoint = function() {
var name = $scope.formValues.Name; var name = $scope.formValues.Name;
var URL = $filter('stripprotocol')($scope.formValues.URL); var URL = $filter('stripprotocol')($scope.formValues.URL);

View File

@ -64,8 +64,16 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<span class="col-sm-12 text-muted small"> <span class="col-sm-12 text-muted small">
If you have started Portainer in the same overlay network as the agent, you can use <code>tasks.AGENT_SERVICE_NAME:AGENT_SERVICE_PORT</code> as the Ensure that you have deployed the Portainer agent in your cluster first. You can use execute the following command on any manager node to deploy it.
endpoint URL format. <div style="margin-top: 10px;">
<code>
curl -L https://portainer.io/download/agent-stack.yml -o agent-stack.yml && docker stack deploy --compose-file=agent-stack.yml portainer-agent
</code>
<span class="btn btn-primary btn-sm space-left" ng-click="copyAgentCommand()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy</span>
<span>
<i id="copyNotification" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i>
</span>
</div>
</span> </span>
</div> </div>
</div> </div>

View File

@ -24,7 +24,7 @@ function build_archive() {
function build_all() { function build_all() {
mkdir -pv "${ARCHIVE_BUILD_FOLDER}" mkdir -pv "${ARCHIVE_BUILD_FOLDER}"
for tag in $@; do for tag in $@; do
grunt "release:`echo "$tag" | tr '-' ':'`" yarn grunt "release:`echo "$tag" | tr '-' ':'`"
name="portainer"; if [ "$(echo "$tag" | cut -c1)" = "w" ]; then name="${name}.exe"; fi name="portainer"; if [ "$(echo "$tag" | cut -c1)" = "w" ]; then name="${name}.exe"; fi
mv dist/portainer-$tag* dist/$name mv dist/portainer-$tag* dist/$name
if [ `echo $tag | cut -d \- -f 1` == 'linux' ]; then build_and_push_images "$tag"; fi if [ `echo $tag | cut -d \- -f 1` == 'linux' ]; then build_and_push_images "$tag"; fi

View File

@ -1,5 +1,5 @@
Name: portainer Name: portainer
Version: 1.19.2-dev Version: 1.20-dev
Release: 0 Release: 0
License: Zlib License: Zlib
Summary: A lightweight docker management UI Summary: A lightweight docker management UI

View File

@ -80,7 +80,7 @@ module.exports = function (grunt) {
grunt.initConfig({ grunt.initConfig({
root: 'dist', root: 'dist',
distdir: 'dist/public', distdir: 'dist/public',
shippedDockerVersion: '18.03.1-ce', shippedDockerVersion: '18.06.1-ce',
shippedDockerVersionWindows: '17.09.0-ce', shippedDockerVersionWindows: '17.09.0-ce',
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
config: gruntfile_cfg.config, config: gruntfile_cfg.config,

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.19.2-dev", "version": "1.20-dev",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git@github.com:portainer/portainer.git" "url": "git@github.com:portainer/portainer.git"
@ -20,6 +20,7 @@
} }
], ],
"scripts": { "scripts": {
"grunt": "grunt",
"dev": "yarn grunt run-dev", "dev": "yarn grunt run-dev",
"clean-all": "yarn grunt clean:all", "clean-all": "yarn grunt clean:all",
"build": "yarn grunt build", "build": "yarn grunt build",