diff --git a/aio/develop/run-npm-on-container.sh b/aio/develop/run-npm-on-container.sh
index ccb157c9a99..f9d88e8fd2f 100755
--- a/aio/develop/run-npm-on-container.sh
+++ b/aio/develop/run-npm-on-container.sh
@@ -26,6 +26,9 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
LOCAL_UID=$(id -u)
LOCAL_GID=$(id -g)
+# Set max http header size for NodeJS
+NODE_OPTIONS=${NODE_OPTIONS:-"--max-http-header-size=102400"}
+
# K8S_DASHBOARD_NPM_CMD will be passed into container and will be used
# by run-npm-command.sh on container. Then the shell sciprt will run `npm`
# command with K8S_DASHBOAD_NPM_CMD.
@@ -88,6 +91,7 @@ docker run \
-e K8S_DASHBOARD_DEBUG=${K8S_DASHBOARD_DEBUG} \
-e LOCAL_UID="${LOCAL_UID}" \
-e LOCAL_GID="${LOCAL_GID}" \
+ -e NODE_OPTIONS="${NODE_OPTIONS}" \
-p ${K8S_DASHBOARD_PORT}:${K8S_DASHBOARD_PORT} \
-p ${K8S_DASHBOARD_DEBUG_PORT}:${K8S_DASHBOARD_DEBUG_PORT} \
${DOCKER_RUN_OPTS} \
diff --git a/i18n/de/messages.de.xlf b/i18n/de/messages.de.xlf
index eb9c1b175f3..1499d3d797d 100644
--- a/i18n/de/messages.de.xlf
+++ b/i18n/de/messages.de.xlf
@@ -3020,7 +3020,7 @@
Anmelden
../src/app/frontend/chrome/userpanel/template.html
- 37
+ 43
@@ -3029,7 +3029,7 @@
Abmelden
../src/app/frontend/chrome/userpanel/template.html
- 42
+ 48
diff --git a/i18n/fr/messages.fr.xlf b/i18n/fr/messages.fr.xlf
index 906de78b663..e5913aea77b 100644
--- a/i18n/fr/messages.fr.xlf
+++ b/i18n/fr/messages.fr.xlf
@@ -3024,7 +3024,7 @@
Connexion
../src/app/frontend/chrome/userpanel/template.html
- 37
+ 43
@@ -3033,7 +3033,7 @@
Déconnexion
../src/app/frontend/chrome/userpanel/template.html
- 42
+ 48
diff --git a/i18n/ja/messages.ja.xlf b/i18n/ja/messages.ja.xlf
index 18b2d969652..168076caf62 100644
--- a/i18n/ja/messages.ja.xlf
+++ b/i18n/ja/messages.ja.xlf
@@ -2750,7 +2750,7 @@
サインイン
../src/app/frontend/chrome/userpanel/template.html
- 37
+ 43
@@ -2759,7 +2759,7 @@
サインアウト
../src/app/frontend/chrome/userpanel/template.html
- 42
+ 48
diff --git a/i18n/ko/messages.ko.xlf b/i18n/ko/messages.ko.xlf
index 3604d7b9eaa..e5907512fce 100644
--- a/i18n/ko/messages.ko.xlf
+++ b/i18n/ko/messages.ko.xlf
@@ -2805,7 +2805,7 @@
../src/app/frontend/chrome/userpanel/template.html
- 37
+ 43
@@ -2815,7 +2815,7 @@
../src/app/frontend/chrome/userpanel/template.html
- 42
+ 48
diff --git a/i18n/messages.xlf b/i18n/messages.xlf
index 1c756f2669c..6bd31a7a77d 100644
--- a/i18n/messages.xlf
+++ b/i18n/messages.xlf
@@ -2597,7 +2597,7 @@
../src/app/frontend/chrome/userpanel/template.html
- 37
+ 43
@@ -2605,7 +2605,7 @@
../src/app/frontend/chrome/userpanel/template.html
- 42
+ 48
diff --git a/i18n/zh-Hans/messages.zh-Hans.xlf b/i18n/zh-Hans/messages.zh-Hans.xlf
index 55f1470f30a..cfb2ef35293 100644
--- a/i18n/zh-Hans/messages.zh-Hans.xlf
+++ b/i18n/zh-Hans/messages.zh-Hans.xlf
@@ -2805,7 +2805,7 @@
../src/app/frontend/chrome/userpanel/template.html
- 37
+ 43
@@ -2815,7 +2815,7 @@
../src/app/frontend/chrome/userpanel/template.html
- 42
+ 48
diff --git a/i18n/zh-Hant-HK/messages.zh-Hant-HK.xlf b/i18n/zh-Hant-HK/messages.zh-Hant-HK.xlf
index 1b7d45b3445..486122f0d39 100644
--- a/i18n/zh-Hant-HK/messages.zh-Hant-HK.xlf
+++ b/i18n/zh-Hant-HK/messages.zh-Hant-HK.xlf
@@ -2809,7 +2809,7 @@
../src/app/frontend/chrome/userpanel/template.html
- 37
+ 43
@@ -2819,7 +2819,7 @@
../src/app/frontend/chrome/userpanel/template.html
- 42
+ 48
diff --git a/i18n/zh-Hant/messages.zh-Hant.xlf b/i18n/zh-Hant/messages.zh-Hant.xlf
index 5a73c94b167..a821e669739 100644
--- a/i18n/zh-Hant/messages.zh-Hant.xlf
+++ b/i18n/zh-Hant/messages.zh-Hant.xlf
@@ -2809,7 +2809,7 @@
../src/app/frontend/chrome/userpanel/template.html
- 37
+ 43
@@ -2819,7 +2819,7 @@
../src/app/frontend/chrome/userpanel/template.html
- 42
+ 48
diff --git a/package-lock.json b/package-lock.json
index 99e15ade8ec..97aae4417ec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4664,6 +4664,7 @@
"dev": true,
"optional": true,
"requires": {
+ "bindings": "^1.5.0",
"nan": "^2.12.1"
}
},
@@ -6710,7 +6711,6 @@
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
"integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
- "dev": true,
"requires": {
"camelcase": "^5.3.1",
"map-obj": "^4.0.0",
@@ -11331,6 +11331,7 @@
"dev": true,
"optional": true,
"requires": {
+ "bindings": "^1.5.0",
"nan": "^2.12.1"
}
},
@@ -15456,8 +15457,7 @@
"map-obj": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz",
- "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==",
- "dev": true
+ "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g=="
},
"map-visit": {
"version": "1.0.0",
@@ -16207,6 +16207,11 @@
"xmldom": "^0.1.27"
}
},
+ "ngx-webstorage": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ngx-webstorage/-/ngx-webstorage-5.0.0.tgz",
+ "integrity": "sha512-m96dBjUgLCpaknLRKfsJMEik393xrSX0EwO3paNSkS5d+xj2/cAendE3NwJeKY/W1D9EkKAhCvSUDX9/bAwCUg=="
+ },
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -18801,8 +18806,7 @@
"quick-lru": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
- "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
- "dev": true
+ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g=="
},
"ramda": {
"version": "0.26.1",
@@ -20564,6 +20568,7 @@
"dev": true,
"optional": true,
"requires": {
+ "bindings": "^1.5.0",
"nan": "^2.12.1"
}
},
@@ -24313,6 +24318,7 @@
"dev": true,
"optional": true,
"requires": {
+ "bindings": "^1.5.0",
"nan": "^2.12.1"
}
},
diff --git a/package.json b/package.json
index 4bd4b6cc62a..d77d68bab12 100644
--- a/package.json
+++ b/package.json
@@ -117,6 +117,7 @@
"angular-page-visibility": "9.0.0",
"ansi-to-html": "0.6.14",
"c3": "0.7.18",
+ "camelcase-keys": "6.2.2",
"core-js": "3.6.5",
"d3": "5.16.0",
"file-saver": "2.0.2",
@@ -126,6 +127,7 @@
"ng-in-viewport": "6.1.1",
"ngx-cookie-service": "3.0.4",
"ngx-filter-pipe": "2.1.2",
+ "ngx-webstorage": "5.0.0",
"normalize.css": "8.0.1",
"roboto-fontface": "0.10.0",
"rxjs": "6.6.0",
diff --git a/src/app/backend/auth/api/types.go b/src/app/backend/auth/api/types.go
index c9167a45662..7c1be04ed97 100644
--- a/src/app/backend/auth/api/types.go
+++ b/src/app/backend/auth/api/types.go
@@ -29,6 +29,9 @@ const (
// Expiration time (in seconds) of tokens generated by dashboard. Default: 15 min.
DefaultTokenTTL = 900
+
+ // Default user name for AuthInfo. This should be empty string in order not to match with user specified name.
+ DefaultUserName = ""
)
// AuthenticationModes represents auth modes supported by dashboard.
@@ -111,8 +114,8 @@ type TokenManager interface {
// - Kubeconfig based - Authenticates user based on kubeconfig file. Only token/basic modes are supported within
// the kubeconfig file.
type Authenticator interface {
- // GetAuthInfo returns filled AuthInfo structure that can be used for K8S api client creation.
- GetAuthInfo() (api.AuthInfo, error)
+ // GetAuthInfos returns filled AuthInfo structures that can be used for K8S api client creation.
+ GetAuthInfos() (map[string]api.AuthInfo, error)
}
// LoginSpec is extracted from request coming from Dashboard frontend during login request. It contains all the
@@ -127,13 +130,17 @@ type LoginSpec struct {
// KubeConfig is the content of users' kubeconfig file. It will be parsed and auth data will be extracted.
// Kubeconfig can not contain any paths. All data has to be provided within the file.
KubeConfig string `json:"kubeconfig,omitempty"`
+ // Server is API server endpoint
+ Server string `json:"server,omitempty"`
+ // CertificateAuthorityData is CA data for API server endpoint
+ CertificateAuthorityData string `json:"certificateAuthorityData,omitempty"`
}
// AuthResponse is returned from our backend as a response for login/refresh requests. It contains generated JWEToken
// and a list of non-critical errors such as 'Failed authentication'.
type AuthResponse struct {
- // JWEToken is a token generated during login request that contains AuthInfo data in the payload.
- JWEToken string `json:"jweToken"`
+ // JWETokens are tokens generated during login request that contains AuthInfo data in the payload.
+ JWETokens map[string]string `json:"jweTokens"`
// Errors are a list of non-critical errors that happened during login request.
Errors []error `json:"errors"`
}
@@ -141,7 +148,7 @@ type AuthResponse struct {
// TokenRefreshSpec contains token that is required by token refresh operation.
type TokenRefreshSpec struct {
// JWEToken is a token generated during login request that contains AuthInfo data in the payload.
- JWEToken string `json:"jweToken"`
+ JWETokens map[string]string `json:"jweTokens"`
}
// LoginModesResponse contains list of auth modes supported by dashboard.
diff --git a/src/app/backend/auth/basic.go b/src/app/backend/auth/basic.go
index 884e9abb9cd..cf6c2b94253 100644
--- a/src/app/backend/auth/basic.go
+++ b/src/app/backend/auth/basic.go
@@ -26,10 +26,12 @@ type basicAuthenticator struct {
}
// GetAuthInfo implements Authenticator interface. See Authenticator for more information.
-func (self *basicAuthenticator) GetAuthInfo() (api.AuthInfo, error) {
- return api.AuthInfo{
- Username: self.username,
- Password: self.password,
+func (self *basicAuthenticator) GetAuthInfos() (map[string]api.AuthInfo, error) {
+ return map[string]api.AuthInfo{
+ authApi.DefaultUserName: {
+ Username: self.username,
+ Password: self.password,
+ },
}, nil
}
diff --git a/src/app/backend/auth/handler.go b/src/app/backend/auth/handler.go
index ebc3cc0f680..702456c2904 100644
--- a/src/app/backend/auth/handler.go
+++ b/src/app/backend/auth/handler.go
@@ -16,6 +16,7 @@ package auth
import (
"net/http"
+ "net/url"
"github.com/emicklei/go-restful"
@@ -64,6 +65,21 @@ func (self AuthHandler) handleLogin(request *restful.Request, response *restful.
return
}
+ cookie, err := request.Request.Cookie("server")
+ if err == nil && len(cookie.Value) > 0 {
+ server, err := url.QueryUnescape(cookie.Value)
+ if err == nil {
+ loginSpec.Server = server
+ }
+ }
+ cookie, err = request.Request.Cookie("certificateAuthorityData")
+ if err == nil && len(cookie.Value) > 0 {
+ ca, err := url.QueryUnescape(cookie.Value)
+ if err == nil {
+ loginSpec.CertificateAuthorityData = ca
+ }
+ }
+
loginResponse, err := self.manager.Login(loginSpec)
if err != nil {
response.AddHeader("Content-Type", "text/plain")
@@ -86,16 +102,20 @@ func (self *AuthHandler) handleJWETokenRefresh(request *restful.Request, respons
return
}
- refreshedJWEToken, err := self.manager.Refresh(tokenRefreshSpec.JWEToken)
- if err != nil {
- response.AddHeader("Content-Type", "text/plain")
- response.WriteErrorString(errors.HandleHTTPError(err), err.Error()+"\n")
- return
+ refreshedJWETokens := map[string]string{}
+ for userName, token := range tokenRefreshSpec.JWETokens {
+ refreshedJWEToken, err := self.manager.Refresh(token)
+ if err != nil {
+ response.AddHeader("Content-Type", "text/plain")
+ response.WriteErrorString(errors.HandleHTTPError(err), err.Error()+"\n")
+ return
+ }
+ refreshedJWETokens[userName] = refreshedJWEToken
}
response.WriteHeaderAndEntity(http.StatusOK, &authApi.AuthResponse{
- JWEToken: refreshedJWEToken,
- Errors: make([]error, 0),
+ JWETokens: refreshedJWETokens,
+ Errors: make([]error, 0),
})
}
diff --git a/src/app/backend/auth/kubeconfig.go b/src/app/backend/auth/kubeconfig.go
index 035d93a92d2..b90b5eabc6d 100644
--- a/src/app/backend/auth/kubeconfig.go
+++ b/src/app/backend/auth/kubeconfig.go
@@ -67,18 +67,18 @@ type kubeConfigAuthenticator struct {
}
// GetAuthInfo implements Authenticator interface. See Authenticator for more information.
-func (self *kubeConfigAuthenticator) GetAuthInfo() (api.AuthInfo, error) {
+func (self *kubeConfigAuthenticator) GetAuthInfos() (map[string]api.AuthInfo, error) {
kubeConfig, err := self.parseKubeConfig(self.fileContent)
if err != nil {
- return api.AuthInfo{}, err
+ return map[string]api.AuthInfo{}, err
}
- info, err := self.getCurrentUserInfo(*kubeConfig)
+ infos, err := self.getUserInfos(*kubeConfig)
if err != nil {
- return api.AuthInfo{}, err
+ return map[string]api.AuthInfo{}, err
}
- return self.getAuthInfo(info)
+ return self.getAuthInfos(infos)
}
// Parses kubeconfig file and returns kubeConfig object.
@@ -91,50 +91,63 @@ func (self *kubeConfigAuthenticator) parseKubeConfig(bytes []byte) (*kubeConfig,
return kubeConfig, nil
}
-// Returns user info based on defined current context. In case it is not found error is returned.
-func (self *kubeConfigAuthenticator) getCurrentUserInfo(config kubeConfig) (userInfo, error) {
- userName := ""
+// Returns user infos. User info for current context would be set at first. In case it is not found error is returned.
+func (self *kubeConfigAuthenticator) getUserInfos(config kubeConfig) (map[string]userInfo, error) {
+ currentUserName := ""
for _, context := range config.Contexts {
if context.Name == config.CurrentContext {
- userName = context.Context.User
+ currentUserName = context.Context.User
}
}
- if len(userName) == 0 {
- return userInfo{}, errors.NewInvalid("Context matching current context not found. Check if your config file is valid.")
+ if len(currentUserName) == 0 {
+ return map[string]userInfo{}, errors.NewInvalid("Context matching current context not found. Check if your config file is valid.")
}
+ currentUser := false
+ userInfos := make(map[string]userInfo, len(config.Users))
for _, user := range config.Users {
- if user.Name == userName {
- return user.User, nil
+ userName := user.Name
+ if userName == currentUserName {
+ currentUser = true
+ userName = authApi.DefaultUserName
}
+ userInfos[userName] = user.User
}
- return userInfo{}, errors.NewInvalid("User matching current context user not found. Check if your config file is valid.")
+ if !currentUser {
+ return map[string]userInfo{}, errors.NewInvalid("User matching current context user not found. Check if your config file is valid.")
+ }
+
+ return userInfos, nil
}
// Returns auth info structure based on provided user info or error in case not enough data has been provided.
-func (self *kubeConfigAuthenticator) getAuthInfo(info userInfo) (api.AuthInfo, error) {
- // If "token" is empty for the current "user" entry, fallback to the value of "auth-provider.config.access-token".
- if len(info.Token) == 0 {
- info.Token = info.AuthProvider.Config.AccessToken
- }
+func (self *kubeConfigAuthenticator) getAuthInfos(infos map[string]userInfo) (map[string]api.AuthInfo, error) {
+ results := map[string]api.AuthInfo{}
+ for userName, info := range infos {
+ // If "token" is empty for the current "user" entry, fallback to the value of "auth-provider.config.access-token".
+ if len(info.Token) == 0 {
+ info.Token = info.AuthProvider.Config.AccessToken
+ }
- if len(info.Token) == 0 && (len(info.Password) == 0 || len(info.Username) == 0) {
- return api.AuthInfo{}, errors.NewInvalid("Not enough data to create auth info structure.")
- }
+ if len(info.Token) == 0 && (len(info.Password) == 0 || len(info.Username) == 0) {
+ return map[string]api.AuthInfo{}, errors.NewInvalid("Not enough data to create auth info structure.")
+ }
- result := api.AuthInfo{}
- if self.authModes.IsEnabled(authApi.Token) {
- result.Token = info.Token
- }
+ result := api.AuthInfo{}
+ if self.authModes.IsEnabled(authApi.Token) {
+ result.Token = info.Token
+ }
- if self.authModes.IsEnabled(authApi.Basic) {
- result.Username = info.Username
- result.Password = info.Password
+ if self.authModes.IsEnabled(authApi.Basic) {
+ result.Username = info.Username
+ result.Password = info.Password
+ }
+ results[userName] = result
}
- return result, nil
+ return results, nil
}
// NewBasicAuthenticator returns Authenticator based on LoginSpec.
diff --git a/src/app/backend/auth/kubeconfig_test.go b/src/app/backend/auth/kubeconfig_test.go
index be494361ed3..879eefe7384 100644
--- a/src/app/backend/auth/kubeconfig_test.go
+++ b/src/app/backend/auth/kubeconfig_test.go
@@ -75,35 +75,35 @@ func TestKubeConfigAuthenticator(t *testing.T) {
info string
authModes authApi.AuthenticationModes
params map[string]string
- expected api.AuthInfo
+ expected map[string]api.AuthInfo
expectedErr error
}{
{
`If "token" is empty for the current "user" entry, the value of "auth-provider.config.access-token" is picked up.`,
authModeToken,
map[string]string{"accessToken": "foo", "token": ""},
- api.AuthInfo{Token: "foo"},
+ map[string]api.AuthInfo{"": api.AuthInfo{Token: "foo"}},
nil,
},
{
`If "token" is provided for the current "user" entry, that token is picked up instead.`,
authModeToken,
map[string]string{"accessToken": "foo", "token": "bar"},
- api.AuthInfo{Token: "bar"},
+ map[string]api.AuthInfo{"": api.AuthInfo{Token: "bar"}},
nil,
},
{
`If the "basic" auth mode is enabled, "username" and "password" are picked up.`,
authModeBasic,
map[string]string{"username": "foo", "password": "bar"},
- api.AuthInfo{Username: "foo", Password: "bar"},
+ map[string]api.AuthInfo{"": api.AuthInfo{Username: "foo", Password: "bar"}},
nil,
},
{
`If no value for "token", "username" or "password" is provided or can be inferred, an error is returned.`,
authModeBoth,
map[string]string{},
- api.AuthInfo{},
+ map[string]api.AuthInfo{},
errors.NewInvalid("Not enough data to create auth info structure."),
},
}
@@ -115,7 +115,7 @@ func TestKubeConfigAuthenticator(t *testing.T) {
}
kubeConfigAuthenticator := NewKubeConfigAuthenticator(&authApi.LoginSpec{KubeConfig: kb.String()}, c.authModes)
- response, err := kubeConfigAuthenticator.GetAuthInfo()
+ response, err := kubeConfigAuthenticator.GetAuthInfos()
if !areErrorsEqual(err, c.expectedErr) {
t.Errorf("Test Case: %s. Expected error to be: %v, but got %v.",
diff --git a/src/app/backend/auth/manager.go b/src/app/backend/auth/manager.go
index 7991ca3eba7..8391a54b1e9 100644
--- a/src/app/backend/auth/manager.go
+++ b/src/app/backend/auth/manager.go
@@ -37,23 +37,28 @@ func (self authManager) Login(spec *authApi.LoginSpec) (*authApi.AuthResponse, e
return nil, err
}
- authInfo, err := authenticator.GetAuthInfo()
+ authInfos, err := authenticator.GetAuthInfos()
if err != nil {
return nil, err
}
- err = self.healthCheck(authInfo)
+ // AuthInfo for current user is set in entry with "" key.
+ err = self.healthCheck(authInfos[authApi.DefaultUserName], spec.Server, spec.CertificateAuthorityData)
nonCriticalErrors, criticalError := errors.HandleError(err)
if criticalError != nil || len(nonCriticalErrors) > 0 {
return &authApi.AuthResponse{Errors: nonCriticalErrors}, criticalError
}
- token, err := self.tokenManager.Generate(authInfo)
- if err != nil {
- return nil, err
+ tokens := map[string]string{}
+ for userName, authInfo := range authInfos {
+ token, err := self.tokenManager.Generate(authInfo)
+ if err != nil {
+ return nil, err
+ }
+ tokens[userName] = token
}
- return &authApi.AuthResponse{JWEToken: token, Errors: nonCriticalErrors}, nil
+ return &authApi.AuthResponse{JWETokens: tokens, Errors: nonCriticalErrors}, nil
}
// Refresh implements auth manager. See AuthManager interface for more information.
@@ -89,8 +94,8 @@ func (self authManager) getAuthenticator(spec *authApi.LoginSpec) (authApi.Authe
// Checks if user data extracted from provided AuthInfo structure is valid and user is correctly authenticated
// by K8S apiserver.
-func (self authManager) healthCheck(authInfo api.AuthInfo) error {
- return self.clientManager.HasAccess(authInfo)
+func (self authManager) healthCheck(authInfo api.AuthInfo, server string, caData string) error {
+ return self.clientManager.HasAccess(authInfo, server, caData)
}
// NewAuthManager creates auth manager.
diff --git a/src/app/backend/auth/manager_test.go b/src/app/backend/auth/manager_test.go
index 729e5bc5835..5ddbeeebdc2 100644
--- a/src/app/backend/auth/manager_test.go
+++ b/src/app/backend/auth/manager_test.go
@@ -82,7 +82,7 @@ func (self *fakeClientManager) CSRFKey() string {
return ""
}
-func (self *fakeClientManager) HasAccess(authInfo api.AuthInfo) error {
+func (self *fakeClientManager) HasAccess(authInfo api.AuthInfo, server string, caData string) error {
return self.HasAccessError
}
@@ -143,7 +143,7 @@ func TestAuthManager_Login(t *testing.T) {
&authApi.LoginSpec{Token: "existing-token"},
&fakeClientManager{HasAccessError: nil},
&fakeTokenManager{GeneratedToken: "generated-token"},
- &authApi.AuthResponse{JWEToken: "generated-token", Errors: make([]error, 0)},
+ &authApi.AuthResponse{JWETokens: map[string]string{"": "generated-token"}, Errors: make([]error, 0)},
nil,
}, {
"Should propagate error on unexpected error",
diff --git a/src/app/backend/auth/token.go b/src/app/backend/auth/token.go
index f25bc2a632d..afc6c0b2178 100644
--- a/src/app/backend/auth/token.go
+++ b/src/app/backend/auth/token.go
@@ -25,9 +25,11 @@ type tokenAuthenticator struct {
}
// GetAuthInfo implements Authenticator interface. See Authenticator for more information.
-func (self tokenAuthenticator) GetAuthInfo() (api.AuthInfo, error) {
- return api.AuthInfo{
- Token: self.token,
+func (self tokenAuthenticator) GetAuthInfos() (map[string]api.AuthInfo, error) {
+ return map[string]api.AuthInfo{
+ authApi.DefaultUserName: {
+ Token: self.token,
+ },
}, nil
}
diff --git a/src/app/backend/client/api/types.go b/src/app/backend/client/api/types.go
index 862709aa78f..31e675b1c7a 100644
--- a/src/app/backend/client/api/types.go
+++ b/src/app/backend/client/api/types.go
@@ -49,7 +49,7 @@ type ClientManager interface {
Config(req *restful.Request) (*rest.Config, error)
ClientCmdConfig(req *restful.Request) (clientcmd.ClientConfig, error)
CSRFKey() string
- HasAccess(authInfo api.AuthInfo) error
+ HasAccess(authInfo api.AuthInfo, server string, caData string) error
VerberClient(req *restful.Request, config *rest.Config) (ResourceVerber, error)
SetTokenManager(manager authApi.TokenManager)
}
diff --git a/src/app/backend/client/manager.go b/src/app/backend/client/manager.go
index bbc86392327..33659f6046b 100644
--- a/src/app/backend/client/manager.go
+++ b/src/app/backend/client/manager.go
@@ -16,7 +16,9 @@ package client
import (
"context"
+ "encoding/base64"
"log"
+ "net/url"
"strings"
"github.com/emicklei/go-restful"
@@ -208,7 +210,24 @@ func (self *clientManager) ClientCmdConfig(req *restful.Request) (clientcmd.Clie
return nil, err
}
- cfg, err := self.buildConfigFromFlags(self.apiserverHost, self.kubeConfigPath)
+ server := self.apiserverHost
+ cookie, err := req.Request.Cookie("server")
+ if err == nil && len(cookie.Value) > 0 {
+ sv, err := url.QueryUnescape(cookie.Value)
+ if err == nil {
+ server = sv
+ }
+ }
+ caData := ""
+ cookie, err = req.Request.Cookie("certificateAuthorityData")
+ if err == nil && len(cookie.Value) > 0 {
+ ca, err := url.QueryUnescape(cookie.Value)
+ if err == nil {
+ caData = ca
+ }
+ }
+
+ cfg, err := self.buildConfigFromFlags(server, caData, self.kubeConfigPath)
if err != nil {
return nil, err
}
@@ -223,8 +242,8 @@ func (self *clientManager) CSRFKey() string {
// HasAccess configures K8S api client with provided auth info and executes a basic check against apiserver to see
// if it is valid.
-func (self *clientManager) HasAccess(authInfo api.AuthInfo) error {
- cfg, err := self.buildConfigFromFlags(self.apiserverHost, self.kubeConfigPath)
+func (self *clientManager) HasAccess(authInfo api.AuthInfo, server string, caData string) error {
+ cfg, err := self.buildConfigFromFlags(server, caData, self.kubeConfigPath)
if err != nil {
return err
}
@@ -295,12 +314,16 @@ func (self *clientManager) initConfig(cfg *rest.Config) {
// Returns rest Config based on provided apiserverHost and kubeConfigPath flags. If both are
// empty then in-cluster config will be used and if it is nil the error is returned.
-func (self *clientManager) buildConfigFromFlags(apiserverHost, kubeConfigPath string) (
+func (self *clientManager) buildConfigFromFlags(apiserverHost string, caData string, kubeConfigPath string) (
*rest.Config, error) {
if len(kubeConfigPath) > 0 || len(apiserverHost) > 0 {
+ decodedCAData := []byte{}
+ if len(caData) > 0 {
+ decodedCAData, _ = base64.StdEncoding.DecodeString(caData)
+ }
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeConfigPath},
- &clientcmd.ConfigOverrides{ClusterInfo: api.Cluster{Server: apiserverHost}}).ClientConfig()
+ &clientcmd.ConfigOverrides{ClusterInfo: api.Cluster{Server: apiserverHost, CertificateAuthorityData: decodedCAData}}).ClientConfig()
}
if self.isRunningInCluster() {
@@ -525,7 +548,7 @@ func (self *clientManager) initInsecureClients() {
}
func (self *clientManager) initInsecureConfig() {
- cfg, err := self.buildConfigFromFlags(self.apiserverHost, self.kubeConfigPath)
+ cfg, err := self.buildConfigFromFlags(self.apiserverHost, "", self.kubeConfigPath)
if err != nil {
panic(err)
}
diff --git a/src/app/backend/handler/apihandler.go b/src/app/backend/handler/apihandler.go
index 343d4dc9c8e..06c505d1e81 100644
--- a/src/app/backend/handler/apihandler.go
+++ b/src/app/backend/handler/apihandler.go
@@ -809,7 +809,7 @@ func (apiHandler *APIHandler) handleGetStatefulSetList(request *restful.Request,
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
result, err := statefulset.GetStatefulSetList(k8sClient, namespace, dataSelect,
- apiHandler.iManager.Metric().Client())
+ apiHandler.iManager.Metric().Client(request))
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -826,7 +826,7 @@ func (apiHandler *APIHandler) handleGetStatefulSetDetail(request *restful.Reques
namespace := request.PathParameter("namespace")
name := request.PathParameter("statefulset")
- result, err := statefulset.GetStatefulSetDetail(k8sClient, apiHandler.iManager.Metric().Client(), namespace, name)
+ result, err := statefulset.GetStatefulSetDetail(k8sClient, apiHandler.iManager.Metric().Client(request), namespace, name)
if err != nil {
errors.HandleInternalError(response, err)
@@ -846,7 +846,7 @@ func (apiHandler *APIHandler) handleGetStatefulSetPods(request *restful.Request,
name := request.PathParameter("statefulset")
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := statefulset.GetStatefulSetPods(k8sClient, apiHandler.iManager.Metric().Client(), dataSelect, name, namespace)
+ result, err := statefulset.GetStatefulSetPods(k8sClient, apiHandler.iManager.Metric().Client(request), dataSelect, name, namespace)
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1040,7 +1040,7 @@ func (apiHandler *APIHandler) handleGetServicePods(request *restful.Request, res
name := request.PathParameter("service")
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := resourceService.GetServicePods(k8sClient, apiHandler.iManager.Metric().Client(), namespace, name, dataSelect)
+ result, err := resourceService.GetServicePods(k8sClient, apiHandler.iManager.Metric().Client(request), namespace, name, dataSelect)
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1057,7 +1057,7 @@ func (apiHandler *APIHandler) handleGetNodeList(request *restful.Request, respon
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := node.GetNodeList(k8sClient, dataSelect, apiHandler.iManager.Metric().Client())
+ result, err := node.GetNodeList(k8sClient, dataSelect, apiHandler.iManager.Metric().Client(request))
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1075,7 +1075,7 @@ func (apiHandler *APIHandler) handleGetNodeDetail(request *restful.Request, resp
name := request.PathParameter("name")
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := node.GetNodeDetail(k8sClient, apiHandler.iManager.Metric().Client(), name, dataSelect)
+ result, err := node.GetNodeDetail(k8sClient, apiHandler.iManager.Metric().Client(request), name, dataSelect)
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1111,7 +1111,7 @@ func (apiHandler *APIHandler) handleGetNodePods(request *restful.Request, respon
name := request.PathParameter("name")
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := node.GetNodePods(k8sClient, apiHandler.iManager.Metric().Client(), dataSelect, name)
+ result, err := node.GetNodePods(k8sClient, apiHandler.iManager.Metric().Client(request), dataSelect, name)
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1266,7 +1266,7 @@ func (apiHandler *APIHandler) handleGetReplicationControllerList(request *restfu
namespace := parseNamespacePathParameter(request)
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := replicationcontroller.GetReplicationControllerList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client())
+ result, err := replicationcontroller.GetReplicationControllerList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client(request))
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1284,7 +1284,7 @@ func (apiHandler *APIHandler) handleGetReplicaSets(request *restful.Request, res
namespace := parseNamespacePathParameter(request)
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := replicaset.GetReplicaSetList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client())
+ result, err := replicaset.GetReplicaSetList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client(request))
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1301,7 +1301,7 @@ func (apiHandler *APIHandler) handleGetReplicaSetDetail(request *restful.Request
namespace := request.PathParameter("namespace")
replicaSet := request.PathParameter("replicaSet")
- result, err := replicaset.GetReplicaSetDetail(k8sClient, apiHandler.iManager.Metric().Client(), namespace, replicaSet)
+ result, err := replicaset.GetReplicaSetDetail(k8sClient, apiHandler.iManager.Metric().Client(request), namespace, replicaSet)
if err != nil {
errors.HandleInternalError(response, err)
@@ -1322,7 +1322,7 @@ func (apiHandler *APIHandler) handleGetReplicaSetPods(request *restful.Request,
replicaSet := request.PathParameter("replicaSet")
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := replicaset.GetReplicaSetPods(k8sClient, apiHandler.iManager.Metric().Client(), dataSelect, replicaSet, namespace)
+ result, err := replicaset.GetReplicaSetPods(k8sClient, apiHandler.iManager.Metric().Client(request), dataSelect, replicaSet, namespace)
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1430,7 +1430,7 @@ func (apiHandler *APIHandler) handleGetDeployments(request *restful.Request, res
namespace := parseNamespacePathParameter(request)
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := deployment.GetDeploymentList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client())
+ result, err := deployment.GetDeploymentList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client(request))
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1522,7 +1522,7 @@ func (apiHandler *APIHandler) handleGetPods(request *restful.Request, response *
namespace := parseNamespacePathParameter(request)
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics // download standard metrics - cpu, and memory - by default
- result, err := pod.GetPodList(k8sClient, apiHandler.iManager.Metric().Client(), namespace, dataSelect)
+ result, err := pod.GetPodList(k8sClient, apiHandler.iManager.Metric().Client(request), namespace, dataSelect)
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1539,7 +1539,7 @@ func (apiHandler *APIHandler) handleGetPodDetail(request *restful.Request, respo
namespace := request.PathParameter("namespace")
name := request.PathParameter("pod")
- result, err := pod.GetPodDetail(k8sClient, apiHandler.iManager.Metric().Client(), namespace, name)
+ result, err := pod.GetPodDetail(k8sClient, apiHandler.iManager.Metric().Client(request), namespace, name)
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1698,7 +1698,7 @@ func (apiHandler *APIHandler) handleGetReplicationControllerPods(request *restfu
rc := request.PathParameter("replicationController")
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := replicationcontroller.GetReplicationControllerPods(k8sClient, apiHandler.iManager.Metric().Client(), dataSelect, rc, namespace)
+ result, err := replicationcontroller.GetReplicationControllerPods(k8sClient, apiHandler.iManager.Metric().Client(request), dataSelect, rc, namespace)
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -1992,7 +1992,7 @@ func (apiHandler *APIHandler) handleGetDaemonSetList(request *restful.Request, r
namespace := parseNamespacePathParameter(request)
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := daemonset.GetDaemonSetList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client())
+ result, err := daemonset.GetDaemonSetList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client(request))
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -2010,7 +2010,7 @@ func (apiHandler *APIHandler) handleGetDaemonSetDetail(
namespace := request.PathParameter("namespace")
name := request.PathParameter("daemonSet")
- result, err := daemonset.GetDaemonSetDetail(k8sClient, apiHandler.iManager.Metric().Client(), namespace, name)
+ result, err := daemonset.GetDaemonSetDetail(k8sClient, apiHandler.iManager.Metric().Client(request), namespace, name)
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -2029,7 +2029,7 @@ func (apiHandler *APIHandler) handleGetDaemonSetPods(request *restful.Request, r
name := request.PathParameter("daemonSet")
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := daemonset.GetDaemonSetPods(k8sClient, apiHandler.iManager.Metric().Client(), dataSelect, name, namespace)
+ result, err := daemonset.GetDaemonSetPods(k8sClient, apiHandler.iManager.Metric().Client(request), dataSelect, name, namespace)
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -2137,7 +2137,7 @@ func (apiHandler *APIHandler) handleGetJobList(request *restful.Request, respons
namespace := parseNamespacePathParameter(request)
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := job.GetJobList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client())
+ result, err := job.GetJobList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client(request))
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -2173,7 +2173,7 @@ func (apiHandler *APIHandler) handleGetJobPods(request *restful.Request, respons
name := request.PathParameter("name")
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := job.GetJobPods(k8sClient, apiHandler.iManager.Metric().Client(), dataSelect, namespace, name)
+ result, err := job.GetJobPods(k8sClient, apiHandler.iManager.Metric().Client(request), dataSelect, namespace, name)
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -2209,7 +2209,7 @@ func (apiHandler *APIHandler) handleGetCronJobList(request *restful.Request, res
namespace := parseNamespacePathParameter(request)
dataSelect := parser.ParseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics
- result, err := cronjob.GetCronJobList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client())
+ result, err := cronjob.GetCronJobList(k8sClient, namespace, dataSelect, apiHandler.iManager.Metric().Client(request))
if err != nil {
errors.HandleInternalError(response, err)
return
@@ -2249,7 +2249,7 @@ func (apiHandler *APIHandler) handleGetCronJobJobs(request *restful.Request, res
}
dataSelect := parser.ParseDataSelectPathParameter(request)
- result, err := cronjob.GetCronJobJobs(k8sClient, apiHandler.iManager.Metric().Client(), dataSelect, namespace, name, active)
+ result, err := cronjob.GetCronJobJobs(k8sClient, apiHandler.iManager.Metric().Client(request), dataSelect, namespace, name, active)
if err != nil {
errors.HandleInternalError(response, err)
return
diff --git a/src/app/backend/integration/metric/manager.go b/src/app/backend/integration/metric/manager.go
index 2824d71ca6c..d110b800cad 100644
--- a/src/app/backend/integration/metric/manager.go
+++ b/src/app/backend/integration/metric/manager.go
@@ -17,8 +17,11 @@ package metric
import (
"fmt"
"log"
+ "net/url"
"time"
+ "github.com/emicklei/go-restful"
+
clientapi "github.com/kubernetes/dashboard/src/app/backend/client/api"
integrationapi "github.com/kubernetes/dashboard/src/app/backend/integration/api"
metricapi "github.com/kubernetes/dashboard/src/app/backend/integration/metric/api"
@@ -32,7 +35,7 @@ type MetricManager interface {
// AddClient adds metric client to client list supported by this manager.
AddClient(metricapi.MetricClient) MetricManager
// Client returns active Metric client.
- Client() metricapi.MetricClient
+ Client(*restful.Request) metricapi.MetricClient
// Enable is responsible for switching active client if given integration application id
// is found and related application is healthy (we can connect to it).
Enable(integrationapi.IntegrationID) error
@@ -64,7 +67,21 @@ func (self *metricManager) AddClient(client metricapi.MetricClient) MetricManage
}
// Client implements metric manager interface. See MetricManager for more information.
-func (self *metricManager) Client() metricapi.MetricClient {
+func (self *metricManager) Client(request *restful.Request) metricapi.MetricClient {
+ // Create client dynamically if `sidecarHost` is set in cookie.
+ cookie, err := request.Request.Cookie("sidecarHost")
+ if err == nil && len(cookie.Value) > 0 {
+ sidecarHost, err := url.QueryUnescape(cookie.Value)
+ if err == nil {
+ metricClient, err := sidecar.CreateSidecarClient(sidecarHost, nil)
+ if err == nil {
+ return metricClient
+ }
+ }
+ log.Printf("There was an error during sidecar client dynamic creation: %s", err.Error())
+ return nil
+ }
+
return self.active
}
diff --git a/src/app/backend/integration/metric/manager_test.go b/src/app/backend/integration/metric/manager_test.go
index 678d8523b0d..768e8feb8c4 100644
--- a/src/app/backend/integration/metric/manager_test.go
+++ b/src/app/backend/integration/metric/manager_test.go
@@ -15,9 +15,11 @@
package metric
import (
+ "net/http"
"reflect"
"testing"
+ restful "github.com/emicklei/go-restful"
"github.com/kubernetes/dashboard/src/app/backend/client"
"github.com/kubernetes/dashboard/src/app/backend/errors"
integrationapi "github.com/kubernetes/dashboard/src/app/backend/integration/api"
@@ -71,18 +73,35 @@ func TestNewMetricManager(t *testing.T) {
func TestMetricManager_Client(t *testing.T) {
cases := []struct {
+ request *restful.Request
client api.MetricClient
expected api.MetricClient
}{
- {&FakeMetricClient{healthOk: false}, nil},
- {&FakeMetricClient{healthOk: true}, &FakeMetricClient{healthOk: true}},
+ {
+ &restful.Request{
+ Request: &http.Request{
+ Header: http.Header(map[string][]string{}),
+ },
+ },
+ &FakeMetricClient{healthOk: false},
+ nil,
+ },
+ {
+ &restful.Request{
+ Request: &http.Request{
+ Header: http.Header(map[string][]string{}),
+ },
+ },
+ &FakeMetricClient{healthOk: true},
+ &FakeMetricClient{healthOk: true},
+ },
}
for _, c := range cases {
metricManager := NewMetricManager(nil)
metricManager.AddClient(c.client)
metricManager.Enable(fakeMetricClientID)
- client := metricManager.Client()
+ client := metricManager.Client(c.request)
if !reflect.DeepEqual(client, c.expected) {
t.Errorf("Failed to get active metric client. Expected: %v, but got %v.",
diff --git a/src/app/backend/plugin/config_test.go b/src/app/backend/plugin/config_test.go
index fc0989a5554..8e42eb1a156 100644
--- a/src/app/backend/plugin/config_test.go
+++ b/src/app/backend/plugin/config_test.go
@@ -119,7 +119,7 @@ func (cm *fakeClientManager) CSRFKey() string {
panic("implement me")
}
-func (cm *fakeClientManager) HasAccess(authInfo api.AuthInfo) error {
+func (cm *fakeClientManager) HasAccess(authInfo api.AuthInfo, server string, caData string) error {
panic("implement me")
}
diff --git a/src/app/frontend/chrome/userpanel/component.ts b/src/app/frontend/chrome/userpanel/component.ts
index 7c8f093e978..79d8b511421 100644
--- a/src/app/frontend/chrome/userpanel/component.ts
+++ b/src/app/frontend/chrome/userpanel/component.ts
@@ -15,6 +15,7 @@
import {Component, OnInit} from '@angular/core';
import {LoginStatus} from '@api/backendapi';
import {AuthService} from '../../common/services/global/authentication';
+import {KubeconfigService} from '../../common/services/global/kubeconfig';
@Component({
selector: 'kd-user-panel',
@@ -27,14 +28,18 @@ import {AuthService} from '../../common/services/global/authentication';
export class UserPanelComponent implements OnInit {
loginStatus: LoginStatus;
isLoginStatusInitialized = false;
+ currentContext: string;
+ contexts: string[];
- constructor(private readonly authService_: AuthService) {}
+ constructor(private readonly authService_: AuthService, private readonly kubeconfigService_: KubeconfigService) {}
ngOnInit(): void {
this.authService_.getLoginStatus().subscribe(status => {
this.loginStatus = status;
this.isLoginStatusInitialized = true;
});
+ this.contexts = this.kubeconfigService_.getContexts();
+ this.currentContext = this.kubeconfigService_.getCurrentContext();
}
isAuthSkipped(): boolean {
@@ -49,6 +54,15 @@ export class UserPanelComponent implements OnInit {
return this.loginStatus ? this.loginStatus.httpsMode : false;
}
+ isCurrentContext(context: string): boolean {
+ this.currentContext = this.kubeconfigService_.getCurrentContext();
+ return context === this.currentContext;
+ }
+
+ switchContext(context: string): void {
+ this.kubeconfigService_.switchContext(context);
+ }
+
logout(): void {
this.authService_.logout();
}
diff --git a/src/app/frontend/chrome/userpanel/template.html b/src/app/frontend/chrome/userpanel/template.html
index b26bbf14e47..39279a06167 100644
--- a/src/app/frontend/chrome/userpanel/template.html
+++ b/src/app/frontend/chrome/userpanel/template.html
@@ -30,6 +30,12 @@
+
+
-