Merge branch 'release/1.23.1'

pull/3571/head 1.23.1
Anthony Lapenna 2020-02-18 13:46:10 +13:00
commit bcda7e2d7e
60 changed files with 634 additions and 162 deletions

View File

@ -26,6 +26,10 @@ A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Portainer Logs**
Provide the logs of your Portainer container or Service.
You can see how [here](https://portainer.readthedocs.io/en/stable/faq.html#how-do-i-get-the-logs-from-portainer)
**Steps to reproduce the issue:**
1. Go to '...'
2. Click on '....'

View File

@ -259,6 +259,7 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
LogoURL: *flags.Logo,
AuthenticationMethod: portainer.AuthenticationInternal,
LDAPSettings: portainer.LDAPSettings{
AnonymousMode: true,
AutoCreateUsers: true,
TLSConfig: portainer.TLSConfiguration{},
SearchSettings: []portainer.LDAPSearchSettings{

View File

@ -2,7 +2,7 @@ package cron
import (
"github.com/portainer/portainer/api"
"github.com/robfig/cron"
"github.com/robfig/cron/v3"
)
// JobScheduler represents a service for managing crons

View File

@ -7,14 +7,14 @@ import (
"time"
"github.com/docker/docker/client"
"github.com/portainer/portainer/api"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/crypto"
)
const (
unsupportedEnvironmentType = portainer.Error("Environment not supported")
defaultDockerRequestTimeout = 60
dockerClientVersion = "1.40"
dockerClientVersion = "1.37"
)
// ClientFactory is used to create Docker clients

View File

@ -3,6 +3,7 @@ package docker
import (
"context"
"log"
"strings"
"time"
"github.com/docker/docker/api/types"
@ -126,6 +127,8 @@ func snapshotContainers(snapshot *portainer.Snapshot, cli *client.Client) error
runningContainers := 0
stoppedContainers := 0
healthyContainers := 0
unhealthyContainers := 0
stacks := make(map[string]struct{})
for _, container := range containers {
if container.State == "exited" {
@ -134,6 +137,12 @@ func snapshotContainers(snapshot *portainer.Snapshot, cli *client.Client) error
runningContainers++
}
if strings.Contains(container.Status, "(healthy)") {
healthyContainers++
} else if strings.Contains(container.Status, "(unhealthy)") {
unhealthyContainers++
}
for k, v := range container.Labels {
if k == "com.docker.compose.project" {
stacks[v] = struct{}{}
@ -143,6 +152,8 @@ func snapshotContainers(snapshot *portainer.Snapshot, cli *client.Client) error
snapshot.RunningContainerCount = runningContainers
snapshot.StoppedContainerCount = stoppedContainers
snapshot.HealthyContainerCount = healthyContainers
snapshot.UnhealthyContainerCount = unhealthyContainers
snapshot.StackCount += len(stacks)
snapshot.SnapshotRaw.Containers = containers
return nil

40
api/go.mod Normal file
View File

@ -0,0 +1,40 @@
module github.com/portainer/portainer/api
go 1.13
require (
github.com/Microsoft/go-winio v0.3.8
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
github.com/boltdb/bolt v1.3.1
github.com/containerd/containerd v1.3.1 // indirect
github.com/coreos/go-semver v0.3.0
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/docker/cli v0.0.0-20191126203649-54d085b857e9
github.com/docker/docker v0.0.0-00010101000000-000000000000
github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814
github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/mux v1.7.3
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/websocket v1.4.1
github.com/imdario/mergo v0.3.8 // indirect
github.com/jpillora/chisel v0.0.0-20190724232113-f3a8df20e389
github.com/json-iterator/go v1.1.8
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
github.com/mattn/go-shellwords v1.0.6 // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
github.com/portainer/libcompose v0.5.3
github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33
github.com/robfig/cron/v3 v3.0.0
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/asn1-ber.v1 v1.0.0-00010101000000-000000000000 // indirect
gopkg.in/ldap.v2 v2.5.1
gopkg.in/src-d/go-git.v4 v4.13.1
)
replace github.com/docker/docker => github.com/docker/engine v1.4.2-0.20191127222017-3152f9436292
replace gopkg.in/asn1-ber.v1 => github.com/go-asn1-ber/asn1-ber v1.3.1

273
api/go.sum Normal file
View File

@ -0,0 +1,273 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.3.8 h1:dvxbxtpTIjdAbx2OtL26p4eq0iEvys/U5yrsTJb3NZI=
github.com/Microsoft/go-winio v0.3.8/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZOs7ygH5BgQp4N+aYrZ2DNpWZ1KG3VOSOM=
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/containerd/containerd v1.3.1 h1:LdbWxLhkAIxGO7h3mATHkyav06WuDs/yTWxIljJOTks=
github.com/containerd/containerd v1.3.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 h1:74lLNRzvsdIlkTgfDSMuaPjBr4cf6k7pwQQANm/yLKU=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/docker/cli v0.0.0-20190711175710-5b38d82aa076/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v0.0.0-20191126203649-54d085b857e9 h1:Q6D6b2iRKhvtL3Wj9p0SyPOvUDJ1ht62mbiBoNJ3Aus=
github.com/docker/cli v0.0.0-20191126203649-54d085b857e9/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/engine v1.4.2-0.20191127222017-3152f9436292 h1:qQ7mw+CVWpRj5DWBL4CVHtBbGQdlPCj4j1evDh0ethw=
github.com/docker/engine v1.4.2-0.20191127222017-3152f9436292/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY=
github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o=
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 h1:X0fj836zx99zFu83v/M79DuBn84IL/Syx1SY6Y5ZEMA=
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814 h1:gWvniJ4GbFfkf700kykAImbLiEMU0Q3QN9hQ26Js1pU=
github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814/go.mod h1:secRm32Ro77eD23BmPVbgLbWN+JWDw7pJszenjxI4bI=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v0.0.0-20160317213430-0eeaf8392f5b/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669 h1:l5rH/CnVVu+HPxjtxjM90nHrm4nov3j3RF9/62UjgLs=
github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669/go.mod h1:kOeLNvjNBGSV3uYtFjvb72+fnZCMFJF1XDvRIjdom0g=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/jpillora/chisel v0.0.0-20190724232113-f3a8df20e389 h1:K3JsoRqX6C4gmTvY4jqtFGCfK8uToj9DMahciJaoWwE=
github.com/jpillora/chisel v0.0.0-20190724232113-f3a8df20e389/go.mod h1:wHQUFFnFySoqdAOzjHkTvb4DsVM1h/73PS9l2vnioRM=
github.com/jpillora/requestlog v0.0.0-20181015073026-df8817be5f82 h1:7ufdyC3aMxFcCv+ABZy/dmIVGKFoGNBCqOgLYPIckD8=
github.com/jpillora/requestlog v0.0.0-20181015073026-df8817be5f82/go.mod h1:w8buj+yNfmLEP0ENlbG/FRnK6bVmuhqXnukYCs9sDvY=
github.com/jpillora/sizestr v0.0.0-20160130011556-e2ea2fa42fb9 h1:0c9jcgBtHRtDU//jTrcCgWG6UHjMZytiq/3WhraNgUM=
github.com/jpillora/sizestr v0.0.0-20160130011556-e2ea2fa42fb9/go.mod h1:1ffp+CRe0eAwwRb0/BownUAjMBsmTLwgAvRbfj9dRwE=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c h1:N7A4JCA2G+j5fuFxCsJqjFU/sZe0mj8H0sSoSwbaikw=
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c/go.mod h1:Nn5wlyECw3iJrzi0AhIWg+AJUb4PlRQVW4/3XHH1LZA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v0.0.0-20150511174710-5cf931ef8f76/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI=
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420 h1:Yu3681ykYHDfLoI6XVjL4JWmkE+3TX9yfIWwRCh1kFM=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v0.0.0-20170515205857-f03dbe35d449 h1:Aq8iG72akPb/kszE7ksZ5ldV+JYPYii/KZOxlpJF07s=
github.com/opencontainers/image-spec v0.0.0-20170515205857-f03dbe35d449/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.0.0-20161109192122-51371867a01c h1:iOMba/KmaXgSX5PFKu1u6s+DZXiq+EzPayawa76w6aA=
github.com/opencontainers/runc v0.0.0-20161109192122-51371867a01c/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw=
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/portainer/libcompose v0.5.3 h1:tE4WcPuGvo+NKeDkDWpwNavNLZ5GHIJ4RvuZXsI9uI8=
github.com/portainer/libcompose v0.5.3/go.mod h1:7SKd/ho69rRKHDFSDUwkbMcol2TMKU5OslDsajr8Ro8=
github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2 h1:0PfgGLys9yHr4rtnirg0W0Cjvv6/DzxBIZk5sV59208=
github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2/go.mod h1:/wIeGwJOMYc1JplE/OvYMO5korce39HddIfI8VKGyAM=
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33 h1:H8HR2dHdBf8HANSkUyVw4o8+4tegGcd+zyKZ3e599II=
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33/go.mod h1:Y2TfgviWI4rT2qaOTHr+hq6MdKIE5YjgQAu7qwptTV0=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg=
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1 h1:anGSYQpPhQwXlwsu5wmfq0nWkCNaMEMUwAv13Y92hd8=
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -69,11 +69,16 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
}
if payload.LDAPSettings != nil {
ldapReaderDN := settings.LDAPSettings.ReaderDN
ldapPassword := settings.LDAPSettings.Password
if payload.LDAPSettings.ReaderDN != "" {
ldapReaderDN = payload.LDAPSettings.ReaderDN
}
if payload.LDAPSettings.Password != "" {
ldapPassword = payload.LDAPSettings.Password
}
settings.LDAPSettings = *payload.LDAPSettings
settings.LDAPSettings.ReaderDN = ldapReaderDN
settings.LDAPSettings.Password = ldapPassword
}

View File

@ -4,11 +4,11 @@ import (
"net/http"
"github.com/asaskevich/govalidator"
"github.com/gofrs/uuid"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
"github.com/satori/go.uuid"
)
type webhookCreatePayload struct {

View File

@ -6,7 +6,7 @@ import (
"github.com/docker/docker/client"
"github.com/portainer/portainer/api"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
)
@ -74,8 +74,8 @@ func (transport *Transport) configInspectOperation(response *http.Response, exec
// selectorConfigLabels retrieve the labels object associated to the config object.
// Labels are available under the "Spec.Labels" property.
// API schema references:
// https://docs.docker.com/engine/api/v1.40/#operation/ConfigList
// https://docs.docker.com/engine/api/v1.40/#operation/ConfigInspect
// https://docs.docker.com/engine/api/v1.37/#operation/ConfigList
// https://docs.docker.com/engine/api/v1.37/#operation/ConfigInspect
func selectorConfigLabels(responseObject map[string]interface{}) map[string]interface{} {
secretSpec := responseutils.GetJSONObject(responseObject, "Spec")
if secretSpec != nil {

View File

@ -5,7 +5,7 @@ import (
"net/http"
"github.com/docker/docker/client"
"github.com/portainer/portainer/api"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
)
@ -19,16 +19,19 @@ func getInheritedResourceControlFromContainerLabels(dockerClient *client.Client,
return nil, err
}
serviceName := container.Config.Labels[resourceLabelForDockerServiceID]
if serviceName != "" {
serviceResourceControl := portainer.GetResourceControlByResourceIDAndType(serviceName, portainer.ServiceResourceControl, resourceControls)
if serviceResourceControl != nil {
return serviceResourceControl, nil
}
}
swarmStackName := container.Config.Labels[resourceLabelForDockerSwarmStackName]
if swarmStackName != "" {
return portainer.GetResourceControlByResourceIDAndType(swarmStackName, portainer.StackResourceControl, resourceControls), nil
}
serviceName := container.Config.Labels[resourceLabelForDockerServiceID]
if serviceName != "" {
return portainer.GetResourceControlByResourceIDAndType(serviceName, portainer.ServiceResourceControl, resourceControls), nil
}
composeStackName := container.Config.Labels[resourceLabelForDockerComposeStackName]
if composeStackName != "" {
return portainer.GetResourceControlByResourceIDAndType(composeStackName, portainer.StackResourceControl, resourceControls), nil

View File

@ -6,9 +6,8 @@ import (
"github.com/docker/docker/client"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
"github.com/portainer/portainer/api"
)
const (
@ -75,8 +74,8 @@ func (transport *Transport) secretInspectOperation(response *http.Response, exec
// selectorSecretLabels retrieve the labels object associated to the secret object.
// Labels are available under the "Spec.Labels" property.
// API schema references:
// https://docs.docker.com/engine/api/v1.40/#operation/SecretList
// https://docs.docker.com/engine/api/v1.40/#operation/SecretInspect
// https://docs.docker.com/engine/api/v1.37/#operation/SecretList
// https://docs.docker.com/engine/api/v1.37/#operation/SecretInspect
func selectorSecretLabels(responseObject map[string]interface{}) map[string]interface{} {
secretSpec := responseutils.GetJSONObject(responseObject, "Spec")
if secretSpec != nil {

View File

@ -13,7 +13,7 @@ import (
"github.com/portainer/portainer/api/docker"
"github.com/docker/docker/client"
"github.com/portainer/portainer/api"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
"github.com/portainer/portainer/api/http/security"
)
@ -549,12 +549,12 @@ func (transport *Transport) interceptAndRewriteRequest(request *http.Request, op
// on the resourceIdentifierAttribute parameter then generate a new resource control associated to that resource
// with a random token and rewrites the response by decorating the original response with a ResourceControl object.
// The generic Docker API response format is JSON object:
// https://docs.docker.com/engine/api/v1.40/#operation/ContainerCreate
// https://docs.docker.com/engine/api/v1.40/#operation/NetworkCreate
// https://docs.docker.com/engine/api/v1.40/#operation/VolumeCreate
// https://docs.docker.com/engine/api/v1.40/#operation/ServiceCreate
// https://docs.docker.com/engine/api/v1.40/#operation/SecretCreate
// https://docs.docker.com/engine/api/v1.40/#operation/ConfigCreate
// https://docs.docker.com/engine/api/v1.37/#operation/ContainerCreate
// https://docs.docker.com/engine/api/v1.37/#operation/NetworkCreate
// https://docs.docker.com/engine/api/v1.37/#operation/VolumeCreate
// https://docs.docker.com/engine/api/v1.37/#operation/ServiceCreate
// https://docs.docker.com/engine/api/v1.37/#operation/SecretCreate
// https://docs.docker.com/engine/api/v1.37/#operation/ConfigCreate
func (transport *Transport) decorateGenericResourceCreationResponse(response *http.Response, resourceIdentifierAttribute string, resourceType portainer.ResourceControlType, userID portainer.UserID) error {
responseObject, err := responseutils.GetResponseAsJSONOBject(response)
if err != nil {

View File

@ -4,7 +4,7 @@ import (
"fmt"
"strings"
"github.com/portainer/portainer/api"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/crypto"
"gopkg.in/ldap.v2"
@ -92,9 +92,11 @@ func (*Service) AuthenticateUser(username, password string, settings *portainer.
}
defer connection.Close()
err = connection.Bind(settings.ReaderDN, settings.Password)
if err != nil {
return err
if !settings.AnonymousMode {
err = connection.Bind(settings.ReaderDN, settings.Password)
if err != nil {
return err
}
}
userDN, err := searchUser(username, connection, settings.SearchSettings)
@ -118,9 +120,11 @@ func (*Service) GetUserGroups(username string, settings *portainer.LDAPSettings)
}
defer connection.Close()
err = connection.Bind(settings.ReaderDN, settings.Password)
if err != nil {
return nil, err
if !settings.AnonymousMode {
err = connection.Bind(settings.ReaderDN, settings.Password)
if err != nil {
return nil, err
}
}
userDN, err := searchUser(username, connection, settings.SearchSettings)

View File

@ -50,6 +50,7 @@ type (
// LDAPSettings represents the settings used to connect to a LDAP server
LDAPSettings struct {
AnonymousMode bool `json:"AnonymousMode"`
ReaderDN string `json:"ReaderDN"`
Password string `json:"Password,omitempty"`
URL string `json:"URL"`
@ -389,18 +390,20 @@ type (
// Snapshot represents a snapshot of a specific endpoint at a specific time
Snapshot struct {
Time int64 `json:"Time"`
DockerVersion string `json:"DockerVersion"`
Swarm bool `json:"Swarm"`
TotalCPU int `json:"TotalCPU"`
TotalMemory int64 `json:"TotalMemory"`
RunningContainerCount int `json:"RunningContainerCount"`
StoppedContainerCount int `json:"StoppedContainerCount"`
VolumeCount int `json:"VolumeCount"`
ImageCount int `json:"ImageCount"`
ServiceCount int `json:"ServiceCount"`
StackCount int `json:"StackCount"`
SnapshotRaw SnapshotRaw `json:"SnapshotRaw"`
Time int64 `json:"Time"`
DockerVersion string `json:"DockerVersion"`
Swarm bool `json:"Swarm"`
TotalCPU int `json:"TotalCPU"`
TotalMemory int64 `json:"TotalMemory"`
RunningContainerCount int `json:"RunningContainerCount"`
StoppedContainerCount int `json:"StoppedContainerCount"`
HealthyContainerCount int `json:"HealthyContainerCount"`
UnhealthyContainerCount int `json:"UnhealthyContainerCount"`
VolumeCount int `json:"VolumeCount"`
ImageCount int `json:"ImageCount"`
ServiceCount int `json:"ServiceCount"`
StackCount int `json:"StackCount"`
SnapshotRaw SnapshotRaw `json:"SnapshotRaw"`
}
// SnapshotRaw represents all the information related to a snapshot as returned by the Docker API
@ -914,7 +917,7 @@ type (
const (
// APIVersion is the version number of the Portainer API
APIVersion = "1.23.0"
APIVersion = "1.23.1"
// DBVersion is the version number of the Portainer database
DBVersion = 22
// AssetsServerURL represents the URL of the Portainer asset server

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).
version: "1.23.0"
version: "1.23.1"
title: "Portainer API"
contact:
email: "info@portainer.io"
@ -3174,7 +3174,7 @@ definitions:
description: "Is analytics enabled"
Version:
type: "string"
example: "1.23.0"
example: "1.23.1"
description: "Portainer API version"
PublicSettingsInspectResponse:
type: "object"
@ -3296,6 +3296,10 @@ definitions:
LDAPSettings:
type: "object"
properties:
AnonymousMode:
type: "boolean"
example: true
description: "Enable this option if the server is configured for Anonymous access. When enabled, ReaderDN and Password will not be used."
ReaderDN:
type: "string"
example: "cn=readonly-account,dc=ldap,dc=domain,dc=tld"

View File

@ -1,5 +1,5 @@
{
"packageName": "portainer",
"packageVersion": "1.23.0",
"packageVersion": "1.23.1",
"projectName": "portainer"
}

View File

@ -33,7 +33,7 @@
<th>IP Address</th>
<th>Gateway</th>
<th>MAC Address</th>
<th>Actions</th>
<th authorization="DockerNetworkDisconnect">Actions</th>
</tr>
</thead>
<tbody>

View File

@ -1,9 +1,9 @@
<td ng-if="allowCheckbox">
<span class="md-checkbox" ng-if="!parentCtrl.offlineMode">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="parentCtrl.selectItem(item)" ng-disabled="parentCtrl.disableRemove(item)"/>
<span class="md-checkbox" ng-if="!parentCtrl.offlineMode" authorization="DockerNetworkDelete, DockerNetworkCreate">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="parentCtrl.selectItem(item, $event); $event.stopPropagation()" ng-disabled="parentCtrl.disableRemove(item)"/>
<label for="select_{{ $index }}"></label>
</span>
<a ng-if="parentCtrl.itemCanExpand(item)" ng-click="parentCtrl.expandItem(item, !item.Expanded)"><i ng-class="{ 'fas fa-angle-down': item.Expanded, 'fas fa-angle-right': !item.Expanded }" class="space-right" aria-hidden="true"></i></a>
<a ng-if="parentCtrl.itemCanExpand(item)"><i ng-class="{ 'fas fa-angle-down': item.Expanded, 'fas fa-angle-right': !item.Expanded }" class="space-right" aria-hidden="true"></i></a>
</td>
<td ng-if="!allowCheckbox"></td>
<td>

View File

@ -62,7 +62,7 @@
<thead>
<tr>
<th style="width:55px;">
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
<span class="md-checkbox" ng-if="!$ctrl.offlineMode" authorization="DockerNetworkDelete, DockerNetworkCreate">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
@ -151,7 +151,7 @@
</thead>
<tbody>
<tr dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
ng-class="{active: item.Checked}" network-row-content item="item" parent-ctrl="$ctrl" allow-checkbox="true">
ng-class="{active: item.Checked}" network-row-content item="item" parent-ctrl="$ctrl" ng-click="$ctrl.expandItem(item, !item.Expanded)" allow-checkbox="true">
</tr>
<tr dir-paginate-end ng-show="item.Expanded" ng-repeat="it in item.Subs" style="background: #d5e8f3;"
network-row-content item="it" parent-ctrl="$ctrl">

View File

@ -122,7 +122,7 @@
<tr ng-click="$ctrl.expandItem(item, !item.Expanded)" dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}" class="interactive">
<td>
<span class="md-checkbox" authorization="DockerServiceUpdate, DockerServiceDelete, DockerServiceCreate">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)"/>
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event); $event.stopPropagation()"/>
<label for="select_{{ $index }}"></label>
</span>
<a><i ng-class="{ 'fas fa-angle-down': item.Expanded, 'fas fa-angle-right': !item.Expanded }" class="space-right" aria-hidden="true"></i></a>

View File

@ -248,6 +248,22 @@ angular.module('portainer.docker')
}).length;
};
})
.filter('healthycontainers', function () {
'use strict';
return function healthyContainersFilter(containers) {
return containers.filter(function (container) {
return container.Status === 'healthy';
}).length;
}
})
.filter('unhealthycontainers', function () {
'use strict';
return function unhealthyContainersFilter(containers) {
return containers.filter(function (container) {
return container.Status === 'unhealthy';
}).length;
}
})
.filter('imagestotalsize', function () {
'use strict';
return function (images) {

View File

@ -1,5 +1,11 @@
import { ResourceControlViewModel } from 'Portainer/models/resourceControl/resourceControl';
function b64DecodeUnicode(str) {
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
export function ConfigViewModel(data) {
this.Id = data.ID;
this.CreatedAt = data.CreatedAt;
@ -7,7 +13,7 @@ export function ConfigViewModel(data) {
this.Version = data.Version.Index;
this.Name = data.Spec.Name;
this.Labels = data.Spec.Labels;
this.Data = atob(data.Spec.Data);
this.Data = b64DecodeUnicode(data.Spec.Data);
if (data.Portainer && data.Portainer.ResourceControl) {
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);

View File

@ -21,6 +21,8 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container
MacAddress: '',
IPv4: '',
IPv6: '',
DnsPrimary: '',
DnsSecondary: '',
AccessControlData: new AccessControlFormData(),
CpuLimit: 0,
MemoryLimit: 0,
@ -202,19 +204,28 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container
}
config.HostConfig.NetworkMode = networkMode;
config.MacAddress = $scope.formValues.MacAddress;
config.NetworkingConfig.EndpointsConfig[networkMode] = {
IPAMConfig: {
IPv4Address: $scope.formValues.IPv4,
IPv6Address: $scope.formValues.IPv6
}
};
if (networkMode && _.get($scope.config.NetworkingConfig.EndpointsConfig[networkMode], 'Aliases')){
var aliases = $scope.config.NetworkingConfig.EndpointsConfig[networkMode].Aliases;
config.NetworkingConfig.EndpointsConfig[networkMode].Aliases = _.filter(aliases, (o) => { return !_.startsWith($scope.fromContainer.Id,o)});
}
var dnsServers = [];
if ($scope.formValues.DnsPrimary) {
dnsServers.push($scope.formValues.DnsPrimary);
}
if ($scope.formValues.DnsSecondary) {
dnsServers.push($scope.formValues.DnsSecondary);
}
config.HostConfig.Dns = dnsServers;
$scope.formValues.ExtraHosts.forEach(function (v) {
if (v.value) {
config.HostConfig.ExtraHosts.push(v.value);
@ -312,7 +323,7 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container
return config;
}
function loadFromContainerCmd() {
if ($scope.config.Cmd) {
$scope.config.Cmd = ContainerHelper.commandArrayToString($scope.config.Cmd);
@ -387,6 +398,13 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container
}
$scope.formValues.MacAddress = d.Config.MacAddress;
if (d.HostConfig.Dns && d.HostConfig.Dns[0]) {
$scope.formValues.DnsPrimary = d.HostConfig.Dns[0];
if (d.HostConfig.Dns[1]) {
$scope.formValues.DnsSecondary = d.HostConfig.Dns[1];
}
}
// ExtraHosts
if ($scope.config.HostConfig.ExtraHosts) {
var extraHosts = $scope.config.HostConfig.ExtraHosts;

View File

@ -435,6 +435,22 @@
</div>
</div>
<!-- !ipv6-input -->
<!-- dns-primary-input -->
<div class="form-group">
<label for="container_dns_primary" class="col-sm-2 col-lg-1 control-label text-left">Primary DNS Server</label>
<div class="col-sm-9">
<input type="text" class="form-control" ng-model="formValues.DnsPrimary" id="container_dns_primary" placeholder="e.g. 1.1.1.1">
</div>
</div>
<!-- !dns-primary-input -->
<!-- dns-secondary-input -->
<div class="form-group">
<label for="container_dns_secondary" class="col-sm-2 col-lg-1 control-label text-left">Secondary DNS Server</label>
<div class="col-sm-9">
<input type="text" class="form-control" ng-model="formValues.DnsSecondary" id="container_dns_secondary" placeholder="e.g. 1.0.0.1">
</div>
</div>
<!-- !dns-secondary-input -->
<!-- extra-hosts-variables -->
<div class="form-group">
<div class="col-sm-12" style="margin-top: 5px;">

View File

@ -109,9 +109,13 @@
<div class="widget-icon blue pull-left">
<i class="fa fa-server"></i>
</div>
<div class="pull-right">
<div><i class="fa fa-heartbeat space-right green-icon"></i>{{ containers | runningcontainers }} running</div>
<div><i class="fa fa-heartbeat space-right red-icon"></i>{{ containers | stoppedcontainers }} stopped</div>
<div class="pull-right"style="padding-left: 5px;">
<div><i class="fa fa-power-off space-right green-icon"></i>{{ containers | runningcontainers }} running</div>
<div><i class="fa fa-power-off space-right red-icon"></i>{{ containers | stoppedcontainers }} stopped</div>
</div>
<div class="pull-right" style="padding-right: 5px;">
<div><i class="fa fa-heartbeat space-right green-icon"></i>{{ containers | healthycontainers }} healthy</div>
<div><i class="fa fa-heartbeat space-right orange-icon"></i>{{ containers | unhealthycontainers }} unhealthy</div>
</div>
<div class="title">{{ containers.length }}</div>
<div class="comment">{{ containers.length === 1 ? 'Container' : 'Containers' }}</div>

View File

@ -27,7 +27,7 @@
<tr ng-repeat="config in service.ServiceConfigs">
<td><a ui-sref="docker.configs.config({id: config.Id})">{{ config.Name }}</a></td>
<td>
<input class="form-control" ng-model="config.FileName" ng-change="updateConfig(service)" placeholder="e.g. /path/in/container" required />
<input class="form-control" ng-model="config.FileName" ng-change="updateConfig(service)" placeholder="e.g. /path/in/container" required disable-authorization="DockerServiceUpdate"/>
</td>
<td>{{ config.Uid }}</td>
<td>{{ config.Gid }}</td>

View File

@ -23,12 +23,12 @@
<tr ng-repeat="constraint in service.ServiceConstraints">
<td>
<div class="input-group input-group-sm">
<input type="text" class="form-control" ng-model="constraint.key" placeholder="e.g. node.role" ng-change="updatePlacementConstraint(service, constraint)" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="constraint.key" placeholder="e.g. node.role" ng-change="updatePlacementConstraint(service, constraint)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
</div>
</td>
<td>
<div class="input-group input-group-sm">
<select name="constraintOperator" class="form-control" ng-model="constraint.operator" ng-change="updatePlacementConstraint(service, constraint)" ng-disabled="isUpdating">
<select name="constraintOperator" class="form-control" ng-model="constraint.operator" ng-change="updatePlacementConstraint(service, constraint)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
<option value="==">==</option>
<option value="!=">!=</option>
</select>
@ -36,7 +36,7 @@
</td>
<td>
<div class="input-group input-group-sm">
<input type="text" class="form-control" ng-model="constraint.value" placeholder="e.g. manager" ng-change="updatePlacementConstraint(service, constraint)" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="constraint.value" placeholder="e.g. manager" ng-change="updatePlacementConstraint(service, constraint)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removePlacementConstraint(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>

View File

@ -23,13 +23,13 @@
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon fit-text-size">name</span>
<input type="text" class="form-control" ng-model="label.key" placeholder="e.g. com.example.foo" ng-change="updateContainerLabel(service, label)" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="label.key" placeholder="e.g. com.example.foo" ng-change="updateContainerLabel(service, label)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
</div>
</td>
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon fit-text-size">value</span>
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" ng-change="updateContainerLabel(service, label)" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" ng-change="updateContainerLabel(service, label)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeContainerLabel(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>

View File

@ -29,7 +29,7 @@
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon fit-text-size">value</span>
<input type="text" class="form-control" ng-model="var.value" ng-change="updateEnvironmentVariable(service, var)" placeholder="e.g. bar" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="var.value" ng-change="updateEnvironmentVariable(service, var)" placeholder="e.g. bar" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeEnvironmentVariable(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>

View File

@ -22,12 +22,12 @@
<tr ng-repeat="entry in service.Hosts">
<td>
<div class="input-group input-group-sm">
<input type="text" class="form-control" ng-model="entry.hostname" placeholder="e.g. example.com" ng-change="updateHostsEntry(service, entry)" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="entry.hostname" placeholder="e.g. example.com" ng-change="updateHostsEntry(service, entry)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
</div>
</td>
<td>
<div class="input-group input-group-sm">
<input type="text" class="form-control" ng-model="entry.ip" placeholder="e.g. 10.0.1.1" ng-change="updateHostsEntry(service, entry)" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="entry.ip" placeholder="e.g. 10.0.1.1" ng-change="updateHostsEntry(service, entry)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeHostsEntry(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>

View File

@ -32,7 +32,7 @@
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon fit-text-size">value</span>
<input type="text" class="form-control" ng-model="option.value" ng-change="updateLogDriverOpt(service, option)" placeholder="e.g. bar" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="option.value" ng-change="updateLogDriverOpt(service, option)" placeholder="e.g. bar" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLogDriverOpt(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>

View File

@ -17,28 +17,28 @@
<th ng-if="isAdmin || allowBindMounts">Type</th>
<th>Source</th>
<th>Target</th>
<th>Read only</th>
<th>Actions</th>
<th authorization="DockerServiceUpdate">Read only</th>
<th authorization="DockerServiceUpdate">Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="mount in service.ServiceMounts">
<td ng-if="isAdmin || allowBindMounts">
<select name="mountType" class="form-control" ng-model="mount.Type" ng-disabled="isUpdating">
<select name="mountType" class="form-control" ng-model="mount.Type" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
<option value="volume">Volume</option>
<option value="bind">Bind</option>
</select>
</td>
<td>
<select class="form-control" ng-model="mount.Source" ng-options="vol.Id as ((vol.Id|truncate:30) + ' - ' + (vol.Driver|truncate:30)) for vol in availableVolumes" ng-if="mount.Type === 'volume'">
<select class="form-control" ng-model="mount.Source" ng-options="vol.Id as ((vol.Id|truncate:30) + ' - ' + (vol.Driver|truncate:30)) for vol in availableVolumes" ng-if="mount.Type === 'volume'" disable-authorization="DockerServiceUpdate">
<option selected disabled hidden value="">Select a volume</option>
</select>
<input type="text" class="form-control" ng-model="mount.Source" placeholder="e.g. /tmp/portainer/data" ng-change="updateMount(service, mount)" ng-disabled="isUpdating || (!isAdmin && !allowBindMounts && mount.Type === 'bind')" ng-if="mount.Type === 'bind'">
</td>
<td>
<input type="text" class="form-control" ng-model="mount.Target" placeholder="e.g. /tmp/portainer/data" ng-change="updateMount(service, mount)" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="mount.Target" placeholder="e.g. /tmp/portainer/data" ng-change="updateMount(service, mount)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
</td>
<td>
<td authorization="DockerServiceUpdate">
<input type="checkbox" class="form-control" ng-model="mount.ReadOnly" ng-change="updateMount(service, mount)" ng-disabled="isUpdating">
</td>
<td authorization="DockerServiceUpdate">

View File

@ -22,12 +22,12 @@
<tr ng-repeat="preference in service.ServicePreferences">
<td>
<div class="input-group input-group-sm">
<input type="text" class="form-control" ng-model="preference.strategy" placeholder="e.g. node.role" ng-change="updatePlacementPreference(service, preference)" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="preference.strategy" placeholder="e.g. node.role" ng-change="updatePlacementPreference(service, preference)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
</div>
</td>
<td>
<div class="input-group input-group-sm">
<input type="text" class="form-control" ng-model="preference.value" placeholder="e.g. manager" ng-change="updatePlacementPreference(service, preference)" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="preference.value" placeholder="e.g. manager" ng-change="updatePlacementPreference(service, preference)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removePlacementPreference(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>

View File

@ -18,7 +18,7 @@
<th>Container port</th>
<th>Protocol</th>
<th>Publish mode</th>
<th>Actions</th>
<th authorization="DockerServiceUpdate">Actions</th>
</tr>
</thead>
<tbody>
@ -26,18 +26,18 @@
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon">host</span>
<input type="number" class="form-control" ng-model="portBinding.PublishedPort" placeholder="e.g. 8080" ng-change="updatePublishedPort(service, mapping)" ng-disabled="isUpdating">
<input type="number" class="form-control" ng-model="portBinding.PublishedPort" placeholder="e.g. 8080" ng-change="updatePublishedPort(service, mapping)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
</div>
</td>
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon">container</span>
<input type="number" class="form-control" ng-model="portBinding.TargetPort" placeholder="e.g. 80" ng-change="updatePublishedPort(service, mapping)" ng-disabled="isUpdating">
<input type="number" class="form-control" ng-model="portBinding.TargetPort" placeholder="e.g. 80" ng-change="updatePublishedPort(service, mapping)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
</div>
</td>
<td>
<div class="input-group input-group-sm">
<select class="selectpicker form-control" ng-model="portBinding.Protocol" ng-change="updatePublishedPort(service, mapping)" ng-disabled="isUpdating">
<select class="selectpicker form-control" ng-model="portBinding.Protocol" ng-change="updatePublishedPort(service, mapping)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
<option value="tcp">tcp</option>
<option value="udp">udp</option>
</select>
@ -45,7 +45,7 @@
</td>
<td>
<div class="input-group input-group-sm">
<select class="selectpicker form-control" ng-model="portBinding.PublishMode" ng-change="updatePublishedPort(service, mapping)" ng-disabled="isUpdating">
<select class="selectpicker form-control" ng-model="portBinding.PublishMode" ng-change="updatePublishedPort(service, mapping)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
<option value="ingress">ingress</option>
<option value="host">host</option>
</select>

View File

@ -38,7 +38,7 @@
</div>
</td>
<td>
<slider model="service.ReservationNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'ReservationNanoCPUs')"></slider>
<slider model="service.ReservationNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'ReservationNanoCPUs')" disable-authorization="DockerServiceUpdate"></slider>
</td>
<td style="vertical-align : middle;">
<p class="small text-muted">
@ -53,7 +53,7 @@
</div>
</td>
<td>
<slider model="service.LimitNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'LimitNanoCPUs')"></slider>
<slider model="service.LimitNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'LimitNanoCPUs')" disable-authorization="DockerServiceUpdate"></slider>
</td>
<td style="vertical-align : middle;">
<p class="small text-muted">

View File

@ -27,13 +27,13 @@
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon fit-text-size">name</span>
<input type="text" class="form-control" ng-model="label.key" placeholder="e.g. com.example.foo" ng-change="updateLabel(service, label)" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="label.key" placeholder="e.g. com.example.foo" ng-change="updateLabel(service, label)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
</div>
</td>
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon fit-text-size">value</span>
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" ng-change="updateLabel(service, label)" ng-disabled="isUpdating">
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" ng-change="updateLabel(service, label)" ng-disabled="isUpdating" disable-authorization="DockerServiceUpdate">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLabel(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>

View File

@ -88,7 +88,7 @@
</tr>
<tr authorization="DockerServiceLogs, DockerServiceUpdate, DockerServiceDelete">
<td colspan="2">
<p class="small text-muted">
<p class="small text-muted" authorization="DockerServiceUpdate">
Note: you can only rollback one level of changes. Clicking the rollback button without making a new change will undo your previous rollback
<p>
<a authorization="DockerServiceLogs" ng-if="applicationState.endpoint.apiVersion >= 1.30" class="btn btn-primary btn-sm" type="button" ui-sref="docker.services.service.logs({id: service.Id})"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Service logs</a>

View File

@ -4,7 +4,7 @@
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
With automatic user provisioning enabled, Portainer will create user(s) automatically with standard user role. If
With automatic user provisioning enabled, Portainer will create user(s) automatically with the standard user role. If
disabled, users must be created beforehand in Portainer in order to login.
</span>
</div>
@ -19,13 +19,13 @@
<div class="form-group">
<span class="col-sm-12 text-muted small">
<p>The users created by the automatic provisioning feature can be added to a default team on creation.</p>
<p>By assigning newly created users to a team they will be able to access the environments associated to that team. This setting is optional and if not set newly created users won't be able to access any environments.</p>
<p>By assigning newly created users to a team, they will be able to access the environments associated to that team. This setting is optional and if not set, newly created users won't be able to access any environments.</p>
</span>
</div>
<div class="form-group">
<label class="col-sm-3 col-lg-2 control-label text-left">Default team</label>
<span class="small text-muted" style="margin-left: 20px;" ng-if="$ctrl.teams.length === 0">
You have not yet created any team. Head over the <a ui-sref="portainer.teams">teams view</a> to manage user teams.
You have not yet created any teams. Head over to the <a ui-sref="portainer.teams">Teams view</a> to manage teams.
</span>
<button type="button" class="btn btn-sm btn-danger" ng-click="$ctrl.settings.DefaultTeamID = null" ng-disabled="!$ctrl.settings.DefaultTeamID" ng-if="$ctrl.teams.length > 0"><i class="fa fa-times" aria-hidden="true"></i></button>
<div class="col-sm-9 col-lg-9" ng-if="$ctrl.teams.length > 0">

View File

@ -18,6 +18,9 @@ angular.module('portainer.extensions.rbac')
if (!Authentication.hasAuthorizations(authorizations)) {
elem.attr('disabled', true);
if (elem.is('Slider')) {
elem.css('pointer-events', 'none');
}
}
}

View File

@ -5,10 +5,10 @@
<rd-widget-body classes="no-padding">
<div class="toolBar small" ng-if="$ctrl.inheritFrom">
Access tagged as <code>inherited</code> are inherited from the group access. They cannot be
removed nor modified at the endpoint level but they can be overriden.
removed or modified at the endpoint level but they can be overriden.
</div>
<div class="toolBar small" ng-if="$ctrl.inheritFrom">
Access tagged as <code>override</code> are overriding the group access for the related users / teams.
Access tagged as <code>override</code> are overriding the group access for the related users/teams.
</div>
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0"
@ -99,7 +99,7 @@
<td colspan="4" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
<td colspan="4" class="text-center text-muted">No authorized user or team.</td>
<td colspan="4" class="text-center text-muted">No authorized users or teams.</td>
</tr>
</tbody>
</table>

View File

@ -79,7 +79,7 @@
<portainer-tooltip ng-if="!$ctrl.isAdmin && $ctrl.availableTeams.length > 1" position="bottom" message="As you are a member of multiple teams, you can select which teams(s) will be able to manage this resource."></portainer-tooltip>
</label>
<span ng-if="$ctrl.isAdmin && $ctrl.availableTeams.length === 0" class="small text-muted" style="margin-left: 20px;">
You have not yet created any team. Head over the <a ui-sref="portainer.teams">teams view</a> to manage user teams.
You have not yet created any teams. Head over to the <a ui-sref="portainer.teams">Teams view</a> to manage teams.
</span>
<span isteven-multi-select
ng-if="($ctrl.isAdmin && $ctrl.availableTeams.length > 0) || (!$ctrl.isAdmin && $ctrl.availableTeams.length > 1)"
@ -104,7 +104,7 @@
<portainer-tooltip ng-if="$ctrl.isAdmin && $ctrl.availableUsers.length > 0" position="bottom" message="You can select which user(s) will be able to manage this resource."></portainer-tooltip>
</label>
<span ng-if="$ctrl.availableUsers.length === 0" class="small text-muted" style="margin-left: 20px;">
You have not yet created any user. Head over the <a ui-sref="portainer.users">users view</a> to manage users.
You have not yet created any users. Head over to the <a ui-sref="portainer.users">Users view</a> to manage users.
</span>
<span isteven-multi-select
ng-if="$ctrl.availableUsers.length > 0"

View File

@ -48,9 +48,9 @@ function ($q, UserService, TeamService, Notifications, Authentication, ResourceC
availableUsers: isAdmin ? UserService.users(false) : []
})
.then(function success(data) {
ctrl.availableUsers = data.availableUsers;
ctrl.availableUsers = _.orderBy(data.availableUsers, 'Username', 'asc');
var availableTeams = data.availableTeams;
var availableTeams = _.orderBy(data.availableTeams, 'Name', 'asc');
ctrl.availableTeams = availableTeams;
if (!isAdmin && availableTeams.length === 1) {
ctrl.formData.AuthorizedTeams = availableTeams;

View File

@ -127,7 +127,7 @@
<td colspan="2">
<span>Teams</span>
<span ng-if="$ctrl.isAdmin && $ctrl.availableTeams.length === 0" class="small text-muted" style="margin-left: 10px;">
You have not yet created any team. Head over the <a ui-sref="portainer.teams">teams view</a> to manage user teams.
You have not yet created any teams. Head over to the <a ui-sref="portainer.teams">Teams view</a> to manage teams.
</span>
<span isteven-multi-select
ng-if="($ctrl.isAdmin && $ctrl.availableTeams.length > 0) || (!$ctrl.isAdmin && $ctrl.availableTeams.length > 1)"
@ -149,7 +149,7 @@
<td colspan="2">
<span>Users</span>
<span ng-if="$ctrl.availableUsers.length === 0" class="small text-muted" style="margin-left: 10px;">
You have not yet created any user. Head over the <a ui-sref="portainer.users">users view</a> to manage users.
You have not yet created any users. Head over to the <a ui-sref="portainer.users">Users view</a> to manage users.
</span>
<span isteven-multi-select
ng-if="$ctrl.availableUsers.length > 0"

View File

@ -106,14 +106,14 @@ function ($q, $state, UserService, TeamService, ResourceControlHelper, ResourceC
});
})
.then(function success(data) {
ctrl.availableUsers = data.availableUsers;
ctrl.availableUsers = _.orderBy(data.availableUsers, 'Username', 'asc');
angular.forEach(ctrl.availableUsers, function(user) {
var found = _.find(ctrl.authorizedUsers, { Id: user.Id });
if (found) {
user.selected = true;
}
});
ctrl.availableTeams = data.availableTeams;
ctrl.availableTeams = _.orderBy(data.availableTeams, 'Name', 'asc');
angular.forEach(data.availableTeams, function(team) {
var found = _.find(ctrl.authorizedTeams, { Id: team.Id });
if (found) {

View File

@ -10,7 +10,7 @@
</label>
<div class="col-sm-9 col-lg-4">
<span class="small text-muted" ng-if="ctrl.availableUsersAndTeams.length === 0">
No user nor team available.
No users or teams available.
</span>
<span isteven-multi-select ng-if="ctrl.availableUsersAndTeams.length > 0" input-model="ctrl.availableUsersAndTeams"
output-model="ctrl.formValues.multiselectOutput" button-label="icon '-' Name" item-label="icon '-' Name"

View File

@ -88,7 +88,7 @@ class PorAccessManagementController {
parent ? parent.TeamAccessPolicies : {},
this.roles
);
this.availableUsersAndTeams = data.availableUsersAndTeams;
this.availableUsersAndTeams = _.orderBy(data.availableUsersAndTeams, 'Name', 'asc');
this.authorizedUsersAndTeams = data.authorizedUsersAndTeams;
} catch (err) {
this.availableUsersAndTeams = [];

View File

@ -51,8 +51,11 @@
<i class="fa fa-server space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].RunningContainerCount + $ctrl.model.Snapshots[0].StoppedContainerCount }} {{ $ctrl.model.Snapshots[0].RunningContainerCount + $ctrl.model.Snapshots[0].StoppedContainerCount === 1 ? 'container' : 'containers' }}
<span ng-if="$ctrl.model.Snapshots[0].RunningContainerCount > 0 || $ctrl.model.Snapshots[0].StoppedContainerCount > 0">
-
<i class="fa fa-heartbeat green-icon" aria-hidden="true"></i> {{ $ctrl.model.Snapshots[0].RunningContainerCount }}
<i class="fa fa-heartbeat red-icon" aria-hidden="true"></i> {{ $ctrl.model.Snapshots[0].StoppedContainerCount }}
<i class="fa fa-power-off green-icon" aria-hidden="true"></i> {{ $ctrl.model.Snapshots[0].RunningContainerCount }}
<i class="fa fa-power-off red-icon" aria-hidden="true"></i> {{ $ctrl.model.Snapshots[0].StoppedContainerCount }}
/
<i class="fa fa-heartbeat green-icon" aria-hidden="true"></i> {{ $ctrl.model.Snapshots[0].HealthyContainerCount }}
<i class="fa fa-heartbeat orange-icon" aria-hidden="true"></i> {{ $ctrl.model.Snapshots[0].UnhealthyContainerCount }}
</span>
</span>
<span style="padding: 0 7px 0 7px;">

View File

@ -103,7 +103,7 @@
<div class="form-group">
<label for="template_note" class="col-sm-3 col-lg-2 control-label text-left">
Note
<portainer-tooltip position="bottom" message="Usage / extra information about the template. Supports HTML."></portainer-tooltip>
<portainer-tooltip position="bottom" message="Usage/extra information about the template. Supports HTML."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<textarea class="form-control" name="template_note" ng-model="$ctrl.model.Note" placeholder='You can use this field to specify extra information. <br/> It supports <b>HTML</b>.'></textarea>
@ -144,7 +144,7 @@
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
Administrator template
<portainer-tooltip position="bottom" message="Should this template be only available to administrator users."></portainer-tooltip>
<portainer-tooltip position="bottom" message="This template will only be available to administrator users."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="$ctrl.model.AdministratorOnly"><i></i>

View File

@ -45,7 +45,6 @@
-e EDGE_ID={{ randomEdgeID }} \
-e EDGE_KEY={{ endpoint.EdgeKey }} \
-e CAP_HOST_MANAGEMENT=1 \
-p 8000:80 \
-v portainer_agent_data:/data \
--name portainer_edge_agent \
portainer/agent
@ -66,7 +65,6 @@
-e EDGE_KEY={{ endpoint.EdgeKey }} \
-e CAP_HOST_MANAGEMENT=1 \
--mode global \
--publish mode=host,published=8000,target=80 \
--constraint 'node.platform.os == linux' \
--mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \
--mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volumes \

View File

@ -22,9 +22,9 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService,
$scope.copyEdgeAgentDeploymentCommand = function() {
if ($scope.state.deploymentTab === 0) {
clipboard.copyText('docker run -d -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/docker/volumes:/var/lib/docker/volumes -v /:/host --restart always -e EDGE=1 -e EDGE_ID=' + $scope.randomEdgeID + ' -e EDGE_KEY=' + $scope.endpoint.EdgeKey +' -e CAP_HOST_MANAGEMENT=1 -p 8000:80 -v portainer_agent_data:/data --name portainer_edge_agent portainer/agent');
clipboard.copyText('docker run -d -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/docker/volumes:/var/lib/docker/volumes -v /:/host --restart always -e EDGE=1 -e EDGE_ID=' + $scope.randomEdgeID + ' -e EDGE_KEY=' + $scope.endpoint.EdgeKey +' -e CAP_HOST_MANAGEMENT=1 -v portainer_agent_data:/data --name portainer_edge_agent portainer/agent');
} else {
clipboard.copyText('docker network create --driver overlay portainer_agent_network; docker service create --name portainer_edge_agent --network portainer_agent_network -e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent -e EDGE=1 -e EDGE_ID=' + $scope.randomEdgeID + ' -e EDGE_KEY=' + $scope.endpoint.EdgeKey +' -e CAP_HOST_MANAGEMENT=1 --mode global --publish mode=host,published=8000,target=80 --constraint \'node.platform.os == linux\' --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock --mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volume --mount type=bind,src=//,dst=/host --mount type=volume,src=portainer_agent_data,dst=/data portainer/agent');
clipboard.copyText('docker network create --driver overlay portainer_agent_network; docker service create --name portainer_edge_agent --network portainer_agent_network -e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent -e EDGE=1 -e EDGE_ID=' + $scope.randomEdgeID + ' -e EDGE_KEY=' + $scope.endpoint.EdgeKey +' -e CAP_HOST_MANAGEMENT=1 --mode global --constraint \'node.platform.os == linux\' --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock --mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volume --mount type=bind,src=//,dst=/host --mount type=volume,src=portainer_agent_data,dst=/data portainer/agent');
}
$('#copyNotificationDeploymentCommand').show().fadeOut(2500);
};

View File

@ -90,37 +90,54 @@
<portainer-tooltip position="bottom" message="URL or IP address of the LDAP server."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="ldap_url" ng-model="LDAPSettings.URL" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389">
<input type="text" class="form-control" id="ldap_url" ng-model="formValues.LDAPSettings.URL" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389">
</div>
</div>
<!-- Anonymous mode-->
<div class="form-group">
<label for="ldap_username" class="col-sm-3 col-lg-2 control-label text-left">
Reader DN
<portainer-tooltip position="bottom" message="Account that will be used to search for users."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="ldap_username" ng-model="LDAPSettings.ReaderDN" placeholder="cn=readonly-account,dc=ldap,dc=domain,dc=tld">
<div class="col-sm-12">
<label for="anonymous_mode" class="control-label text-left">
Anonymous mode
<portainer-tooltip position="bottom" message="Enable this option if the server is configured for Anonymous access."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" id="anonymous_mode" ng-model="formValues.LDAPSettings.AnonymousMode"><i></i>
</label>
</div>
</div>
<!-- !Anonymous mode-->
<div class="form-group">
<label for="ldap_password" class="col-sm-3 col-lg-2 control-label text-left">
Password
</label>
<div class="col-sm-9 col-lg-10">
<input type="password" class="form-control" id="ldap_password" ng-model="LDAPSettings.Password" placeholder="password">
<div ng-if="!formValues.LDAPSettings.AnonymousMode">
<div class="form-group">
<label for="ldap_username" class="col-sm-3 col-lg-2 control-label text-left">
Reader DN
<portainer-tooltip position="bottom" message="Account that will be used to search for users."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="ldap_username" ng-model="formValues.LDAPSettings.ReaderDN" placeholder="cn=readonly-account,dc=ldap,dc=domain,dc=tld">
</div>
</div>
<div class="form-group">
<label for="ldap_password" class="col-sm-3 col-lg-2 control-label text-left">
Password
<portainer-tooltip position="bottom" message="If you do not enter a password, Portainer will leave the current password unchanged."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="password" class="form-control" id="ldap_password" ng-model="formValues.LDAPSettings.Password" placeholder="password">
</div>
</div>
</div>
<div class="form-group" ng-if="!LDAPSettings.TLSConfig.TLS && !LDAPSettings.StartTLS">
<div class="form-group" ng-if="!formValues.LDAPSettings.TLSConfig.TLS && !formValues.LDAPSettings.StartTLS">
<label for="ldap_password" class="col-sm-3 col-lg-2 control-label text-left">
Connectivity check
<i class="fa fa-check green-icon" style="margin-left: 5px;" ng-if="state.successfulConnectivityCheck"></i>
<i class="fa fa-times red-icon" style="margin-left: 5px;" ng-if="state.failedConnectivityCheck"></i>
</label>
<div class="col-sm-9 col-lg-10">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.connectivityCheckInProgress || !LDAPSettings.URL || !LDAPSettings.ReaderDN || !LDAPSettings.Password" ng-click="LDAPConnectivityCheck()" button-spinner="state.connectivityCheckInProgress">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="(state.connectivityCheckInProgress) || (!formValues.LDAPSettings.URL) || ((!formValues.LDAPSettings.ReaderDN || !formValues.LDAPSettings.Password) && !formValues.LDAPSettings.AnonymousMode)" ng-click="LDAPConnectivityCheck()" button-spinner="state.connectivityCheckInProgress">
<span ng-hide="state.connectivityCheckInProgress">Test connectivity</span>
<span ng-show="state.connectivityCheckInProgress">Testing connectivity...</span>
</button>
@ -132,28 +149,28 @@
</div>
<!-- starttls -->
<div class="form-group" ng-if="!LDAPSettings.TLSConfig.TLS">
<div class="form-group" ng-if="!formValues.LDAPSettings.TLSConfig.TLS">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
Use StartTLS
<portainer-tooltip position="bottom" message="Enable this option if want to use StartTLS to secure the connection to the server. Ignored if Use TLS is selected."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="LDAPSettings.StartTLS"><i></i>
<input type="checkbox" ng-model="formValues.LDAPSettings.StartTLS"><i></i>
</label>
</div>
</div>
<!-- !starttls -->
<!-- tls-checkbox -->
<div class="form-group" ng-if="!LDAPSettings.StartTLS">
<div class="form-group" ng-if="!formValues.LDAPSettings.StartTLS">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
Use TLS
<portainer-tooltip position="bottom" message="Enable this option if you need to specify TLS certificates to connect to the LDAP server."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="LDAPSettings.TLSConfig.TLS"><i></i>
<input type="checkbox" ng-model="formValues.LDAPSettings.TLSConfig.TLS"><i></i>
</label>
</div>
</div>
@ -167,22 +184,22 @@
<portainer-tooltip position="bottom" message="Skip the verification of the server TLS certificate. Not recommended on unsecured networks."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="LDAPSettings.TLSConfig.TLSSkipVerify"><i></i>
<input type="checkbox" ng-model="formValues.LDAPSettings.TLSConfig.TLSSkipVerify"><i></i>
</label>
</div>
</div>
<!-- !tls-skip-verify -->
<!-- tls-certs -->
<div ng-if="LDAPSettings.TLSConfig.TLS || LDAPSettings.StartTLS">
<div ng-if="formValues.LDAPSettings.TLSConfig.TLS || formValues.LDAPSettings.StartTLS">
<!-- ca-input -->
<div class="form-group" ng-if="!LDAPSettings.TLSConfig.TLSSkipVerify">
<div class="form-group" ng-if="!formValues.LDAPSettings.TLSConfig.TLSSkipVerify">
<label class="col-sm-2 control-label text-left">TLS CA certificate</label>
<div class="col-sm-10">
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCACert">Select file</button>
<span style="margin-left: 5px;">
{{ formValues.TLSCACert.name }}
<i class="fa fa-check green-icon" ng-if="formValues.TLSCACert && formValues.TLSCACert === LDAPSettings.TLSConfig.TLSCACert" aria-hidden="true"></i>
<i class="fa fa-check green-icon" ng-if="formValues.TLSCACert && formValues.TLSCACert === formValues.LDAPSettings.TLSConfig.TLSCACert" aria-hidden="true"></i>
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCACert" aria-hidden="true"></i>
<i class="fa fa-circle-notch fa-spin" ng-if="state.uploadInProgress"></i>
</span>
@ -192,14 +209,14 @@
</div>
<!-- !tls-certs -->
<div class="form-group" ng-if="LDAPSettings.TLSConfig.TLS || LDAPSettings.StartTLS">
<div class="form-group" ng-if="formValues.LDAPSettings.TLSConfig.TLS || formValues.LDAPSettings.StartTLS">
<label for="ldap_password" class="col-sm-3 col-lg-2 control-label text-left">
Connectivity check
<i class="fa fa-check green-icon" style="margin-left: 5px;" ng-if="state.successfulConnectivityCheck"></i>
<i class="fa fa-times red-icon" style="margin-left: 5px;" ng-if="state.failedConnectivityCheck"></i>
</label>
<div class="col-sm-9 col-lg-10">
<button type="button" class="btn btn-primary btn-sm" ng-click="LDAPConnectivityCheck()" ng-disabled="!LDAPSettings.URL || !LDAPSettings.ReaderDN || !LDAPSettings.Password || (!formValues.TLSCACert && !LDAPSettings.TLSConfig.TLSSkipVerify)">Test connectivity</button>
<button type="button" class="btn btn-primary btn-sm" ng-click="LDAPConnectivityCheck()" ng-disabled="(!formValues.LDAPSettings.URL) || (!formValues.TLSCACert && !formValues.LDAPSettings.TLSConfig.TLSSkipVerify) || ((!formValues.LDAPSettings.ReaderDN || !formValues.LDAPSettings.Password) && !formValues.LDAPSettings.AnonymousMode)">Test connectivity</button>
<i id="connectivityCheckSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>
@ -218,7 +235,7 @@
Automatic user provisioning
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="LDAPSettings.AutoCreateUsers"><i></i>
<input type="checkbox" ng-model="formValues.LDAPSettings.AutoCreateUsers"><i></i>
</label>
</div>
</div>
@ -228,7 +245,7 @@
</div>
<!-- search-settings -->
<div ng-repeat="config in LDAPSettings.SearchSettings | limitTo: (1 - LDAPSettings.SearchSettings)" style="margin-top: 5px;">
<div ng-repeat="config in formValues.LDAPSettings.SearchSettings | limitTo: (1 - formValues.LDAPSettings.SearchSettings)" style="margin-top: 5px;">
<div class="form-group" ng-if="$index > 0">
<span class="col-sm-12 text-muted small">
@ -282,7 +299,7 @@
</div>
<!-- group-search-settings -->
<div ng-repeat="groupConfig in LDAPSettings.GroupSearchSettings | limitTo: (1 - LDAPSettings.GroupSearchSettings)" style="margin-top: 5px;">
<div ng-repeat="groupConfig in formValues.LDAPSettings.GroupSearchSettings | limitTo: (1 - formValues.LDAPSettings.GroupSearchSettings)" style="margin-top: 5px;">
<div class="form-group" ng-if="$index > 0">
<span class="col-sm-12 text-muted small">

View File

@ -11,7 +11,32 @@ function($q, $scope, $state, Notifications, SettingsService, FileUploadService,
};
$scope.formValues = {
TLSCACert: ''
TLSCACert: '',
LDAPSettings: {
AnonymousMode: true,
ReaderDN: '',
URL: '',
TLSConfig: {
TLS: false,
TLSSkipVerify: false
},
StartTLS: false,
SearchSettings: [
{
BaseDN: '',
Filter: '',
UserNameAttribute: ''
}
],
GroupSearchSettings: [
{
GroupBaseDN: '',
GroupFilter: '',
GroupAttribute: ''
}
],
AutoCreateUsers: true
}
};
$scope.goToOAuthExtensionView = function() {
@ -23,32 +48,37 @@ function($q, $scope, $state, Notifications, SettingsService, FileUploadService,
};
$scope.addSearchConfiguration = function() {
$scope.LDAPSettings.SearchSettings.push({ BaseDN: '', UserNameAttribute: '', Filter: '' });
$scope.formValues.LDAPSettings.SearchSettings.push({ BaseDN: '', UserNameAttribute: '', Filter: '' });
};
$scope.removeSearchConfiguration = function(index) {
$scope.LDAPSettings.SearchSettings.splice(index, 1);
$scope.formValues.LDAPSettings.SearchSettings.splice(index, 1);
};
$scope.addGroupSearchConfiguration = function() {
$scope.LDAPSettings.GroupSearchSettings.push({ GroupBaseDN: '', GroupAttribute: '', GroupFilter: '' });
$scope.formValues.LDAPSettings.GroupSearchSettings.push({ GroupBaseDN: '', GroupAttribute: '', GroupFilter: '' });
};
$scope.removeGroupSearchConfiguration = function(index) {
$scope.LDAPSettings.GroupSearchSettings.splice(index, 1);
$scope.formValues.LDAPSettings.GroupSearchSettings.splice(index, 1);
};
$scope.LDAPConnectivityCheck = function() {
var settings = $scope.settings;
var settings = angular.copy($scope.settings);
var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null;
var uploadRequired = ($scope.LDAPSettings.TLSConfig.TLS || $scope.LDAPSettings.StartTLS) && !$scope.LDAPSettings.TLSConfig.TLSSkipVerify;
if ($scope.formValues.LDAPSettings.AnonymousMode){
settings.LDAPSettings['ReaderDN'] = '';
settings.LDAPSettings['Password'] = '';
}
var uploadRequired = ($scope.formValues.LDAPSettings.TLSConfig.TLS || $scope.formValues.LDAPSettings.StartTLS) && !$scope.formValues.LDAPSettings.TLSConfig.TLSSkipVerify;
$scope.state.uploadInProgress = uploadRequired;
$scope.state.connectivityCheckInProgress = true;
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null))
.then(function success() {
addLDAPDefaultPort(settings, $scope.LDAPSettings.TLSConfig.TLS);
addLDAPDefaultPort(settings, $scope.formValues.LDAPSettings.TLSConfig.TLS);
return SettingsService.checkLDAPConnectivity(settings);
})
.then(function success() {
@ -68,16 +98,21 @@ function($q, $scope, $state, Notifications, SettingsService, FileUploadService,
};
$scope.saveSettings = function() {
var settings = $scope.settings;
var settings = angular.copy($scope.settings);
var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null;
var uploadRequired = ($scope.LDAPSettings.TLSConfig.TLS || $scope.LDAPSettings.StartTLS) && !$scope.LDAPSettings.TLSConfig.TLSSkipVerify;
if ($scope.formValues.LDAPSettings.AnonymousMode){
settings.LDAPSettings['ReaderDN'] = '';
settings.LDAPSettings['Password'] = '';
}
var uploadRequired = ($scope.formValues.LDAPSettings.TLSConfig.TLS || $scope.formValues.LDAPSettings.StartTLS) && !$scope.formValues.LDAPSettings.TLSConfig.TLSSkipVerify;
$scope.state.uploadInProgress = uploadRequired;
$scope.state.actionInProgress = true;
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null))
.then(function success() {
addLDAPDefaultPort(settings, $scope.LDAPSettings.TLSConfig.TLS);
addLDAPDefaultPort(settings, $scope.formValues.LDAPSettings.TLSConfig.TLS);
return SettingsService.update(settings);
})
.then(function success() {
@ -109,7 +144,7 @@ function($q, $scope, $state, Notifications, SettingsService, FileUploadService,
var settings = data.settings;
$scope.teams = data.teams;
$scope.settings = settings;
$scope.LDAPSettings = settings.LDAPSettings;
$scope.formValues.LDAPSettings = settings.LDAPSettings;
$scope.OAuthSettings = settings.OAuthSettings;
$scope.formValues.TLSCACert = settings.LDAPSettings.TLSConfig.TLSCACert;
$scope.oauthAuthenticationAvailable = data.oauthAuthentication;

View File

@ -160,15 +160,20 @@
</label>
</div>
</div>
<div class="form-group" ng-if="formValues.RepositoryAuthentication">
<span class="col-sm-12 text-muted small">
If your git account has 2FA enabled, you may receive an <code>authentication required</code> error when deploying your stack. In this case, you will need to provide a personal-access token instead of your password.
</span>
</div>
<div class="form-group" ng-if="formValues.RepositoryAuthentication">
<label for="repository_username" class="col-sm-1 control-label text-left">Username</label>
<div class="col-sm-11 col-md-5">
<input type="text" class="form-control" ng-model="formValues.RepositoryUsername" name="repository_username" placeholder="myGitUser">
</div>
<label for="repository_password" class="col-sm-1 margin-sm-top control-label text-left">
<label for="repository_password" class="col-sm-1 control-label text-left">
Password
</label>
<div class="col-sm-11 col-md-5 margin-sm-top">
<div class="col-sm-11 col-md-5">
<input type="password" class="form-control" ng-model="formValues.RepositoryPassword" name="repository_password" placeholder="myPassword">
</div>
</div>

View File

@ -1,3 +1,5 @@
import _ from 'lodash-es';
angular.module('portainer.app')
.controller('TeamsController', ['$q', '$scope', '$state', 'TeamService', 'UserService', 'ModalService', 'Notifications', 'Authentication',
function ($q, $scope, $state, TeamService, UserService, ModalService, Notifications, Authentication) {
@ -84,7 +86,7 @@ function ($q, $scope, $state, TeamService, UserService, ModalService, Notificati
.then(function success(data) {
var teams = data.teams;
$scope.teams = teams;
$scope.users = data.users;
$scope.users = _.orderBy(data.users, 'Username', 'asc');
})
.catch(function error(err) {
$scope.teams = [];

View File

@ -239,7 +239,7 @@
id="container_volumes" class="form-control"
placeholder="Select a volume"
typeahead-min-length="0"
uib-typeahead="vol as vol.Name for vol in availableVolumes | filter:$viewValue"
uib-typeahead="vol.Name as vol.Name for vol in availableVolumes | filter:$viewValue"
/>
</div>
<!-- !volume -->

View File

@ -71,7 +71,7 @@
Add to team(s)
</label>
<span class="small text-muted" style="margin-left: 20px;" ng-if="teams.length === 0">
You don't seem to have any teams to add user's into. Head over to the <a ui-sref="portainer.teams">Teams view</a> to create some.
You don't seem to have any teams to add users into. Head over to the <a ui-sref="portainer.teams">Teams view</a> to create some.
</span>
<span isteven-multi-select
ng-if="teams.length > 0"

View File

@ -1,3 +1,5 @@
import _ from 'lodash-es';
angular.module('portainer.app')
.controller('UsersController', ['$q', '$scope', '$state', 'UserService', 'TeamService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Authentication', 'SettingsService',
function ($q, $scope, $state, UserService, TeamService, TeamMembershipService, ModalService, Notifications, Authentication, SettingsService) {
@ -110,7 +112,7 @@ function ($q, $scope, $state, UserService, TeamService, TeamMembershipService, M
var users = data.users;
assignTeamLeaders(users, data.memberships);
$scope.users = users;
$scope.teams = data.teams;
$scope.teams = _.orderBy(data.teams, 'Name', 'asc');
$scope.AuthenticationMethod = data.settings.AuthenticationMethod;
})
.catch(function error(err) {

View File

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

View File

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