diff --git a/prometheus/locales/cs/translation.json b/prometheus/locales/cs/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/cs/translation.json +++ b/prometheus/locales/cs/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/de/translation.json b/prometheus/locales/de/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/de/translation.json +++ b/prometheus/locales/de/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/en/translation.json b/prometheus/locales/en/translation.json index ce8dfaafe..484f8a234 100644 --- a/prometheus/locales/en/translation.json +++ b/prometheus/locales/en/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "Connection failed: {{ errorMessage }}", "Enable Metrics": "Enable Metrics", "Auto detect": "Auto detect", - "Prometheus Service Address": "Prometheus Service Address", - "Invalid format. Use: namespace/service-name:port": "Invalid format. Use: namespace/service-name:port", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port", + "Prometheus Address": "Prometheus Address", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.", "Test Connection": "Test Connection", - "Prometheus Service Subpath": "Prometheus Service Subpath", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.", + "Prometheus Subpath": "Prometheus Subpath", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.", "Default Timespan": "Default Timespan", "Default Resolution": "Default Resolution", "Select Cluster": "Select Cluster", diff --git a/prometheus/locales/es/translation.json b/prometheus/locales/es/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/es/translation.json +++ b/prometheus/locales/es/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/fr/translation.json b/prometheus/locales/fr/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/fr/translation.json +++ b/prometheus/locales/fr/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/hu/translation.json b/prometheus/locales/hu/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/hu/translation.json +++ b/prometheus/locales/hu/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/id/translation.json b/prometheus/locales/id/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/id/translation.json +++ b/prometheus/locales/id/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/it/translation.json b/prometheus/locales/it/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/it/translation.json +++ b/prometheus/locales/it/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/ja/translation.json b/prometheus/locales/ja/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/ja/translation.json +++ b/prometheus/locales/ja/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/ko/translation.json b/prometheus/locales/ko/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/ko/translation.json +++ b/prometheus/locales/ko/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/nl/translation.json b/prometheus/locales/nl/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/nl/translation.json +++ b/prometheus/locales/nl/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/pl/translation.json b/prometheus/locales/pl/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/pl/translation.json +++ b/prometheus/locales/pl/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/pt-BR/translation.json b/prometheus/locales/pt-BR/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/pt-BR/translation.json +++ b/prometheus/locales/pt-BR/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/pt-PT/translation.json b/prometheus/locales/pt-PT/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/pt-PT/translation.json +++ b/prometheus/locales/pt-PT/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/ru/translation.json b/prometheus/locales/ru/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/ru/translation.json +++ b/prometheus/locales/ru/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/sv/translation.json b/prometheus/locales/sv/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/sv/translation.json +++ b/prometheus/locales/sv/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/tr/translation.json b/prometheus/locales/tr/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/tr/translation.json +++ b/prometheus/locales/tr/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/zh-Hans/translation.json b/prometheus/locales/zh-Hans/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/zh-Hans/translation.json +++ b/prometheus/locales/zh-Hans/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/locales/zh-Hant/translation.json b/prometheus/locales/zh-Hant/translation.json index e4eb4ad51..6b3c7d2a3 100644 --- a/prometheus/locales/zh-Hant/translation.json +++ b/prometheus/locales/zh-Hant/translation.json @@ -63,12 +63,12 @@ "Connection failed: {{ errorMessage }}": "", "Enable Metrics": "", "Auto detect": "", - "Prometheus Service Address": "", - "Invalid format. Use: namespace/service-name:port": "", - "Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port": "", + "Prometheus Address": "", + "Invalid format. Use: namespace/service-name:port or https://prometheus.example.com": "", + "Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.": "", "Test Connection": "", - "Prometheus Service Subpath": "", - "Optional subpath to the Prometheus Service endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", + "Prometheus Subpath": "", + "Optional subpath to the Prometheus endpoint. Only used when auto-detection is disabled. Examples: 'prometheus'.": "", "Default Timespan": "", "Default Resolution": "", "Select Cluster": "", diff --git a/prometheus/src/components/Settings/Settings.tsx b/prometheus/src/components/Settings/Settings.tsx index b518fa26c..09dda60f4 100644 --- a/prometheus/src/components/Settings/Settings.tsx +++ b/prometheus/src/components/Settings/Settings.tsx @@ -12,17 +12,29 @@ import Switch from '@mui/material/Switch'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import { useEffect, useState } from 'react'; +import { isHttpUrl } from '../../helpers'; /** - * Validates if the given address string is in the correct format. - * The format should be: namespace/service:port + * Validates whether the given address is in a supported format. + * Supports namespace/service:port and HTTP/HTTPS URLs. + * Examples: monitoring/prometheus:9090, https://prometheus.example.com * * @param {string} address - The address string to validate. * @returns {boolean} True if the address is valid, false otherwise. */ function isValidAddress(address: string): boolean { - const regex = /^[a-z0-9-]+\/[a-z0-9-]+:[0-9]+$/; - return regex.test(address); + if (!address) return false; + + const value = address.trim().replace(/\/$/, ''); + + // namespace/service:port + const k8sRegex = /^[a-z0-9-]+\/[a-z0-9-]+:[0-9]+$/; + if (k8sRegex.test(value)) { + return true; + } + + // http(s)://... + return isHttpUrl(value); } /** @@ -107,17 +119,33 @@ export function Settings(props: SettingsProps) { setTestMessage(t('Testing Connection')); try { - const [namespace, serviceAndPort] = selectedClusterData.address.split('/'); - const [service, port] = serviceAndPort.split(':'); + const address = selectedClusterData.address.trim().replace(/\/$/, ''); + const normalizeSubPath = (value: string) => { + const trimmed = value.trim().replace(/^\/+|\/+$/g, ''); + return trimmed ? `/${trimmed}` : ''; + }; + const subPath = normalizeSubPath(selectedClusterData.subPath || ''); - let subPath = selectedClusterData.subPath || ''; - if (subPath && !subPath.startsWith('/')) { - subPath = '/' + subPath; + if (isHttpUrl(address)) { + // External URL: direct fetch + const url = new URL(address); + const basePath = url.pathname.replace(/\/+$/g, ''); + url.pathname = `${basePath}${subPath}/-/healthy`; + const response = await fetch(url.toString(), { method: 'GET' }); + if (!response.ok) { + throw new Error(`HTTP ${response.status} ${response.statusText}`); + } + } else { + // Kubernetes service proxy: namespace/service:port + const [namespace, serviceAndPort] = address.split('/'); + const [service, port] = serviceAndPort.split(':'); + const proxyUrl = `/clusters/${selectedCluster}/api/v1/namespaces/${namespace}/services/${service}:${port}/proxy${subPath}/-/healthy`; + const response = await request(proxyUrl, { method: 'GET', isJSON: false }); + if (response.status !== 200) { + throw new Error(`HTTP ${response.status} ${response.statusText}`); + } } - const proxyUrl = `/clusters/${selectedCluster}/api/v1/namespaces/${namespace}/services/${service}:${port}/proxy${subPath}/-/healthy`; - await request(proxyUrl); - setTestStatus('success'); setTestMessage(t('Connection successful!')); } catch (err) { @@ -167,7 +195,7 @@ export function Settings(props: SettingsProps) { ), }, { - name: t('Prometheus Service Address'), + name: t('Prometheus Address'), value: ( @@ -175,9 +203,11 @@ export function Settings(props: SettingsProps) { disabled={!isAddressFieldEnabled} helperText={ addressError - ? t('Invalid format. Use: namespace/service-name:port') + ? t( + 'Invalid format. Use: namespace/service-name:port or https://prometheus.example.com' + ) : t( - 'Address of the Prometheus Service, only used when auto-detection is disabled. Format: namespace/service-name:port' + 'Prometheus address. Used only when auto-detection is disabled. Examples: namespace/service-name:port or https://prometheus.example.com. External URLs require CORS to be enabled on the Prometheus server.' ) } error={addressError} @@ -220,16 +250,16 @@ export function Settings(props: SettingsProps) { ), }, { - name: t('Prometheus Service Subpath'), + name: t('Prometheus Subpath'), value: ( { - const newSubPath = e.target.value; + const newSubPath = e.target.value.trim().replace(/^\/+|\/+$/g, ''); onDataChange({ ...(data || {}), [selectedCluster]: { ...((data || {})[selectedCluster] || {}), subPath: newSubPath }, diff --git a/prometheus/src/helpers.ts b/prometheus/src/helpers.ts new file mode 100644 index 000000000..69ad7d6b4 --- /dev/null +++ b/prometheus/src/helpers.ts @@ -0,0 +1,14 @@ +/** + * Checks whether the given string is a valid HTTP or HTTPS URL. + * + * @param {string} value - The value to validate. + * @returns {boolean} True if the value is a valid http/https URL, otherwise false. + */ +export function isHttpUrl(value: string): boolean { + try { + const url = new URL(value); + return url.protocol === 'http:' || url.protocol === 'https:'; + } catch { + return false; + } +} diff --git a/prometheus/src/request.tsx b/prometheus/src/request.tsx index b3cf33240..32c6b7c38 100644 --- a/prometheus/src/request.tsx +++ b/prometheus/src/request.tsx @@ -1,4 +1,5 @@ import { ApiProxy } from '@kinvolk/headlamp-plugin/lib'; +import { isHttpUrl } from './helpers'; const request = ApiProxy.request; @@ -280,7 +281,7 @@ async function testPrometheusQuery( /** * Fetches metrics data from Prometheus using the provided parameters. * @param {object} data - The parameters for fetching metrics. - * @param {string} data.prefix - The namespace prefix. + * @param {string} data.prefix - Either a Kubernetes proxy prefix in the form {namespace}/{pods|services}/{name}:port, or a full HTTP/HTTPS Prometheus base URL. * @param {string} data.query - The Prometheus query string. * @param {number} data.from - The start time for the query (Unix timestamp). * @param {number} data.to - The end time for the query (Unix timestamp). @@ -309,17 +310,52 @@ export async function fetchMetrics(data: { if (data.query) { params.append('query', data.query); } - var url = `/api/v1/namespaces/${data.prefix}/proxy/api/v1/query_range?${params.toString()}`; - if (data.subPath && data.subPath !== '') { - if (data.subPath.startsWith('/')) { - data.subPath = data.subPath.slice(1); + + const isExternal = isHttpUrl(data.prefix); + var url: string; + + if (isExternal) { + const baseUrl = new URL(data.prefix); + + // Build the path segments + const pathSegments = [baseUrl.pathname.replace(/\/$/, '')]; + if (data.subPath && data.subPath !== '') { + pathSegments.push(data.subPath.replace(/^\/+|\/+$/g, '')); + } + pathSegments.push('api/v1/query_range'); + baseUrl.pathname = pathSegments.join('/'); + + // Merge query params (preserve any existing params in the URL) + params.forEach((value, key) => { + baseUrl.searchParams.set(key, value); + }); + + url = baseUrl.toString(); + } else { + url = `/api/v1/namespaces/${data.prefix}/proxy/api/v1/query_range?${params.toString()}`; + if (data.subPath && data.subPath !== '') { + const normalizedSubPath = data.subPath.replace(/^\/+|\/+$/g, ''); + url = `/api/v1/namespaces/${ + data.prefix + }/proxy/${normalizedSubPath}/api/v1/query_range?${params.toString()}`; + } + } + + if (isExternal) { + const response = await fetch(url, { method: 'GET' }); + if (response.ok) { + return response.json(); } - if (data.subPath.endsWith('/')) { - data.subPath = data.subPath.slice(0, -1); + let message = `Request failed with status ${response.status} ${response.statusText}`; + try { + const bodyText = await response.text(); + if (bodyText) { + message += `: ${bodyText}`; + } + } catch { + // Ignore errors while reading the error body; fallback to status-only message. } - url = `/api/v1/namespaces/${data.prefix}/proxy/${ - data.subPath - }/api/v1/query_range?${params.toString()}`; + throw new Error(message); } const response = await request(url, { diff --git a/prometheus/src/util.test.ts b/prometheus/src/util.test.ts index 09d48614e..970ab4656 100644 --- a/prometheus/src/util.test.ts +++ b/prometheus/src/util.test.ts @@ -1,21 +1,161 @@ -import { getTimeRangeAndStepSize } from './util'; +import { TextDecoder, TextEncoder } from 'node:util'; +import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -beforeAll(async () => { - global.TextEncoder = require('util').TextEncoder; - global.TextDecoder = require('util').TextDecoder; +vi.mock('./request', () => ({ + isPrometheusInstalled: vi.fn(), + KubernetesType: { + none: 'none', + services: 'services', + pods: 'pods', + }, +})); + +vi.mock('@kinvolk/headlamp-plugin/lib', () => ({ + ConfigStore: vi.fn(), +})); + +import { ConfigStore } from '@kinvolk/headlamp-plugin/lib'; +import { isHttpUrl } from './helpers'; +import { isPrometheusInstalled, KubernetesType } from './request'; +import { getPrometheusPrefix, getTimeRangeAndStepSize } from './util'; + +const mockIsPrometheusInstalled = vi.mocked(isPrometheusInstalled); +const MockConfigStore = vi.mocked(ConfigStore); + +beforeAll(() => { + global.TextEncoder = TextEncoder; + global.TextDecoder = TextDecoder as unknown as typeof global.TextDecoder; +}); + +function mockClusterConfig(clusterName: string, config: Record | null) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (MockConfigStore as any).mockImplementation(() => ({ + get: () => (config ? { [clusterName]: config } : null), + })); +} + +describe('isHttpUrl', () => { + test.each([ + ['http://prometheus.example.com', true], + ['https://prometheus.example.com', true], + ['http://prometheus.example.com:9090', true], + ['https://prometheus.example.com/api/v1', true], + ['', false], + ['monitoring/prometheus', false], + ['ftp://example.com', false], + ['not a url', false], + ])('isHttpUrl(%s) returns %s', (input, expected) => { + expect(isHttpUrl(input)).toBe(expected); + }); +}); + +describe('getPrometheusPrefix', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('auto-detect mode', () => { + test('returns detected endpoint when prometheus is found', async () => { + mockClusterConfig('test-cluster', { autoDetect: true }); + mockIsPrometheusInstalled.mockResolvedValue({ + type: KubernetesType.services, + namespace: 'monitoring', + name: 'prometheus', + port: '9090', + }); + + const result = await getPrometheusPrefix('test-cluster'); + expect(result).toBe('monitoring/services/prometheus:9090'); + }); + + test('returns endpoint without port when port is empty', async () => { + mockClusterConfig('test-cluster', { autoDetect: true }); + mockIsPrometheusInstalled.mockResolvedValue({ + type: KubernetesType.services, + namespace: 'monitoring', + name: 'prometheus', + port: '', + }); + + const result = await getPrometheusPrefix('test-cluster'); + expect(result).toBe('monitoring/services/prometheus'); + }); + + test('returns null when no prometheus is found', async () => { + mockClusterConfig('test-cluster', { autoDetect: true }); + mockIsPrometheusInstalled.mockResolvedValue({ + type: KubernetesType.none, + namespace: '', + name: '', + port: '', + }); + + const result = await getPrometheusPrefix('test-cluster'); + expect(result).toBeNull(); + }); + + test('takes priority over manual address', async () => { + mockClusterConfig('test-cluster', { + autoDetect: true, + address: 'https://manual-address.com', + }); + mockIsPrometheusInstalled.mockResolvedValue({ + type: KubernetesType.services, + namespace: 'monitoring', + name: 'prometheus', + port: '9090', + }); + + const result = await getPrometheusPrefix('test-cluster'); + expect(result).toBe('monitoring/services/prometheus:9090'); + }); + }); + + describe('manual address mode', () => { + test.each([ + ['http://prometheus.example.com:9090', 'http://prometheus.example.com:9090'], + ['https://prometheus.example.com', 'https://prometheus.example.com'], + ['https://prometheus.example.com/', 'https://prometheus.example.com'], + ['monitoring/prometheus:9090', 'monitoring/services/prometheus:9090'], + [' monitoring/prometheus:9090 ', 'monitoring/services/prometheus:9090'], + ])('address "%s" returns "%s"', async (address, expected) => { + mockClusterConfig('test-cluster', { autoDetect: false, address }); + const result = await getPrometheusPrefix('test-cluster'); + expect(result).toBe(expected); + }); + + test('returns null for invalid address format', async () => { + mockClusterConfig('test-cluster', { autoDetect: false, address: 'invalid-format' }); + const result = await getPrometheusPrefix('test-cluster'); + expect(result).toBeNull(); + }); + }); + + describe('no configuration', () => { + test('returns null when cluster config is null', async () => { + mockClusterConfig('test-cluster', null); + const result = await getPrometheusPrefix('test-cluster'); + expect(result).toBeNull(); + }); + + test('returns null when neither autoDetect nor address is set', async () => { + mockClusterConfig('test-cluster', {}); + const result = await getPrometheusPrefix('test-cluster'); + expect(result).toBeNull(); + }); + }); }); describe('getTimeRangeAndStepSize', () => { - // Mock the current timestamp for consistent testing const mockNow = 1700000000; - const day = 86400; // seconds in a day + const day = 86400; beforeEach(() => { - vitest.spyOn(Date, 'now').mockImplementation(() => mockNow * 1000); + vi.spyOn(Date, 'now').mockImplementation(() => mockNow * 1000); }); afterEach(() => { - vitest.restoreAllMocks(); + vi.restoreAllMocks(); }); test.each([ @@ -38,68 +178,24 @@ describe('getTimeRangeAndStepSize', () => { [ 'yesterday', 'medium', - { - from: mockNow - (mockNow % day) - day, - to: mockNow - (mockNow % day), - step: 345, - }, + { from: mockNow - (mockNow % day) - day, to: mockNow - (mockNow % day), step: 345 }, ], ['week', 'medium', { from: mockNow - 7 * day, to: mockNow, step: 2419 }], - [ - 'lastweek', - 'medium', - { - from: mockNow - 14 * day, - to: mockNow - 7 * day, - step: 2419, - }, - ], - - // Different resolutions with same interval - ['1h', 'low', { from: mockNow - 3600, to: mockNow, step: 36 }], // timeRange / 100 - ['1h', 'medium', { from: mockNow - 3600, to: mockNow, step: 14 }], // timeRange / 250 - ['1h', 'high', { from: mockNow - 3600, to: mockNow, step: 4 }], // timeRange / 750 - - // Fixed step sizes with same interval + ['lastweek', 'medium', { from: mockNow - 14 * day, to: mockNow - 7 * day, step: 2419 }], + // Different resolutions + ['1h', 'low', { from: mockNow - 3600, to: mockNow, step: 36 }], + ['1h', 'medium', { from: mockNow - 3600, to: mockNow, step: 14 }], + ['1h', 'high', { from: mockNow - 3600, to: mockNow, step: 4 }], + // Fixed step sizes ['1h', '30s', { from: mockNow - 3600, to: mockNow, step: 30 }], ['1h', '15m', { from: mockNow - 3600, to: mockNow, step: 900 }], ['1h', '1h', { from: mockNow - 3600, to: mockNow, step: 3600 }], - // Edge cases - ['1m', 'medium', { from: mockNow - 60, to: mockNow, step: 1 }], // Minimum step size is 1 - ['14d', 'medium', { from: mockNow - 14 * day, to: mockNow, step: 4838 }], // Large time range - [ - 'invalid', // Falls back to 10 minutes interval - 'medium', - { - from: mockNow - 600, - to: mockNow, - step: 2, - }, - ], - [ - '1h', - 'invalid', // Falls back to medium resolution - { from: mockNow - 3600, to: mockNow, step: 14 }, - ], - ])( - 'should return correct timeRange and stepSize for %s interval and %s resolution', - (interval, resolution, expected) => { - const result = getTimeRangeAndStepSize(interval, resolution); - expect(result).toEqual(expected); - } - ); - - test('should handle different timestamps correctly', () => { - // Test with a specific timestamp - const specificTime = 1600000000; - vitest.spyOn(Date, 'now').mockImplementation(() => specificTime * 1000); - - const result = getTimeRangeAndStepSize('1h', 'medium'); - expect(result).toEqual({ - from: specificTime - 3600, - to: specificTime, - step: 14, - }); + ['1m', 'medium', { from: mockNow - 60, to: mockNow, step: 1 }], + ['14d', 'medium', { from: mockNow - 14 * day, to: mockNow, step: 4838 }], + ['invalid', 'medium', { from: mockNow - 600, to: mockNow, step: 2 }], + ['1h', 'invalid', { from: mockNow - 3600, to: mockNow, step: 14 }], + ])('interval=%s resolution=%s', (interval, resolution, expected) => { + expect(getTimeRangeAndStepSize(interval, resolution)).toEqual(expected); }); }); diff --git a/prometheus/src/util.ts b/prometheus/src/util.ts index c7c6f08cc..04d4a8168 100644 --- a/prometheus/src/util.ts +++ b/prometheus/src/util.ts @@ -4,6 +4,7 @@ import { NodeClaimCreationChart } from './components/Chart/KarpenterNodeClaimCre import { KarpenterNodeClaimsProvisionChart } from './components/Chart/KarpenterNodeClaimProvisionChart/KarpenterNodeClaimProvisionChart'; import { KarpenterNodePoolResourceChart } from './components/Chart/KarpenterNodePoolResourceChart/KarpenterNodePoolResourceChart'; import { KarpenterPendingPods } from './components/Chart/KarpenterPendingPods/KarpenterPendingPods'; +import { isHttpUrl } from './helpers'; import { isPrometheusInstalled, KubernetesType } from './request'; export const PLUGIN_NAME = 'prometheus'; @@ -100,27 +101,50 @@ export function isMetricsEnabled(cluster: string): boolean { } /** - * getPrometheusPrefix returns the prefix for the Prometheus metrics. + * Resolves the base address for accessing Prometheus for a cluster. + * + * The configured `address` may be either: + * - a full HTTP/HTTPS URL (returned as-is), or + * - a Kubernetes service spec in the form `namespace/service:port`. + * + * For non-URL addresses, this function returns a Kubernetes API proxy prefix, e.g.: + * - `{namespace}/services/{service}:{port}` for manually configured service addresses, or + * - `{namespace}/{type}/{name}:{port}` when autodetected (where `type` may be `services` or `pods`). + * * @param {string} cluster - The name of the cluster. - * @returns {Promise} The prefix for the Prometheus metrics, or null if not found. + * @returns {Promise} A full URL or Kubernetes API proxy prefix for Prometheus, or null if none is found. */ export async function getPrometheusPrefix(cluster: string): Promise { - // check if cluster has autoDetect enabled - // if so return the prometheus pod address const clusterData = getClusterConfig(cluster); + + // 1. Auto-detect takes priority when enabled if (clusterData?.autoDetect) { const prometheusEndpoint = await isPrometheusInstalled(); if (prometheusEndpoint.type === KubernetesType.none) { return null; } + const prometheusPortStr = prometheusEndpoint.port ? `:${prometheusEndpoint.port}` : ''; return `${prometheusEndpoint.namespace}/${prometheusEndpoint.type}/${prometheusEndpoint.name}${prometheusPortStr}`; } + // 2. Manual address (only when autoDetect is not enabled) if (clusterData?.address) { - const [namespace, service] = clusterData?.address.split('/'); - return `${namespace}/services/${service}`; + const address = clusterData.address.trim().replace(/\/$/, ''); + + if (isHttpUrl(address)) { + return address; + } + + // Expected Kubernetes service proxy format: namespace/service:port + // Validate that a numeric port is provided to avoid ambiguous multi-port services. + const serviceMatch = address.match(/^([^/]+)\/([^/:]+):(\d+)$/); + if (serviceMatch) { + const [, namespace, service, port] = serviceMatch; + return `${namespace}/services/${service}:${port}`; + } } + return null; }