diff --git a/backend/cmd/headlamp.go b/backend/cmd/headlamp.go index 24504a8ab38..00e5677c5f5 100644 --- a/backend/cmd/headlamp.go +++ b/backend/cmd/headlamp.go @@ -97,6 +97,7 @@ const ( type clientConfig struct { Clusters []Cluster `json:"clusters"` IsDynamicClusterEnabled bool `json:"isDynamicClusterEnabled"` + PrometheusEndpoint string `json:"prometheusEndpoint"` } type OauthConfig struct { @@ -1750,7 +1751,7 @@ func parseClusterFromKubeConfig(kubeConfigs []string) ([]Cluster, []error) { func (c *HeadlampConfig) getConfig(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - clientConfig := clientConfig{c.getClusters(), c.EnableDynamicClusters} + clientConfig := clientConfig{c.getClusters(), c.EnableDynamicClusters, c.PrometheusEndpoint} if err := json.NewEncoder(w).Encode(&clientConfig); err != nil { logger.Log(logger.LevelError, nil, err, "encoding config") diff --git a/backend/cmd/server.go b/backend/cmd/server.go index a17bbb5d655..07d232f055a 100644 --- a/backend/cmd/server.go +++ b/backend/cmd/server.go @@ -101,6 +101,7 @@ func buildHeadlampCFG(conf *config.Config, kubeConfigStore kubeconfig.ContextSto TLSCertPath: conf.TLSCertPath, TLSKeyPath: conf.TLSKeyPath, SessionTTL: conf.SessionTTL, + PrometheusEndpoint: *conf.PrometheusEndpoint, } } diff --git a/backend/cmd/stateless.go b/backend/cmd/stateless.go index a7d395c23cd..8fdd357511f 100644 --- a/backend/cmd/stateless.go +++ b/backend/cmd/stateless.go @@ -178,7 +178,7 @@ func (c *HeadlampConfig) parseKubeConfig(w http.ResponseWriter, r *http.Request) return } - clientConfig := clientConfig{contexts, c.EnableDynamicClusters} + clientConfig := clientConfig{contexts, c.EnableDynamicClusters, c.PrometheusEndpoint} if err := json.NewEncoder(w).Encode(&clientConfig); err != nil { logger.Log(logger.LevelError, nil, err, "encoding config") diff --git a/backend/pkg/config/config.go b/backend/pkg/config/config.go index 8aca9c8db2e..36bc1b45d73 100644 --- a/backend/pkg/config/config.go +++ b/backend/pkg/config/config.go @@ -83,8 +83,9 @@ type Config struct { StdoutTraceEnabled *bool `koanf:"stdout-trace-enabled"` SamplingRate *float64 `koanf:"sampling-rate"` // TLS config - TLSCertPath string `koanf:"tls-cert-path"` - TLSKeyPath string `koanf:"tls-key-path"` + TLSCertPath string `koanf:"tls-cert-path"` + TLSKeyPath string `koanf:"tls-key-path"` + PrometheusEndpoint *string `koanf:"prometheus-endpoint"` } func (c *Config) Validate() error { @@ -144,6 +145,16 @@ func (c *Config) Validate() error { } } + return c.validatePrometheusEndpoint() +} + +func (c *Config) validatePrometheusEndpoint() error { + if c.PrometheusEndpoint != nil && *c.PrometheusEndpoint != "" { + if !strings.HasPrefix(*c.PrometheusEndpoint, "http://") && !strings.HasPrefix(*c.PrometheusEndpoint, "https://") { + return errors.New("prometheus-endpoint must start with http:// or https://") + } + } + return nil } @@ -448,6 +459,7 @@ func addGeneralFlags(f *flag.FlagSet) { f.Uint("port", defaultPort, "Port to listen from") f.String("proxy-urls", "", "Allow proxy requests to specified URLs") f.Bool("enable-helm", false, "Enable Helm operations") + f.String("prometheus-endpoint", "", "Prometheus endpoint for the cluster") } func addOIDCFlags(f *flag.FlagSet) { diff --git a/backend/pkg/headlampconfig/headlampConfig.go b/backend/pkg/headlampconfig/headlampConfig.go index 66f0f006f5c..308003e7d88 100644 --- a/backend/pkg/headlampconfig/headlampConfig.go +++ b/backend/pkg/headlampconfig/headlampConfig.go @@ -63,4 +63,5 @@ type HeadlampCFG struct { TLSCertPath string TLSKeyPath string SessionTTL int + PrometheusEndpoint string } diff --git a/backend/pkg/kubeconfig/kubeconfig_test.go b/backend/pkg/kubeconfig/kubeconfig_test.go index e5efce60365..172dab0a70e 100644 --- a/backend/pkg/kubeconfig/kubeconfig_test.go +++ b/backend/pkg/kubeconfig/kubeconfig_test.go @@ -10,7 +10,6 @@ import ( "path/filepath" "testing" - "github.com/kubernetes-sigs/headlamp/backend/pkg/config" "github.com/kubernetes-sigs/headlamp/backend/pkg/kubeconfig" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -314,12 +313,55 @@ func createTempKubeconfig(t *testing.T, content string) string { return tempFile.Name() } +// setupMockK8sServer creates a test HTTP server and a temporary kubeconfig pointing to it. +// Returns the server, temp kubeconfig path, and a cleanup function. +func setupMockK8sServer(t *testing.T) (*httptest.Server, string) { + t.Helper() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/version" { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"major": "1", "minor": "20"}`)) + + return + } + + w.WriteHeader(http.StatusNotFound) + })) + + kubeConfigContent := fmt.Sprintf(`apiVersion: v1 +clusters: +- cluster: + server: %s + insecure-skip-tls-verify: true + name: minikube +contexts: +- context: + cluster: minikube + namespace: default + user: minikube + name: minikube +current-context: minikube +kind: Config +users: +- name: minikube + user: + token: test-token +`, server.URL) + + tempFile := createTempKubeconfig(t, kubeConfigContent) + + return server, tempFile +} + func TestContext(t *testing.T) { - kubeConfigFile := config.GetDefaultKubeConfigPath() + server, tempFile := setupMockK8sServer(t) + defer server.Close() + defer os.Remove(tempFile) configStore := kubeconfig.NewContextStore() - err := kubeconfig.LoadAndStoreKubeConfigs(configStore, kubeConfigFile, kubeconfig.KubeConfig, nil) + err := kubeconfig.LoadAndStoreKubeConfigs(configStore, tempFile, kubeconfig.KubeConfig, nil) require.NoError(t, err) testContext, err := configStore.GetContext("minikube") @@ -333,8 +375,6 @@ func TestContext(t *testing.T) { require.NoError(t, err) require.NotNil(t, restConf) - // Test proxy request handler - request, err := http.NewRequestWithContext(context.Background(), "GET", "/version", nil) require.NoError(t, err) @@ -342,9 +382,8 @@ func TestContext(t *testing.T) { err = testContext.ProxyRequest(rr, request) require.NoError(t, err) - assert.Equal(t, http.StatusOK, rr.Code) - t.Logf("Proxy request Response: %s", rr.Body.String()) + assert.Equal(t, http.StatusOK, rr.Code) assert.Contains(t, rr.Body.String(), "major") assert.Contains(t, rr.Body.String(), "minor") } diff --git a/charts/headlamp/templates/deployment.yaml b/charts/headlamp/templates/deployment.yaml index 3cbe214b9f9..c90f754be8d 100644 --- a/charts/headlamp/templates/deployment.yaml +++ b/charts/headlamp/templates/deployment.yaml @@ -319,6 +319,9 @@ spec: {{- with .Values.config.baseURL }} - "-base-url={{ . }}" {{- end }} + {{- with .Values.config.prometheus.endpoint }} + - "-prometheus-endpoint={{ . | quote }}" + {{- end }} {{- with .Values.config.tlsCertPath }} - "-tls-cert-path={{ . }}" {{- end }} diff --git a/charts/headlamp/values.yaml b/charts/headlamp/values.yaml index bc9d30f99b9..9e3564b662c 100644 --- a/charts/headlamp/values.yaml +++ b/charts/headlamp/values.yaml @@ -38,6 +38,9 @@ config: baseURL: "" # -- session token TTL in seconds (default is 24 hours) sessionTTL: 86400 + prometheus: + # -- Prometheus endpoint for the cluster + endpoint: "" oidc: # Option 1: # @param config.oidc.secret - OIDC secret configuration @@ -161,8 +164,7 @@ podLabels: {} hostUsers: true # -- Headlamp pod's Security Context -podSecurityContext: - {} +podSecurityContext: {} # fsGroup: 2000 # -- Headlamp containers Security Context @@ -184,7 +186,6 @@ securityContext: # drop: # - ALL - service: # -- Annotations to add to the service annotations: {} @@ -211,8 +212,7 @@ persistentVolumeClaim: # -- Enable Persistent Volume Claim enabled: false # -- Annotations to add to the persistent volume claim (if enabled) - annotations: - {} + annotations: {} # -- accessModes for the persistent volume claim, eg: ReadWriteOnce, ReadOnlyMany, ReadWriteMany etc. accessModes: [] # -- size of the persistent volume claim, eg: 10Gi. Required if enabled is true. @@ -228,8 +228,7 @@ ingress: # -- Enable ingress controller resource enabled: false # -- Annotations for Ingress resource - annotations: - {} + annotations: {} # kubernetes.io/tls-acme: "true" # -- Additional labels to add to the Ingress resource @@ -242,8 +241,7 @@ ingress: # -- Hostname(s) for the Ingress resource # Please refer to https://kubernetes.io/docs/reference/kubernetes-api/service-resources/ingress-v1/#IngressSpec for more information. - hosts: - [] + hosts: [] # - host: chart-example.local # paths: # - path: / @@ -288,8 +286,7 @@ httpRoute: # port: 80 # -- CPU/Memory resource requests/limits -resources: - {} +resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following @@ -353,8 +350,7 @@ pluginsManager: # cpu: "1000m" # memory: "4096Mi" # If omitted, the plugin manager will inherit the global securityContext - securityContext: - {} + securityContext: {} # runAsUser: 1001 # runAsNonRoot: true # allowPrivilegeEscalation: false diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9cc86d2eae7..eda1d4e7037 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -216,6 +216,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -608,6 +609,7 @@ "version": "11.13.3", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.12.0", @@ -648,6 +650,7 @@ "version": "11.13.0", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.12.0", @@ -1761,6 +1764,7 @@ "version": "5.16.7", "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.7.tgz", "integrity": "sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q==", + "peer": true, "dependencies": { "@babel/runtime": "^7.23.9" }, @@ -1826,6 +1830,7 @@ "version": "5.16.7", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.7.tgz", "integrity": "sha512-cwwVQxBhK60OIOqZOVLFt55t01zmarKJiJUWbk0+8s/Ix5IaUzAShqlJchxsIQ4mSrWqgcKCCXKtIlG5H+/Jmg==", + "peer": true, "dependencies": { "@babel/runtime": "^7.23.9", "@mui/core-downloads-tracker": "^5.16.7", @@ -1927,6 +1932,7 @@ "version": "5.16.7", "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", + "peer": true, "dependencies": { "@babel/runtime": "^7.23.9", "@mui/private-theming": "^5.16.6", @@ -2009,6 +2015,7 @@ "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.15.0.tgz", "integrity": "sha512-YQEQICNxUEFYp/I/yP58cqihA8yhXaXSNZ1/N0JANu2IlCwoJ4Jzi+S0s4RN7RghpiDyoSMFijROBC5HfpTjiw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.25.4", "@mui/utils": "^5.16.6", @@ -2536,6 +2543,7 @@ "integrity": "sha512-KoSTtKjzQUQwamcbeCp63Ne9kL7io1WI4+skTJe2chfLz6wsp/Gfg8aKkfs1DuyG1p+zxFDcYpwTWMsNtxqqiw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rspack/core": "1.4.11", "@rspack/lite-tapable": "~1.0.1", @@ -3346,6 +3354,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -3489,6 +3498,7 @@ "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -3533,6 +3543,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.52.3.tgz", "integrity": "sha512-1K7l2hkqlWuh5SdaTYPSwMmHJF5dDk5INK+EtiEwUZW4+usWTXZx7QeHuk078oSzTzaVkEFyT3VquK7F0hYkUw==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.52.3" }, @@ -3621,6 +3632,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -4099,6 +4111,7 @@ "version": "18.3.4", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.4.tgz", "integrity": "sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw==", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -4108,6 +4121,7 @@ "version": "18.3.0", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "peer": true, "dependencies": { "@types/react": "*" } @@ -4216,6 +4230,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.3.0.tgz", "integrity": "sha512-FLAIn63G5KH+adZosDYiutqkOkYEx0nvcwNNfJAf+c7Ae/H35qWwTYvPZUKFj5AS+WfHG/WJJfWnDnyNUlp8UA==", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.3.0", @@ -4248,6 +4263,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.3.0.tgz", "integrity": "sha512-h53RhVyLu6AtpUzVCYLPhZGL5jzTD9fZL+SYf/+hYOx2bDkyQXztXSc4tbvKYHzfMXExMLiL9CWqJmVz6+78IQ==", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.3.0", "@typescript-eslint/types": "8.3.0", @@ -4695,7 +4711,8 @@ "node_modules/@xterm/xterm": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", - "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==" + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "peer": true }, "node_modules/@xyflow/react": { "version": "12.3.1", @@ -4745,6 +4762,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4789,6 +4807,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -5427,6 +5446,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001735", "electron-to-chromium": "^1.5.204", @@ -6256,7 +6276,8 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "peer": true }, "node_modules/d3-array": { "version": "3.2.4", @@ -6354,6 +6375,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -6884,6 +6906,7 @@ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, + "peer": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -7114,6 +7137,7 @@ "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -7185,6 +7209,7 @@ "version": "8.57.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -7239,6 +7264,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -7292,6 +7318,7 @@ "version": "2.29.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "peer": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -7391,6 +7418,7 @@ "version": "6.9.0", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz", "integrity": "sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==", + "peer": true, "dependencies": { "aria-query": "~5.1.3", "array-includes": "^3.1.8", @@ -7458,6 +7486,7 @@ "version": "7.35.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "peer": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -7489,6 +7518,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "peer": true, "engines": { "node": ">=10" }, @@ -7555,6 +7585,7 @@ "version": "12.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz", "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", + "peer": true, "peerDependencies": { "eslint": ">=5.0.0" } @@ -7563,6 +7594,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.3.tgz", "integrity": "sha512-lqrNZIZjFMUr7P06eoKtQLwyVRibvG7N+LtfKtObYGizAAGrcqLkc3tDx+iAik2z7q0j/XI3ihjupIqxhFabFA==", + "peer": true, "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", "eslint": "^9.0.0 || ^8.0.0" @@ -8934,6 +8966,7 @@ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" } ], + "peer": true, "dependencies": { "@babel/runtime": "^7.23.2" } @@ -9874,6 +9907,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "peer": true, "engines": { "node": ">= 10.16.0" } @@ -11398,7 +11432,8 @@ "node_modules/monaco-editor": { "version": "0.52.0", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.0.tgz", - "integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==" + "integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==", + "peer": true }, "node_modules/moo-color": { "version": "1.0.3", @@ -11420,6 +11455,7 @@ "integrity": "sha512-1m8xccT6ipN4PTqLinPwmzhxQREuxaEJYdx4nIbggxP8aM7r1e71vE7RtOUSQoAm1LydjGfZKy7370XD/tsuYg==", "dev": true, "hasInstallScript": true, + "peer": true, "dependencies": { "@bundled-es-modules/cookie": "^2.0.0", "@bundled-es-modules/statuses": "^1.0.1", @@ -11935,7 +11971,8 @@ "node_modules/openapi-types": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-9.3.1.tgz", - "integrity": "sha512-/Yvsd2D7miYB4HLJ3hOOS0+vnowQpaT75FsHzr/y5M9P4q9bwa7RcbW2YdH6KZBn8ceLbKGnHxMZ1CHliGHUFw==" + "integrity": "sha512-/Yvsd2D7miYB4HLJ3hOOS0+vnowQpaT75FsHzr/y5M9P4q9bwa7RcbW2YdH6KZBn8ceLbKGnHxMZ1CHliGHUFw==", + "peer": true }, "node_modules/opencollective-postinstall": { "version": "2.0.3", @@ -12328,6 +12365,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, + "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -12630,6 +12668,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -12673,6 +12712,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -12805,6 +12845,7 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.3", "use-sync-external-store": "^1.0.0" @@ -12827,6 +12868,7 @@ "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13067,7 +13109,8 @@ "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "peer": true }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", @@ -13393,6 +13436,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.7" }, @@ -14067,6 +14111,7 @@ "integrity": "sha512-kfr6kxQAjA96ADlH6FMALJwJ+eM80UqXy106yVHNgdsAP/CdzkkicglRAhZAvUycXK9AeadF6KZ00CWLtVMN4w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@testing-library/jest-dom": "^6.6.3", @@ -14717,6 +14762,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14960,6 +15006,7 @@ "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.6.tgz", "integrity": "sha512-SfEU3SH3wHNaxhFPjaZE2kNl/NFtLNW5c1oHsg7mti7GjmUj1Roq6osBQeMd+F4kL0BoRBBr8gQAuqBlfFu8LA==", "dev": true, + "peer": true, "dependencies": { "lunr": "^2.3.9", "markdown-it": "^14.1.0", @@ -15005,6 +15052,7 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15472,6 +15520,7 @@ "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -15644,6 +15693,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -15657,6 +15707,7 @@ "integrity": "sha512-/iL1Sc5VeDZKPDe58oGK4HUFLhw6b5XdY1MYawjuSaDA4sEfYlY9HnS6aCEG26fX+MgUi7MwlduTBHHAI/OvMA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "3.0.6", "@vitest/mocker": "3.0.6", diff --git a/frontend/src/components/App/Layout.tsx b/frontend/src/components/App/Layout.tsx index a6db8fa70c7..b0c4661135b 100644 --- a/frontend/src/components/App/Layout.tsx +++ b/frontend/src/components/App/Layout.tsx @@ -32,6 +32,7 @@ import { useCluster, useClustersConf } from '../../lib/k8s'; import { request } from '../../lib/k8s/api/v1/clusterRequests'; import { Cluster } from '../../lib/k8s/cluster'; import { getSavedNamespaces } from '../../lib/storage'; +import { setPluginConfig } from '../../plugin/pluginConfigSlice'; import { setConfig } from '../../redux/configSlice'; import { ConfigState } from '../../redux/configSlice'; import { setNamespaceFilter } from '../../redux/filterSlice'; @@ -178,6 +179,39 @@ const fetchConfig = (dispatch: Dispatch) => { fetchStatelessClusterKubeConfigs(dispatch); } + if (config?.prometheusEndpoint) { + const currentPluginConfigs: { [key: string]: any } = store.getState().pluginConfigs || {}; + const prometheusConfig = currentPluginConfigs['prometheus'] || {}; + const newPrometheusConfig: { [key: string]: any } = { ...prometheusConfig }; + let hasChanges = false; + + Object.keys(clustersToConfig).forEach(clusterName => { + const currentClusterConfig = prometheusConfig[clusterName] || {}; + if ( + currentClusterConfig.address !== config.prometheusEndpoint || + !currentClusterConfig.isMetricsEnabled || + currentClusterConfig.autoDetect !== false + ) { + newPrometheusConfig[clusterName] = { + ...currentClusterConfig, + isMetricsEnabled: true, + address: config.prometheusEndpoint, + autoDetect: false, + }; + hasChanges = true; + } + }); + + if (hasChanges) { + dispatch( + setPluginConfig({ + configKey: 'prometheus', + payload: newPrometheusConfig, + }) + ); + } + } + return configToStore; }); }; diff --git a/frontend/src/redux/configSlice.ts b/frontend/src/redux/configSlice.ts index 422831c6d98..9c59599b020 100644 --- a/frontend/src/redux/configSlice.ts +++ b/frontend/src/redux/configSlice.ts @@ -19,6 +19,7 @@ import { createSlice } from '@reduxjs/toolkit'; import type { Cluster } from '../lib/k8s/cluster'; export interface ConfigState { + prometheusEndpoint?: string; /** * Clusters is a map of cluster names to cluster objects. * Null indicates that the clusters have not been loaded yet. @@ -89,8 +90,14 @@ const configSlice = createSlice({ * @param state - The current state. * @param action - The payload action containing the config. */ - setConfig(state, action: PayloadAction<{ clusters: ConfigState['clusters'] }>) { + setConfig( + state, + action: PayloadAction<{ clusters: ConfigState['clusters']; prometheusEndpoint?: string }> + ) { state.clusters = action.payload.clusters; + if (action.payload.prometheusEndpoint !== undefined) { + state.prometheusEndpoint = action.payload.prometheusEndpoint; + } }, /** * Save the config. To both the store, and localStorage.