diff --git a/README.md b/README.md index a3483f6f9..14a404772 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ [![Gitter](https://badges.gitter.im/portainer/Lobby.svg)](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHXZJQNJQ36H6) -**_Portainer_** is a lightweight management UI which allows you to **easily** manage your Docker host or Swarm cluster. +**_Portainer_** is a lightweight management UI which allows you to **easily** manage your different Docker environments (Docker hosts or Swarm clusters). -**_Portainer_** is meant to be as **simple** to deploy as it is to use. It consists of a single container that can run on any Docker engine (Docker for Linux and Docker for Windows are supported). +**_Portainer_** is meant to be as **simple** to deploy as it is to use. It consists of a single container that can run on any Docker engine (can be deployed as Linux container or a Windows native container). -**_Portainer_** allows you to manage your Docker containers, images, volumes, networks and more ! It is compatible with the *standalone Docker* engine and with *Docker Swarm*. +**_Portainer_** allows you to manage your Docker containers, images, volumes, networks and more ! It is compatible with the *standalone Docker* engine and with *Docker Swarm mode*. ## Demo @@ -34,8 +34,8 @@ Please note that the public demo cluster is **reset every 15min**. * Issues: https://github.com/portainer/portainer/issues * FAQ: https://portainer.readthedocs.io/en/latest/faq.html +* Slack (chat): https://portainer.io/slack/ * Gitter (chat): https://gitter.im/portainer/Lobby -* Slack: https://portainer.io/slack/ ## Reporting bugs and contributing diff --git a/api/cron/endpoint_sync.go b/api/cron/endpoint_sync.go index 3401c6893..9fbb634dc 100644 --- a/api/cron/endpoint_sync.go +++ b/api/cron/endpoint_sync.go @@ -117,7 +117,7 @@ func (job endpointSyncJob) prepareSyncData(storedEndpoints, fileEndpoints []port } for idx, endpoint := range fileEndpoints { - if endpoint.Name == "" || endpoint.URL == "" { + if !isValidEndpoint(&endpoint) { job.logger.Printf("Invalid file endpoint definition, skipping. [name: %v] [url: %v]", endpoint.Name, endpoint.URL) continue } diff --git a/api/http/handler/auth.go b/api/http/handler/auth.go index 3b464a165..4c8218282 100644 --- a/api/http/handler/auth.go +++ b/api/http/handler/auth.go @@ -75,7 +75,7 @@ func (handler *AuthHandler) handlePostAuth(w http.ResponseWriter, r *http.Reques u, err := handler.UserService.UserByUsername(username) if err == portainer.ErrUserNotFound { - httperror.WriteErrorResponse(w, err, http.StatusNotFound, handler.Logger) + httperror.WriteErrorResponse(w, ErrInvalidCredentials, http.StatusBadRequest, handler.Logger) return } else if err != nil { httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) diff --git a/api/http/proxy/response.go b/api/http/proxy/response.go index ece319672..4208f438c 100644 --- a/api/http/proxy/response.go +++ b/api/http/proxy/response.go @@ -85,6 +85,11 @@ func rewriteResponse(response *http.Response, newResponseData interface{}, statu response.StatusCode = statusCode response.Body = body response.ContentLength = int64(len(jsonData)) + + if response.Header == nil { + response.Header = make(http.Header) + } response.Header.Set("Content-Length", strconv.Itoa(len(jsonData))) + return nil } diff --git a/api/http/proxy/transport.go b/api/http/proxy/transport.go index 29c1d6c72..76c4f43f7 100644 --- a/api/http/proxy/transport.go +++ b/api/http/proxy/transport.go @@ -59,6 +59,8 @@ func (p *proxyTransport) proxyDockerRequest(request *http.Request) (*http.Respon return p.proxyServiceRequest(request) } else if strings.HasPrefix(path, "/volumes") { return p.proxyVolumeRequest(request) + } else if strings.HasPrefix(path, "/swarm") { + return p.proxySwarmRequest(request) } return p.executeDockerRequest(request) @@ -143,6 +145,10 @@ func (p *proxyTransport) proxyVolumeRequest(request *http.Request) (*http.Respon } } +func (p *proxyTransport) proxySwarmRequest(request *http.Request) (*http.Response, error) { + return p.administratorOperation(request) +} + // restrictedOperation ensures that the current user has the required authorizations // before executing the original request. func (p *proxyTransport) restrictedOperation(request *http.Request, resourceID string) (*http.Response, error) { diff --git a/api/http/security/authorization.go b/api/http/security/authorization.go index b19dad1f6..30d6dfb72 100644 --- a/api/http/security/authorization.go +++ b/api/http/security/authorization.go @@ -22,7 +22,7 @@ func AuthorizedResourceControlDeletion(resourceControl *portainer.ResourceContro if teamAccessesCount > 0 { for _, access := range resourceControl.TeamAccesses { for _, membership := range context.UserMemberships { - if membership.TeamID == access.TeamID && membership.Role == portainer.TeamLeader { + if membership.TeamID == access.TeamID { return true } } diff --git a/api/portainer.go b/api/portainer.go index fad8e5168..756653787 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -305,7 +305,7 @@ type ( const ( // APIVersion is the version number of the Portainer API. - APIVersion = "1.13.4" + APIVersion = "1.13.5" // DBVersion is the version number of the Portainer database. DBVersion = 2 // DefaultTemplatesURL represents the default URL for the templates definitions. diff --git a/app/app.js b/app/app.js index bba4ad835..ab07489e5 100644 --- a/app/app.js +++ b/app/app.js @@ -20,11 +20,10 @@ angular.module('portainer', [ 'portainer.services', 'auth', 'dashboard', - 'common.accesscontrol.panel', - 'common.accesscontrol.form', 'container', 'containerConsole', 'containerLogs', + 'serviceLogs', 'containers', 'createContainer', 'createNetwork', @@ -166,7 +165,7 @@ angular.module('portainer', [ } } }) - .state('logs', { + .state('containerlogs', { url: '^/containers/:id/logs', views: { 'content@': { @@ -179,6 +178,19 @@ angular.module('portainer', [ } } }) + .state('servicelogs', { + url: '^/services/:id/logs', + views: { + 'content@': { + templateUrl: 'app/components/serviceLogs/servicelogs.html', + controller: 'ServiceLogsController' + }, + 'sidebar@': { + templateUrl: 'app/components/sidebar/sidebar.html', + controller: 'SidebarController' + } + } + }) .state('console', { url: '^/containers/:id/console', views: { diff --git a/app/components/common/accessControlForm/accessControlForm.html b/app/components/common/accessControlForm/accessControlForm.html deleted file mode 100644 index 21e4c3869..000000000 --- a/app/components/common/accessControlForm/accessControlForm.html +++ /dev/null @@ -1,126 +0,0 @@ -
ID | +{{ container.Id }} | +||
Name | @@ -75,7 +79,7 @@ | @@ -87,7 +91,13 @@ - + + |
+ + Size + + + + | ++ + Layer + + + + | + + +
---|---|
+ {{ layer.Size | humansize }} + | +
+
+
+ {{ layer.CreatedBy | imagelayercommand | truncate:130 }}
+
+
+
+
+
+
+
+
+
+
+ {{ layer.CreatedBy | imagelayercommand }}
+
+
+ |
+
There are no placement preferences for this service.
+Strategy | +Value | +
---|---|
+
+
+
+ |
+
+
+
+
+
+
+
+ |
+
{{stdout}}+
{{stderr}}+