diff --git a/api/cli/cli.go b/api/cli/cli.go index c2d931c3b..52efc748f 100644 --- a/api/cli/cli.go +++ b/api/cli/cli.go @@ -55,6 +55,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) { Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')), Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(), Templates: kingpin.Flag("templates", "URL to the templates definitions.").Short('t').String(), + BaseURL: kingpin.Flag("base-url", "Base URL parameter such as portainer if running portainer as http://yourdomain.com/portainer/.").Short('b').Default(defaultBaseURL).String(), } kingpin.Parse() diff --git a/api/cli/defaults.go b/api/cli/defaults.go index be27b4a93..7adf93566 100644 --- a/api/cli/defaults.go +++ b/api/cli/defaults.go @@ -19,4 +19,5 @@ const ( defaultSSLCertPath = "/certs/portainer.crt" defaultSSLKeyPath = "/certs/portainer.key" defaultSnapshotInterval = "5m" + defaultBaseURL = "/" ) diff --git a/api/cli/defaults_windows.go b/api/cli/defaults_windows.go index 89d77ed14..1f7bef733 100644 --- a/api/cli/defaults_windows.go +++ b/api/cli/defaults_windows.go @@ -17,4 +17,5 @@ const ( defaultSSLCertPath = "C:\\certs\\portainer.crt" defaultSSLKeyPath = "C:\\certs\\portainer.key" defaultSnapshotInterval = "5m" + defaultBaseURL = "/" ) diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 7f5305cca..a616417bf 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -653,6 +653,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server { ShutdownCtx: shutdownCtx, ShutdownTrigger: shutdownTrigger, StackDeployer: stackDeployer, + BaseURL: *flags.BaseURL, } } diff --git a/api/http/handler/kubernetes/handler.go b/api/http/handler/kubernetes/handler.go index 44687147c..5ee484a99 100644 --- a/api/http/handler/kubernetes/handler.go +++ b/api/http/handler/kubernetes/handler.go @@ -21,15 +21,17 @@ type Handler struct { kubernetesClientFactory *cli.ClientFactory authorizationService *authorization.Service JwtService portainer.JWTService + BaseURL string } // NewHandler creates a handler to process pre-proxied requests to external APIs. -func NewHandler(bouncer *security.RequestBouncer, authorizationService *authorization.Service, dataStore portainer.DataStore, kubernetesClientFactory *cli.ClientFactory) *Handler { +func NewHandler(bouncer *security.RequestBouncer, authorizationService *authorization.Service, dataStore portainer.DataStore, kubernetesClientFactory *cli.ClientFactory, baseURL string) *Handler { h := &Handler{ Router: mux.NewRouter(), dataStore: dataStore, kubernetesClientFactory: kubernetesClientFactory, authorizationService: authorizationService, + BaseURL: baseURL, } kubeRouter := h.PathPrefix("/kubernetes").Subrouter() diff --git a/api/http/handler/kubernetes/kubernetes_config.go b/api/http/handler/kubernetes/kubernetes_config.go index bc8ec613b..88094d23b 100644 --- a/api/http/handler/kubernetes/kubernetes_config.go +++ b/api/http/handler/kubernetes/kubernetes_config.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "strings" clientV1 "k8s.io/client-go/tools/clientcmd/api/v1" @@ -133,7 +134,7 @@ func (handler *Handler) buildConfig(r *http.Request, tokenData *portainer.TokenD return nil, &httperror.HandlerError{http.StatusInternalServerError, fmt.Sprintf("unable to find serviceaccount associated with user; username=%s", tokenData.Username), err} } - configClusters[idx] = buildCluster(r, endpoint) + configClusters[idx] = buildCluster(r, handler.BaseURL, endpoint) configContexts[idx] = buildContext(serviceAccount.Name, endpoint) if !authInfosSet[serviceAccount.Name] { configAuthInfos = append(configAuthInfos, buildAuthInfo(serviceAccount.Name, bearerToken)) @@ -151,8 +152,11 @@ func (handler *Handler) buildConfig(r *http.Request, tokenData *portainer.TokenD }, nil } -func buildCluster(r *http.Request, endpoint portainer.Endpoint) clientV1.NamedCluster { - proxyURL := fmt.Sprintf("https://%s/api/endpoints/%d/kubernetes", r.Host, endpoint.ID) +func buildCluster(r *http.Request, baseURL string, endpoint portainer.Endpoint) clientV1.NamedCluster { + if baseURL != "/" { + baseURL = fmt.Sprintf("/%s/", strings.Trim(baseURL, "/")) + } + proxyURL := fmt.Sprintf("https://%s%sapi/endpoints/%d/kubernetes", r.Host, baseURL, endpoint.ID) return clientV1.NamedCluster{ Name: buildClusterName(endpoint.Name), Cluster: clientV1.Cluster{ diff --git a/api/http/server.go b/api/http/server.go index f95944595..2aa33b693 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -96,6 +96,7 @@ type Server struct { ShutdownCtx context.Context ShutdownTrigger context.CancelFunc StackDeployer stackdeployer.StackDeployer + BaseURL string } // Start starts the HTTP server @@ -172,7 +173,7 @@ func (server *Server) Start() error { endpointProxyHandler.ProxyManager = server.ProxyManager endpointProxyHandler.ReverseTunnelService = server.ReverseTunnelService - var kubernetesHandler = kubehandler.NewHandler(requestBouncer, server.AuthorizationService, server.DataStore, server.KubernetesClientFactory) + var kubernetesHandler = kubehandler.NewHandler(requestBouncer, server.AuthorizationService, server.DataStore, server.KubernetesClientFactory, server.BaseURL) kubernetesHandler.JwtService = server.JWTService var fileHandler = file.NewHandler(filepath.Join(server.AssetsPath, "public")) diff --git a/api/portainer.go b/api/portainer.go index 7393e7435..62a114f1d 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -95,6 +95,7 @@ type ( SSLKey *string Rollback *bool SnapshotInterval *string + BaseURL *string } // CustomTemplate represents a custom template diff --git a/app/docker/views/containers/console/containerConsoleController.js b/app/docker/views/containers/console/containerConsoleController.js index 27bee1fa6..bd7a58d5f 100644 --- a/app/docker/views/containers/console/containerConsoleController.js +++ b/app/docker/views/containers/console/containerConsoleController.js @@ -1,4 +1,5 @@ import { Terminal } from 'xterm'; +import { baseHref } from '@/portainer/helpers/pathHelper'; angular.module('portainer.docker').controller('ContainerConsoleController', [ '$scope', @@ -69,7 +70,8 @@ angular.module('portainer.docker').controller('ContainerConsoleController', [ }; var url = - window.location.href.split('#')[0] + + window.location.origin + + baseHref() + 'api/websocket/attach?' + Object.keys(params) .map((k) => k + '=' + params[k]) @@ -109,7 +111,8 @@ angular.module('portainer.docker').controller('ContainerConsoleController', [ }; var url = - window.location.href.split('#')[0] + + window.location.origin + + baseHref() + 'api/websocket/exec?' + Object.keys(params) .map((k) => k + '=' + params[k]) diff --git a/app/docker/views/images/import/importimage.html b/app/docker/views/images/import/importimage.html index 78e046eb4..7e6c7fcc7 100644 --- a/app/docker/views/images/import/importimage.html +++ b/app/docker/views/images/import/importimage.html @@ -19,9 +19,7 @@