feat(elasticsearch): allow disabling connectivity checks
This commit is contained in:
1 parent
be3336c1ec
commit
cce36ae058
12 files changed
+202
-10
No files matched your search
@@ -108,7 +108,15 @@ import { oceanbaseModeConnectionPatch, oceanbaseSubModeFromConfig } from "@/lib/
|
|||||||
import { translateBackendError } from "@/i18n/backend-errors";
|
import { translateBackendError } from "@/i18n/backend-errors";
|
||||||
import { applyHiveKerberosSubmitConfig, hiveKerberosFormConfig, type HiveKerberosAuthMode } from "@/lib/database/hiveKerberosOptions";
|
import { applyHiveKerberosSubmitConfig, hiveKerberosFormConfig, type HiveKerberosAuthMode } from "@/lib/database/hiveKerberosOptions";
|
||||||
import { hasCloudflareD1Credentials, isCloudflareD1Connection, normalizeCloudflareD1Connection } from "@/lib/connection/cloudflareD1";
|
import { hasCloudflareD1Credentials, isCloudflareD1Connection, normalizeCloudflareD1Connection } from "@/lib/connection/cloudflareD1";
|
||||||
import { buildElasticsearchExternalConfig, elasticsearchConnectionModeFromConfig, elasticsearchConnectivityCheckPathFromConfig, elasticsearchIndexGroupingPatternFromConfig, elasticsearchKibanaBasePathFromConfig, type ElasticsearchConnectionMode } from "@/lib/connection/elasticsearchKibanaProxy";
|
import {
|
||||||
|
buildElasticsearchExternalConfig,
|
||||||
|
elasticsearchConnectionModeFromConfig,
|
||||||
|
elasticsearchConnectivityCheckDisabledFromConfig,
|
||||||
|
elasticsearchConnectivityCheckPathFromConfig,
|
||||||
|
elasticsearchIndexGroupingPatternFromConfig,
|
||||||
|
elasticsearchKibanaBasePathFromConfig,
|
||||||
|
type ElasticsearchConnectionMode,
|
||||||
|
} from "@/lib/connection/elasticsearchKibanaProxy";
|
||||||
import { GAUSSDB_M_JDBC_DRIVER_CLASS, gaussdbConnectionMode, gaussdbIdentifierQuoteStyle, setGaussdbConnectionMode, setGaussdbIdentifierQuoteStyle, supportsGaussdbIdentifierQuoteStyle, type GaussdbConnectionMode, type GaussdbIdentifierQuoteStyle } from "@/lib/database/jdbcDialect";
|
import { GAUSSDB_M_JDBC_DRIVER_CLASS, gaussdbConnectionMode, gaussdbIdentifierQuoteStyle, setGaussdbConnectionMode, setGaussdbIdentifierQuoteStyle, supportsGaussdbIdentifierQuoteStyle, type GaussdbConnectionMode, type GaussdbIdentifierQuoteStyle } from "@/lib/database/jdbcDialect";
|
||||||
import { normalizeStoredConnectionDatabase } from "@/lib/database/sqliteNamespace";
|
import { normalizeStoredConnectionDatabase } from "@/lib/database/sqliteNamespace";
|
||||||
import {
|
import {
|
||||||
@@ -327,6 +335,7 @@ const defaultForm = (): ConnectionForm => ({
|
|||||||
const elasticsearchConnectionMode = ref<ElasticsearchConnectionMode>("direct");
|
const elasticsearchConnectionMode = ref<ElasticsearchConnectionMode>("direct");
|
||||||
const elasticsearchKibanaBasePath = ref("");
|
const elasticsearchKibanaBasePath = ref("");
|
||||||
const elasticsearchConnectivityCheckPath = ref("");
|
const elasticsearchConnectivityCheckPath = ref("");
|
||||||
|
const elasticsearchConnectivityCheckDisabled = ref(false);
|
||||||
const elasticsearchIndexGroupingPattern = ref("");
|
const elasticsearchIndexGroupingPattern = ref("");
|
||||||
const elasticsearchConnectionPorts = ref<Record<ElasticsearchConnectionMode, number>>({
|
const elasticsearchConnectionPorts = ref<Record<ElasticsearchConnectionMode, number>>({
|
||||||
direct: 9200,
|
direct: 9200,
|
||||||
@@ -338,6 +347,7 @@ function resetElasticsearchProxyFields(externalConfig?: unknown) {
|
|||||||
elasticsearchConnectionMode.value = mode;
|
elasticsearchConnectionMode.value = mode;
|
||||||
elasticsearchKibanaBasePath.value = elasticsearchKibanaBasePathFromConfig(externalConfig);
|
elasticsearchKibanaBasePath.value = elasticsearchKibanaBasePathFromConfig(externalConfig);
|
||||||
elasticsearchConnectivityCheckPath.value = elasticsearchConnectivityCheckPathFromConfig(externalConfig);
|
elasticsearchConnectivityCheckPath.value = elasticsearchConnectivityCheckPathFromConfig(externalConfig);
|
||||||
|
elasticsearchConnectivityCheckDisabled.value = elasticsearchConnectivityCheckDisabledFromConfig(externalConfig);
|
||||||
elasticsearchIndexGroupingPattern.value = elasticsearchIndexGroupingPatternFromConfig(externalConfig);
|
elasticsearchIndexGroupingPattern.value = elasticsearchIndexGroupingPatternFromConfig(externalConfig);
|
||||||
elasticsearchConnectionPorts.value = {
|
elasticsearchConnectionPorts.value = {
|
||||||
direct: mode === "direct" ? form.value.port : 9200,
|
direct: mode === "direct" ? form.value.port : 9200,
|
||||||
@@ -3837,7 +3847,7 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo
|
|||||||
config.database = "metrics";
|
config.database = "metrics";
|
||||||
config.username = config.username.trim();
|
config.username = config.username.trim();
|
||||||
} else if (config.db_type === "elasticsearch") {
|
} else if (config.db_type === "elasticsearch") {
|
||||||
config.external_config = buildElasticsearchExternalConfig(elasticsearchConnectionMode.value, elasticsearchKibanaBasePath.value, elasticsearchConnectivityCheckPath.value, elasticsearchIndexGroupingPattern.value);
|
config.external_config = buildElasticsearchExternalConfig(elasticsearchConnectionMode.value, elasticsearchKibanaBasePath.value, elasticsearchConnectivityCheckPath.value, elasticsearchIndexGroupingPattern.value, elasticsearchConnectivityCheckDisabled.value);
|
||||||
} else if (config.db_type === "meilisearch") {
|
} else if (config.db_type === "meilisearch") {
|
||||||
config.username = "";
|
config.username = "";
|
||||||
config.password = config.password.trim();
|
config.password = config.password.trim();
|
||||||
@@ -6917,11 +6927,18 @@ function openExternalUrl(url: string) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="form.db_type === 'elasticsearch'" class="grid grid-cols-4 items-center gap-4">
|
<div v-if="form.db_type === 'elasticsearch'" class="grid grid-cols-4 items-center gap-4">
|
||||||
<Label :class="connectionLabelSmallClass">{{ t("connection.elasticsearchIndexGroupingPattern") }}</Label>
|
<div class="flex items-center gap-1">
|
||||||
<div class="col-span-3 space-y-1">
|
<Label :class="connectionLabelSmallClass">{{ t("connection.elasticsearchIndexGroupingPattern") }}</Label>
|
||||||
<Input v-model="elasticsearchIndexGroupingPattern" :placeholder="t('connection.elasticsearchIndexGroupingPatternPlaceholder')" @input="resetTestState" />
|
<Tooltip>
|
||||||
<p class="text-xs text-muted-foreground">{{ t("connection.elasticsearchIndexGroupingPatternHint") }}</p>
|
<TooltipTrigger as-child>
|
||||||
|
<CircleHelp class="h-3.5 w-3.5 cursor-help text-muted-foreground hover:text-foreground" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top" align="center" class="max-w-[280px] text-xs leading-relaxed">
|
||||||
|
{{ t("connection.elasticsearchIndexGroupingPatternHint") }}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<Input v-model="elasticsearchIndexGroupingPattern" class="col-span-3" :placeholder="t('connection.elasticsearchIndexGroupingPatternPlaceholder')" @input="resetTestState" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="form.driver_profile === 'gbase8s'" class="grid grid-cols-4 items-center gap-4">
|
<div v-if="form.driver_profile === 'gbase8s'" class="grid grid-cols-4 items-center gap-4">
|
||||||
@@ -7582,6 +7599,23 @@ function openExternalUrl(url: string) {
|
|||||||
|
|
||||||
<TabsContent value="advanced" class="m-0 flex min-h-0 flex-1 flex-col overflow-hidden">
|
<TabsContent value="advanced" class="m-0 flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||||
<div class="connection-form-body grid min-h-0 flex-1 scroll-pb-6 gap-4 overflow-y-auto pt-4 pr-2 pb-6">
|
<div class="connection-form-body grid min-h-0 flex-1 scroll-pb-6 gap-4 overflow-y-auto pt-4 pr-2 pb-6">
|
||||||
|
<div v-if="form.db_type === 'elasticsearch'" class="grid grid-cols-4 items-center gap-4">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Label :class="connectionLabelSmallClass">{{ t("connection.elasticsearchConnectivityCheckDisabled") }}</Label>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger as-child>
|
||||||
|
<CircleHelp class="h-3.5 w-3.5 cursor-help text-muted-foreground hover:text-foreground" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top" align="center" class="max-w-[280px] text-xs leading-relaxed">
|
||||||
|
{{ t("connection.elasticsearchConnectivityCheckDisabledHint") }}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-3">
|
||||||
|
<Switch v-model="elasticsearchConnectivityCheckDisabled" @update:model-value="resetTestState" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section v-if="form.db_type === 'nacos'" data-nacos-advanced-settings class="overflow-hidden rounded-lg border">
|
<section v-if="form.db_type === 'nacos'" data-nacos-advanced-settings class="overflow-hidden rounded-lg border">
|
||||||
<div class="border-b bg-muted/20 px-4 py-3">
|
<div class="border-b bg-muted/20 px-4 py-3">
|
||||||
<div class="text-sm font-medium">{{ t("nacos.nacosAdvancedTitle") }}</div>
|
<div class="text-sm font-medium">{{ t("nacos.nacosAdvancedTitle") }}</div>
|
||||||
|
|||||||
@@ -477,6 +477,8 @@ export default {
|
|||||||
elasticsearchKibanaBasePath: "Base Path",
|
elasticsearchKibanaBasePath: "Base Path",
|
||||||
elasticsearchConnectivityCheckPath: "Connectivity Path",
|
elasticsearchConnectivityCheckPath: "Connectivity Path",
|
||||||
elasticsearchConnectivityCheckPathPlaceholder: "/ or /my-index/_search",
|
elasticsearchConnectivityCheckPathPlaceholder: "/ or /my-index/_search",
|
||||||
|
elasticsearchConnectivityCheckDisabled: "Disable connectivity check",
|
||||||
|
elasticsearchConnectivityCheckDisabledHint: "When on, connect and test skip the probe request and treat the server as reachable. Use it when the account lacks permission for the root path and any check path, or when permissions differ across clusters.",
|
||||||
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
||||||
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
||||||
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
||||||
|
|||||||
@@ -986,6 +986,8 @@ export default withEnglishFallback({
|
|||||||
elasticsearchKibanaBasePath: "ruta base",
|
elasticsearchKibanaBasePath: "ruta base",
|
||||||
elasticsearchConnectivityCheckPath: "Ruta de conectividad",
|
elasticsearchConnectivityCheckPath: "Ruta de conectividad",
|
||||||
elasticsearchConnectivityCheckPathPlaceholder: "/ o /my-index/_search",
|
elasticsearchConnectivityCheckPathPlaceholder: "/ o /my-index/_search",
|
||||||
|
elasticsearchConnectivityCheckDisabled: "Disable connectivity check",
|
||||||
|
elasticsearchConnectivityCheckDisabledHint: "When on, connect and test skip the probe request and treat the server as reachable. Use it when the account lacks permission for the root path and any check path, or when permissions differ across clusters.",
|
||||||
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
||||||
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
||||||
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
||||||
|
|||||||
@@ -984,6 +984,8 @@ export default withEnglishFallback({
|
|||||||
elasticsearchKibanaBasePath: "Percorso base",
|
elasticsearchKibanaBasePath: "Percorso base",
|
||||||
elasticsearchConnectivityCheckPath: "Percorso di connettività",
|
elasticsearchConnectivityCheckPath: "Percorso di connettività",
|
||||||
elasticsearchConnectivityCheckPathPlaceholder: "/ o /my-index/_search",
|
elasticsearchConnectivityCheckPathPlaceholder: "/ o /my-index/_search",
|
||||||
|
elasticsearchConnectivityCheckDisabled: "Disable connectivity check",
|
||||||
|
elasticsearchConnectivityCheckDisabledHint: "When on, connect and test skip the probe request and treat the server as reachable. Use it when the account lacks permission for the root path and any check path, or when permissions differ across clusters.",
|
||||||
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
||||||
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
||||||
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
||||||
|
|||||||
@@ -984,6 +984,8 @@ export default withEnglishFallback({
|
|||||||
elasticsearchKibanaBasePath: "ベースパス",
|
elasticsearchKibanaBasePath: "ベースパス",
|
||||||
elasticsearchConnectivityCheckPath: "接続確認パス",
|
elasticsearchConnectivityCheckPath: "接続確認パス",
|
||||||
elasticsearchConnectivityCheckPathPlaceholder: "/ または /my-index/_search",
|
elasticsearchConnectivityCheckPathPlaceholder: "/ または /my-index/_search",
|
||||||
|
elasticsearchConnectivityCheckDisabled: "Disable connectivity check",
|
||||||
|
elasticsearchConnectivityCheckDisabledHint: "When on, connect and test skip the probe request and treat the server as reachable. Use it when the account lacks permission for the root path and any check path, or when permissions differ across clusters.",
|
||||||
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
||||||
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
||||||
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
||||||
|
|||||||
@@ -471,6 +471,8 @@ export default withEnglishFallback({
|
|||||||
elasticsearchKibanaBasePath: "기본 경로",
|
elasticsearchKibanaBasePath: "기본 경로",
|
||||||
elasticsearchConnectivityCheckPath: "연결 확인 경로",
|
elasticsearchConnectivityCheckPath: "연결 확인 경로",
|
||||||
elasticsearchConnectivityCheckPathPlaceholder: "/ 또는 /my-index/_search",
|
elasticsearchConnectivityCheckPathPlaceholder: "/ 또는 /my-index/_search",
|
||||||
|
elasticsearchConnectivityCheckDisabled: "Disable connectivity check",
|
||||||
|
elasticsearchConnectivityCheckDisabledHint: "When on, connect and test skip the probe request and treat the server as reachable. Use it when the account lacks permission for the root path and any check path, or when permissions differ across clusters.",
|
||||||
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
||||||
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
||||||
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
||||||
|
|||||||
@@ -985,6 +985,8 @@ export default withEnglishFallback({
|
|||||||
elasticsearchKibanaBasePath: "Caminho Base",
|
elasticsearchKibanaBasePath: "Caminho Base",
|
||||||
elasticsearchConnectivityCheckPath: "Caminho de conectividade",
|
elasticsearchConnectivityCheckPath: "Caminho de conectividade",
|
||||||
elasticsearchConnectivityCheckPathPlaceholder: "/ ou /my-index/_search",
|
elasticsearchConnectivityCheckPathPlaceholder: "/ ou /my-index/_search",
|
||||||
|
elasticsearchConnectivityCheckDisabled: "Disable connectivity check",
|
||||||
|
elasticsearchConnectivityCheckDisabledHint: "When on, connect and test skip the probe request and treat the server as reachable. Use it when the account lacks permission for the root path and any check path, or when permissions differ across clusters.",
|
||||||
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
elasticsearchIndexGroupingPattern: "Index grouping regex",
|
||||||
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
elasticsearchIndexGroupingPatternPlaceholder: "off by default; enter a regex to enable",
|
||||||
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
elasticsearchIndexGroupingPatternHint: "Off by default (naming schemes vary). Enter a regular expression to enable: matched rolling or time-series indices are aggregated into one queryable wildcard pattern, hiding per-day detail.",
|
||||||
|
|||||||
@@ -402,6 +402,8 @@ export default withEnglishFallback({
|
|||||||
elasticsearchKibanaBasePath: "基础路径",
|
elasticsearchKibanaBasePath: "基础路径",
|
||||||
elasticsearchConnectivityCheckPath: "连通性检查路径",
|
elasticsearchConnectivityCheckPath: "连通性检查路径",
|
||||||
elasticsearchConnectivityCheckPathPlaceholder: "/ 或 /my-index/_search",
|
elasticsearchConnectivityCheckPathPlaceholder: "/ 或 /my-index/_search",
|
||||||
|
elasticsearchConnectivityCheckDisabled: "关闭连通性检查",
|
||||||
|
elasticsearchConnectivityCheckDisabledHint: "开启后连接与测试都不发探活请求,直接视为可连。用于账号对根路径和任何检查路径都无权限、或集群间权限不统一的情况。",
|
||||||
elasticsearchIndexGroupingPattern: "索引聚合正则",
|
elasticsearchIndexGroupingPattern: "索引聚合正则",
|
||||||
elasticsearchIndexGroupingPatternPlaceholder: "默认关闭,填正则表达式开启",
|
elasticsearchIndexGroupingPatternPlaceholder: "默认关闭,填正则表达式开启",
|
||||||
elasticsearchIndexGroupingPatternHint: "默认关闭(命名格式因环境而异)。填入正则表达式即可开启:将匹配到的滚动或时间序列索引聚合成一个可查询的通配 pattern,隐藏每日明细。",
|
elasticsearchIndexGroupingPatternHint: "默认关闭(命名格式因环境而异)。填入正则表达式即可开启:将匹配到的滚动或时间序列索引聚合成一个可查询的通配 pattern,隐藏每日明细。",
|
||||||
|
|||||||
@@ -984,6 +984,8 @@ export default withEnglishFallback({
|
|||||||
elasticsearchKibanaBasePath: "基礎路徑",
|
elasticsearchKibanaBasePath: "基礎路徑",
|
||||||
elasticsearchConnectivityCheckPath: "連通性檢查路徑",
|
elasticsearchConnectivityCheckPath: "連通性檢查路徑",
|
||||||
elasticsearchConnectivityCheckPathPlaceholder: "/ 或 /my-index/_search",
|
elasticsearchConnectivityCheckPathPlaceholder: "/ 或 /my-index/_search",
|
||||||
|
elasticsearchConnectivityCheckDisabled: "關閉連通性檢查",
|
||||||
|
elasticsearchConnectivityCheckDisabledHint: "開啟後連線與測試都不發探活請求,直接視為可連。用於帳號對根路徑和任何檢查路徑都無權限、或叢集間權限不統一的情況。",
|
||||||
elasticsearchIndexGroupingPattern: "索引聚合正則",
|
elasticsearchIndexGroupingPattern: "索引聚合正則",
|
||||||
elasticsearchIndexGroupingPatternPlaceholder: "預設關閉,填正則表達式開啟",
|
elasticsearchIndexGroupingPatternPlaceholder: "預設關閉,填正則表達式開啟",
|
||||||
elasticsearchIndexGroupingPatternHint: "預設關閉(命名格式因環境而異)。填入正則表達式即可開啟:將比對到的滾動或時間序列索引聚合成一個可查詢的通配 pattern,隱藏每日明細。",
|
elasticsearchIndexGroupingPatternHint: "預設關閉(命名格式因環境而異)。填入正則表達式即可開啟:將比對到的滾動或時間序列索引聚合成一個可查詢的通配 pattern,隱藏每日明細。",
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { buildElasticsearchExternalConfig, elasticsearchConnectivityCheckDisabledFromConfig } from "@/lib/connection/elasticsearchKibanaProxy";
|
||||||
|
|
||||||
|
describe("Elasticsearch connectivity-check configuration", () => {
|
||||||
|
it.each([true, "true", "TRUE", "1", "yes", "YES", "on", "ON"])("reads enabled value %j", (value) => {
|
||||||
|
expect(elasticsearchConnectivityCheckDisabledFromConfig({ connectivityCheckDisabled: value })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([false, "false", "0", "no", "off", "", 1, null])("rejects disabled value %j", (value) => {
|
||||||
|
expect(elasticsearchConnectivityCheckDisabledFromConfig({ connectivityCheckDisabled: value })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes a legacy string flag when an edited connection is saved", () => {
|
||||||
|
const disabled = elasticsearchConnectivityCheckDisabledFromConfig({ connectivityCheckDisabled: " yes " });
|
||||||
|
const saved = buildElasticsearchExternalConfig("direct", "", "", "", disabled);
|
||||||
|
|
||||||
|
expect(saved).toEqual({ connectivityCheckDisabled: true });
|
||||||
|
expect(elasticsearchConnectivityCheckDisabledFromConfig(saved)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,6 +5,8 @@ export interface ElasticsearchExternalConfig {
|
|||||||
kibanaBasePath?: string;
|
kibanaBasePath?: string;
|
||||||
/** GET path for connect/test/health. Empty means GET /. */
|
/** GET path for connect/test/health. Empty means GET /. */
|
||||||
connectivityCheckPath?: string;
|
connectivityCheckPath?: string;
|
||||||
|
/** When true, skip the connectivity check entirely (test/open never probes). */
|
||||||
|
connectivityCheckDisabled?: boolean;
|
||||||
/** Regex collapsing rolling/time-series index suffixes into a `*` pattern. Empty → default; "off" → no grouping. */
|
/** Regex collapsing rolling/time-series index suffixes into a `*` pattern. Empty → default; "off" → no grouping. */
|
||||||
indexGroupingPattern?: string;
|
indexGroupingPattern?: string;
|
||||||
}
|
}
|
||||||
@@ -46,19 +48,28 @@ export function elasticsearchConnectivityCheckPathFromConfig(value: unknown): st
|
|||||||
return typeof path === "string" ? normalizeElasticsearchConnectivityCheckPath(path) : "";
|
return typeof path === "string" ? normalizeElasticsearchConnectivityCheckPath(path) : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function elasticsearchConnectivityCheckDisabledFromConfig(value: unknown): boolean {
|
||||||
|
const disabled = externalConfigRecord(value).connectivityCheckDisabled;
|
||||||
|
if (disabled === true) return true;
|
||||||
|
if (typeof disabled !== "string") return false;
|
||||||
|
const normalized = disabled.trim().toLowerCase();
|
||||||
|
return normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on";
|
||||||
|
}
|
||||||
|
|
||||||
export function elasticsearchIndexGroupingPatternFromConfig(value: unknown): string {
|
export function elasticsearchIndexGroupingPatternFromConfig(value: unknown): string {
|
||||||
const config = externalConfigRecord(value);
|
const config = externalConfigRecord(value);
|
||||||
const pattern = config.indexGroupingPattern;
|
const pattern = config.indexGroupingPattern;
|
||||||
return typeof pattern === "string" ? pattern.trim() : "";
|
return typeof pattern === "string" ? pattern.trim() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildElasticsearchExternalConfig(mode: ElasticsearchConnectionMode, kibanaBasePath: string, connectivityCheckPath = "", indexGroupingPattern = ""): ElasticsearchExternalConfig | undefined {
|
export function buildElasticsearchExternalConfig(mode: ElasticsearchConnectionMode, kibanaBasePath: string, connectivityCheckPath = "", indexGroupingPattern = "", connectivityCheckDisabled = false): ElasticsearchExternalConfig | undefined {
|
||||||
const checkPath = normalizeElasticsearchConnectivityCheckPath(connectivityCheckPath);
|
const checkPath = normalizeElasticsearchConnectivityCheckPath(connectivityCheckPath);
|
||||||
const grouping = indexGroupingPattern.trim();
|
const grouping = indexGroupingPattern.trim();
|
||||||
if (mode !== "kibana") {
|
if (mode !== "kibana") {
|
||||||
if (!checkPath && !grouping) return undefined;
|
if (!checkPath && !grouping && !connectivityCheckDisabled) return undefined;
|
||||||
const config: ElasticsearchExternalConfig = {};
|
const config: ElasticsearchExternalConfig = {};
|
||||||
if (checkPath) config.connectivityCheckPath = checkPath;
|
if (checkPath) config.connectivityCheckPath = checkPath;
|
||||||
|
if (connectivityCheckDisabled) config.connectivityCheckDisabled = true;
|
||||||
if (grouping) config.indexGroupingPattern = grouping;
|
if (grouping) config.indexGroupingPattern = grouping;
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@@ -66,6 +77,7 @@ export function buildElasticsearchExternalConfig(mode: ElasticsearchConnectionMo
|
|||||||
const config: ElasticsearchExternalConfig = { mode: "kibana" };
|
const config: ElasticsearchExternalConfig = { mode: "kibana" };
|
||||||
if (normalizedPath) config.kibanaBasePath = normalizedPath;
|
if (normalizedPath) config.kibanaBasePath = normalizedPath;
|
||||||
if (checkPath) config.connectivityCheckPath = checkPath;
|
if (checkPath) config.connectivityCheckPath = checkPath;
|
||||||
|
if (connectivityCheckDisabled) config.connectivityCheckDisabled = true;
|
||||||
if (grouping) config.indexGroupingPattern = grouping;
|
if (grouping) config.indexGroupingPattern = grouping;
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ use serde::Deserialize;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::{http_client_builder, with_connection_timeout};
|
use super::{http_client_builder, with_connection_timeout};
|
||||||
@@ -51,6 +52,9 @@ pub struct EsClient {
|
|||||||
transport_mode: ElasticsearchTransportMode,
|
transport_mode: ElasticsearchTransportMode,
|
||||||
/// GET path used for connect / health / test (default "/").
|
/// GET path used for connect / health / test (default "/").
|
||||||
connectivity_check_path: String,
|
connectivity_check_path: String,
|
||||||
|
/// 为 true 时完全跳过连通性检查(test_connection 直接返回 Ok)。
|
||||||
|
/// 用于账号连 `/` 或任何检查路径都无权限、且集群间权限不统一的场景。
|
||||||
|
connectivity_check_disabled: bool,
|
||||||
/// 正则:把易变的时间/滚动后缀折叠成 `*`,将同一前缀的滚动索引聚合成一个
|
/// 正则:把易变的时间/滚动后缀折叠成 `*`,将同一前缀的滚动索引聚合成一个
|
||||||
/// pattern 节点。`None` 表示关闭聚合,展示原始索引名。
|
/// pattern 节点。`None` 表示关闭聚合,展示原始索引名。
|
||||||
index_grouping: Option<Regex>,
|
index_grouping: Option<Regex>,
|
||||||
@@ -72,6 +76,7 @@ impl EsClient {
|
|||||||
timeout,
|
timeout,
|
||||||
ElasticsearchTransportMode::Direct,
|
ElasticsearchTransportMode::Direct,
|
||||||
"/".to_string(),
|
"/".to_string(),
|
||||||
|
false,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -84,6 +89,7 @@ impl EsClient {
|
|||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
transport_mode: ElasticsearchTransportMode,
|
transport_mode: ElasticsearchTransportMode,
|
||||||
connectivity_check_path: String,
|
connectivity_check_path: String,
|
||||||
|
connectivity_check_disabled: bool,
|
||||||
index_grouping: Option<Regex>,
|
index_grouping: Option<Regex>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let base_url = url.trim_end_matches('/').to_string();
|
let base_url = url.trim_end_matches('/').to_string();
|
||||||
@@ -91,10 +97,22 @@ impl EsClient {
|
|||||||
(Some(u), Some(p)) if !u.is_empty() => Some((u.to_string(), p.to_string())),
|
(Some(u), Some(p)) if !u.is_empty() => Some((u.to_string(), p.to_string())),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let builder = http_client_builder(timeout).danger_accept_invalid_certs(accept_invalid_certs);
|
let mut builder = http_client_builder(timeout).danger_accept_invalid_certs(accept_invalid_certs);
|
||||||
|
if let Some(addrs) = elasticsearch_localhost_resolve_addrs(&base_url, connectivity_check_disabled) {
|
||||||
|
builder = builder.resolve_to_addrs("localhost", &addrs);
|
||||||
|
}
|
||||||
let http = builder.build().unwrap_or_else(|_| HttpClient::new());
|
let http = builder.build().unwrap_or_else(|_| HttpClient::new());
|
||||||
let fallback_base_urls = elasticsearch_base_url_fallbacks(&base_url);
|
let fallback_base_urls = elasticsearch_base_url_fallbacks(&base_url);
|
||||||
Self { http, base_url, fallback_base_urls, auth, transport_mode, connectivity_check_path, index_grouping }
|
Self {
|
||||||
|
http,
|
||||||
|
base_url,
|
||||||
|
fallback_base_urls,
|
||||||
|
auth,
|
||||||
|
transport_mode,
|
||||||
|
connectivity_check_path,
|
||||||
|
connectivity_check_disabled,
|
||||||
|
index_grouping,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_config(
|
pub fn from_config(
|
||||||
@@ -114,6 +132,7 @@ impl EsClient {
|
|||||||
};
|
};
|
||||||
let base_url = format!("{}{}", url.trim_end_matches('/'), kibana_base_path.as_deref().unwrap_or(""));
|
let base_url = format!("{}{}", url.trim_end_matches('/'), kibana_base_path.as_deref().unwrap_or(""));
|
||||||
let connectivity_check_path = elasticsearch_connectivity_check_path(external_config);
|
let connectivity_check_path = elasticsearch_connectivity_check_path(external_config);
|
||||||
|
let connectivity_check_disabled = elasticsearch_connectivity_check_disabled(external_config);
|
||||||
let index_grouping = elasticsearch_index_grouping(external_config);
|
let index_grouping = elasticsearch_index_grouping(external_config);
|
||||||
Self::new_with_mode(
|
Self::new_with_mode(
|
||||||
&base_url,
|
&base_url,
|
||||||
@@ -123,6 +142,7 @@ impl EsClient {
|
|||||||
timeout,
|
timeout,
|
||||||
transport_mode,
|
transport_mode,
|
||||||
connectivity_check_path,
|
connectivity_check_path,
|
||||||
|
connectivity_check_disabled,
|
||||||
index_grouping,
|
index_grouping,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -188,6 +208,7 @@ impl Clone for EsClient {
|
|||||||
auth: self.auth.clone(),
|
auth: self.auth.clone(),
|
||||||
transport_mode: self.transport_mode,
|
transport_mode: self.transport_mode,
|
||||||
connectivity_check_path: self.connectivity_check_path.clone(),
|
connectivity_check_path: self.connectivity_check_path.clone(),
|
||||||
|
connectivity_check_disabled: self.connectivity_check_disabled,
|
||||||
index_grouping: self.index_grouping.clone(),
|
index_grouping: self.index_grouping.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,6 +263,23 @@ pub fn elasticsearch_connectivity_check_path(external_config: Option<&Value>) ->
|
|||||||
///
|
///
|
||||||
/// 语义:用 `${1}*` 模板替换匹配区间——正则带捕获组 1 时保留其内容做前缀,
|
/// 语义:用 `${1}*` 模板替换匹配区间——正则带捕获组 1 时保留其内容做前缀,
|
||||||
/// 不带捕获组时即把匹配到的“易变尾巴”替换成 `*`。
|
/// 不带捕获组时即把匹配到的“易变尾巴”替换成 `*`。
|
||||||
|
/// 是否完全跳过连通性检查。读取配置 `connectivityCheckDisabled`(布尔)。
|
||||||
|
/// 也兼容字符串 "true"/"1"/"yes"/"on"。用于账号无任何集群/索引探活权限的场景。
|
||||||
|
pub fn elasticsearch_connectivity_check_disabled(external_config: Option<&Value>) -> bool {
|
||||||
|
let Some(value) = external_config.and_then(Value::as_object).and_then(|c| c.get("connectivityCheckDisabled"))
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
match value {
|
||||||
|
Value::Bool(b) => *b,
|
||||||
|
Value::String(s) => {
|
||||||
|
let s = s.trim();
|
||||||
|
s.eq_ignore_ascii_case("true") || s == "1" || s.eq_ignore_ascii_case("yes") || s.eq_ignore_ascii_case("on")
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn elasticsearch_index_grouping(external_config: Option<&Value>) -> Option<Regex> {
|
pub fn elasticsearch_index_grouping(external_config: Option<&Value>) -> Option<Regex> {
|
||||||
let raw = external_config
|
let raw = external_config
|
||||||
.and_then(Value::as_object)
|
.and_then(Value::as_object)
|
||||||
@@ -277,6 +315,10 @@ fn group_index_names(names: Vec<String>, pattern: Option<&Regex>) -> Vec<String>
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn test_connection(client: &mut EsClient, timeout: Duration) -> Result<(), String> {
|
pub async fn test_connection(client: &mut EsClient, timeout: Duration) -> Result<(), String> {
|
||||||
|
// 用户显式关闭连通性检查:不发探活请求,直接视为可连。
|
||||||
|
if client.connectivity_check_disabled {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
let urls = std::iter::once(client.base_url.clone()).chain(client.fallback_base_urls.clone());
|
let urls = std::iter::once(client.base_url.clone()).chain(client.fallback_base_urls.clone());
|
||||||
let check_path = client.connectivity_check_path.clone();
|
let check_path = client.connectivity_check_path.clone();
|
||||||
@@ -356,6 +398,17 @@ fn elasticsearch_base_url_fallbacks(base_url: &str) -> Vec<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn elasticsearch_localhost_resolve_addrs(base_url: &str, connectivity_check_disabled: bool) -> Option<[SocketAddr; 2]> {
|
||||||
|
if !connectivity_check_disabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let parsed = reqwest::Url::parse(base_url).ok()?;
|
||||||
|
if !parsed.host_str()?.eq_ignore_ascii_case("localhost") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some([SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0), SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)])
|
||||||
|
}
|
||||||
|
|
||||||
fn elasticsearch_index_path(index: &str, endpoint: &str) -> String {
|
fn elasticsearch_index_path(index: &str, endpoint: &str) -> String {
|
||||||
format!("/{}/{}", elasticsearch_path_segment(index), endpoint.trim_start_matches('/'))
|
format!("/{}/{}", elasticsearch_path_segment(index), endpoint.trim_start_matches('/'))
|
||||||
}
|
}
|
||||||
@@ -2273,6 +2326,18 @@ mod tests {
|
|||||||
assert_eq!(names, vec!["ngx-log-1".to_string(), "ngx-log-2".to_string()]);
|
assert_eq!(names, vec!["ngx-log-1".to_string(), "ngx-log-2".to_string()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn connectivity_check_disabled_parses_bool_and_string() {
|
||||||
|
use super::elasticsearch_connectivity_check_disabled as disabled;
|
||||||
|
assert!(!disabled(None));
|
||||||
|
assert!(!disabled(Some(&serde_json::json!({}))));
|
||||||
|
assert!(disabled(Some(&serde_json::json!({ "connectivityCheckDisabled": true }))));
|
||||||
|
assert!(disabled(Some(&serde_json::json!({ "connectivityCheckDisabled": "on" }))));
|
||||||
|
assert!(disabled(Some(&serde_json::json!({ "connectivityCheckDisabled": "TRUE" }))));
|
||||||
|
assert!(!disabled(Some(&serde_json::json!({ "connectivityCheckDisabled": false }))));
|
||||||
|
assert!(!disabled(Some(&serde_json::json!({ "connectivityCheckDisabled": "off" }))));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn group_index_names_off_by_default() {
|
fn group_index_names_off_by_default() {
|
||||||
// 缺省配置 → 关闭聚合,原样返回。
|
// 缺省配置 → 关闭聚合,原样返回。
|
||||||
@@ -2406,6 +2471,51 @@ mod tests {
|
|||||||
assert_eq!(elasticsearch_base_url_fallbacks("https://search.example.com:9200"), Vec::<String>::new());
|
assert_eq!(elasticsearch_base_url_fallbacks("https://search.example.com:9200"), Vec::<String>::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn disabled_connectivity_check_keeps_localhost_request_fallback() {
|
||||||
|
assert_eq!(
|
||||||
|
super::elasticsearch_localhost_resolve_addrs("https://localhost:9200", true),
|
||||||
|
Some(["[::1]:0".parse().unwrap(), "127.0.0.1:0".parse().unwrap()])
|
||||||
|
);
|
||||||
|
assert_eq!(super::elasticsearch_localhost_resolve_addrs("https://localhost:9200", false), None);
|
||||||
|
assert_eq!(super::elasticsearch_localhost_resolve_addrs("https://search.example.com:9200", true), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn disabled_connectivity_check_can_request_ipv4_localhost() {
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
|
let addr = listener.local_addr().unwrap();
|
||||||
|
let server = tokio::spawn(async move {
|
||||||
|
let (mut socket, _) = listener.accept().await.unwrap();
|
||||||
|
let request = read_http_request(&mut socket).await;
|
||||||
|
assert!(request.starts_with("GET /_cluster/health "), "unexpected request: {request}");
|
||||||
|
let body = r#"{"status":"green"}"#;
|
||||||
|
let response = format!(
|
||||||
|
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
||||||
|
body.len(),
|
||||||
|
body
|
||||||
|
);
|
||||||
|
socket.write_all(response.as_bytes()).await.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = EsClient::from_config(
|
||||||
|
&format!("http://localhost:{}", addr.port()),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
Some(&json!({ "connectivityCheckDisabled": "yes" })),
|
||||||
|
Duration::from_secs(2),
|
||||||
|
);
|
||||||
|
super::test_connection(&mut client, Duration::from_secs(2)).await.unwrap();
|
||||||
|
let response = client.get("/_cluster/health").send().await.unwrap();
|
||||||
|
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
server.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn elasticsearch_client_from_config_uses_url_params_for_tls_verification() {
|
fn elasticsearch_client_from_config_uses_url_params_for_tls_verification() {
|
||||||
let client = EsClient::from_config(
|
let client = EsClient::from_config(
|
||||||
|
|||||||
Reference in new issue
Block a user