From 2ede64f1cbf8f7976fab7d0fdd70ead93e7a453a Mon Sep 17 00:00:00 2001 From: zipg Date: Wed, 5 Aug 2026 23:58:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(db):=20=E6=94=AF=E6=8C=81=20VictoriaMetric?= =?UTF-8?q?s=20=E6=95=B0=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(victoriametrics): support native MetricsQL queries * fix(victoriametrics): 完善指标浏览和结果导出 * fix(victoriametrics): 修复指标对象通用操作入口 * fix(victoriametrics): 修正指标注释和截断提示 * fix(i18n): 补齐 VictoriaMetrics 连接配置翻译 --------- Co-authored-by: zipg <4047349+zipg@users.noreply.github.com> --- .../public/icons/database/victoriametrics.png | Bin 0 -> 1725 bytes .../connection/ConnectionDialog.vue | 120 +++- .../src/components/editor/QueryEditor.vue | 19 +- apps/desktop/src/components/grid/DataGrid.vue | 9 +- .../__tests__/DataGridTotalRowCount.spec.ts | 8 +- .../src/components/icons/DatabaseIcon.vue | 1 + .../src/components/layout/EditorToolbar.vue | 16 +- .../src/components/objects/ObjectBrowser.vue | 75 ++- .../sidebar/SidebarTreeRuntimeHost.vue | 38 +- apps/desktop/src/composables/useQuickOpen.ts | 2 +- .../useSidebarTreeExportRuntime.ts | 20 + .../src/composables/useSqlExecution.ts | 2 +- apps/desktop/src/i18n/locales/en.ts | 8 + apps/desktop/src/i18n/locales/es.ts | 8 + apps/desktop/src/i18n/locales/it.ts | 8 + apps/desktop/src/i18n/locales/ja.ts | 8 + apps/desktop/src/i18n/locales/ko.ts | 8 + apps/desktop/src/i18n/locales/pt-BR.ts | 8 + apps/desktop/src/i18n/locales/zh-CN.ts | 8 + apps/desktop/src/i18n/locales/zh-TW.ts | 8 + .../connectionDialog.damengSsl.spec.ts | 3 +- .../connectionUrl.victoriametrics.spec.ts | 38 ++ ...taGridColumnFilter.victoriametrics.spec.ts | 12 + .../lib/__tests__/sql/newQueryContext.spec.ts | 4 + .../__tests__/sql/sqlStatementRanges.spec.ts | 1 + .../lib/connection/connectionPresentation.ts | 3 + .../src/lib/connection/connectionUrl.ts | 15 +- .../src/lib/dataGrid/dataGridColumnFilter.ts | 1 + .../lib/dataGrid/dataGridColumnNameCopy.ts | 2 +- .../src/lib/dataGrid/dataGridPagination.ts | 6 + .../lib/database/databaseCapabilitySets.ts | 2 +- .../lib/database/databaseFeatureSupport.ts | 2 +- .../lib/database/databaseNamespaceCreation.ts | 1 + .../database/databaseObjectCapabilities.ts | 1 + .../lib/database/databasePropertyEditing.ts | 1 + .../database/databaseTableDataCapabilities.ts | 12 + apps/desktop/src/lib/sql/newQueryContext.ts | 3 +- .../desktop/src/lib/sql/sqlStatementRanges.ts | 2 +- apps/desktop/src/lib/table/tableClipboard.ts | 2 +- apps/desktop/src/lib/table/tableDataExport.ts | 12 + .../lib/table/tableMetadataCapabilities.ts | 6 + apps/desktop/src/lib/table/tableSelectSql.ts | 10 + .../src/lib/table/tableSqlTemplates.ts | 3 +- apps/desktop/src/stores/connectionStore.ts | 1 + apps/desktop/src/types/database.ts | 1 + apps/desktop/src/types/victoriametrics.ts | 4 + .../assets/database-drivers.manifest.json | 29 + crates/dbx-core/src/connection.rs | 40 ++ crates/dbx-core/src/data_grid_sql.rs | 30 + crates/dbx-core/src/database_capabilities.rs | 2 + crates/dbx-core/src/db/mod.rs | 1 + .../dbx-core/src/db/victoriametrics_driver.rs | 569 ++++++++++++++++++ crates/dbx-core/src/models/connection.rs | 7 +- crates/dbx-core/src/query.rs | 19 + crates/dbx-core/src/query_execution_sql.rs | 22 +- crates/dbx-core/src/schema.rs | 18 + .../dbx-core/src/sql_dialect/ddl_profile.rs | 4 +- .../dbx-core/src/sql_dialect/table_select.rs | 26 + crates/dbx-core/src/table_export.rs | 25 +- .../dbx-core/tests/database_capabilities.rs | 11 + .../public/icons/database/victoriametrics.png | Bin 0 -> 1725 bytes packages/app-tests/tableDataExport.test.ts | 15 + packages/app-tests/tableSqlTemplates.test.ts | 4 + src-tauri/src/commands/connection.rs | 13 + 64 files changed, 1293 insertions(+), 64 deletions(-) create mode 100644 apps/desktop/public/icons/database/victoriametrics.png create mode 100644 apps/desktop/src/lib/__tests__/connection/connectionUrl.victoriametrics.spec.ts create mode 100644 apps/desktop/src/lib/__tests__/dataGrid/dataGridColumnFilter.victoriametrics.spec.ts create mode 100644 apps/desktop/src/types/victoriametrics.ts create mode 100644 crates/dbx-core/src/db/victoriametrics_driver.rs create mode 100644 docs/public/icons/database/victoriametrics.png diff --git a/apps/desktop/public/icons/database/victoriametrics.png b/apps/desktop/public/icons/database/victoriametrics.png new file mode 100644 index 0000000000000000000000000000000000000000..d70cab19e83f560d2654071d4e91622f96692bca GIT binary patch literal 1725 zcmV;u215CXP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NICrLy>RA>dwn0xHjQy9QS?t$w#>`|Nzb&-tEnp66Vi=e%#L1FDmQ_^cfq4>izZciEM)3v>YM zkAOCC2(*S)p!^5hVH^Anf5ImC9oE8XSPAv;1N;OV;a?~#VSwC6ZBd>A1K@1v4c$OP z4}$}tA!P@h>D&g;fn~4=KH2@enX)SB%va;#ZCD3(>#L zYM`umP-a6*ItSYFJQ%X85<}&5_y>w+q^a~TlGV;TP?eb+)xoWK>J zjoMlwDuZ!9^E?{jL>C&I%0nGAv=d(1`a>$(FbcvJ*2ansXq*?hn<00IYS1@i!dpOp zy&x6$`iJKB~ zN*D+p%$(Gs`~u>>!}BlbuZG-}0(EsVn9sh2&}m%m&j30;9O?`DUV(lXaMYZPE(gy&$~?E_=@FKIrQp z9waGJL(U~wB7C_GFZRnqZ zV;~h*Y%0VnR<#2<5jyXf_RM>wgr~9Y^L1c9>8yuIdWgE*Bst4&$#lgE9nheKkPhVX z&VlZbN~MIC_7%TxqO&_7w@tgCe-3ghrj6Q~m21$(%M}mk7U-2}WoR1(t0Dfr+_Q)A z251eb^guBWa)DwCdbehwcwlaYa9~m+KBexgg4To47bU#7pH+M}5_CKjf9<^s@%Q1` z6*I83fwE%A8V743G>*$<=r4qv3`DsI;_u_QUW zBcL8aCvo{B`pd!ZQgI7-FwLgs6c6#&@Hq5_oaj{1>XlouTwi(KXs29>V#_9B=U;=z12Q9TX0gok<$ z(NWKcj;H+?I_E+SG>UYf*C$~s~rxfS%f@ECY~Xc94*+y+Gn?>p-5gJCcfCc#q3Wmhx*WboqFq+(bc2So|b z)o`ojwp&;^FMAWf8$h#2D@MH(RzfhU+HwtT#y~DVpl({JWw@e*mvg9|?}vjE&|;(? zmJ}tt*a;oIpv7377-GVk2e~k5^r-_gdyAG%SZCC6rVWoevm73P+>avE`!4?jIr}FI T%AbWq00000NkvXXu0mjf$e#|U literal 0 HcmV?d00001 diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 991a19b301..eea7411135 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -16,6 +16,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover import { Switch } from "@/components/ui/switch"; import type { ConnectionConfig, ConnectionTestResult, DatabaseConnectionInfo, DatabaseType, HttpTunnelConfig, IdentifierCase, JdbcDriverInfo, JdbcLocalBundleInfo, JdbcMavenBundleInfo, ProxyTunnelConfig, SshConfigHostEntry, SshTunnelConfig, TransportLayerConfig } from "@/types/database"; import type { InfluxDbExternalConfig, InfluxDbVersion } from "@/types/influxdb"; +import type { VictoriaMetricsExternalConfig } from "@/types/victoriametrics"; import type { MqAdminConfig, MqAuth, MqSystemKind } from "@/types/mq"; import type { MqttConnectionConfig } from "@/types/mqtt"; import type { NacosAdminConfig, NacosAuthConfig, NacosImplementation, NacosMetricsMode, NacosRNacosConsoleAuth, NacosVersionMode } from "@/types/nacos"; @@ -1025,6 +1026,7 @@ const driverProfiles: Record< mqtt: { type: "mqtt", port: 1883, user: "", label: "MQTT", icon: "mqtt", host: "127.0.0.1" }, iris: { type: "iris", port: 1972, user: "_SYSTEM", label: "IRIS", icon: "iris" }, influxdb: { type: "influxdb", port: 8086, user: "", label: "InfluxDB", icon: "InfluxDB" }, + victoriametrics: { type: "victoriametrics", port: 8428, user: "", label: "VictoriaMetrics", icon: "victoriametrics" }, custom_mysql: { type: "mysql", port: 3306, @@ -1291,6 +1293,8 @@ function buildMqttExternalConfig(): MqttConnectionConfig { const influxDbVersion = ref("1"); const influxDbOrg = ref(""); +const victoriaMetricsApiPath = ref("/prometheus"); +const victoriaMetricsLookback = ref("1h"); function resetInfluxDbFields(config?: Partial) { influxDbVersion.value = config?.version === "2" ? "2" : "1"; @@ -1328,6 +1332,33 @@ function buildInfluxDbExternalConfig(): InfluxDbExternalConfig { return { version: "2", org }; } +function resetVictoriaMetricsFields(config?: Partial) { + victoriaMetricsApiPath.value = config?.apiPath?.trim() || "/prometheus"; + victoriaMetricsLookback.value = config?.lookback?.trim() || "1h"; +} + +function hydrateVictoriaMetricsFields(value: unknown) { + if (!value || typeof value !== "object") { + resetVictoriaMetricsFields(); + return; + } + const config = value as Partial & { api_path?: string }; + resetVictoriaMetricsFields({ + apiPath: config.apiPath || config.api_path, + lookback: config.lookback, + }); +} + +function buildVictoriaMetricsExternalConfig(): VictoriaMetricsExternalConfig { + const apiPath = victoriaMetricsApiPath.value.trim().replace(/\/+$/, ""); + if (apiPath && !apiPath.startsWith("/")) throw new Error(t("connection.victoriametricsInvalidApiPath")); + const lookback = victoriaMetricsLookback.value.trim(); + if (!/^\d+(?:ms|[smhdwy])$/.test(lookback) || lookback.startsWith("0")) { + throw new Error(t("connection.victoriametricsInvalidLookback")); + } + return { apiPath, lookback }; +} + watch(influxDbVersion, (version) => { if (form.value.db_type !== "influxdb") return; if (version === "2") { @@ -2070,6 +2101,13 @@ function applyProfile(val: string, preserveConnectionFields = false) { form.value.password = ""; form.value.connection_string = undefined; } + if (profile.type === "victoriametrics") { + resetVictoriaMetricsFields(); + form.value.database = "metrics"; + form.value.password = ""; + form.value.connection_string = undefined; + form.value.url_params = ""; + } resetHiveKerberosFields(profile.type === "hive" ? form.value : undefined); } } @@ -2177,6 +2215,11 @@ watch( } else { resetInfluxDbFields(); } + if (config.db_type === "victoriametrics") { + hydrateVictoriaMetricsFields(config.external_config); + } else { + resetVictoriaMetricsFields(); + } resetElasticsearchProxyFields(config.db_type === "elasticsearch" ? config.external_config : undefined); resetHiveKerberosFields(config.db_type === "hive" ? config : undefined); resetDamengJvmOptions(config.db_type === "dameng" ? config : undefined); @@ -2436,6 +2479,7 @@ const iconTypeMap: Record = { sundb: "sundb", oscar: "oscar", influxdb: "influxdb", + victoriametrics: "victoriametrics", jdbc: "jdbc", custom_mysql: "mysql", custom_postgres: "postgres", @@ -2519,6 +2563,7 @@ const dbOptions: DbOption[] = [ { value: "mqtt", label: "MQTT" }, { value: "nacos", label: "Nacos" }, { value: "influxdb", label: "InfluxDB" }, + { value: "victoriametrics", label: "VictoriaMetrics" }, { value: "iris", label: "IRIS" }, { value: "jdbcx", label: "JDBCX" }, { value: "manticoresearch", label: "Manticore Search" }, @@ -2565,7 +2610,7 @@ const dbCategoryDefinitions: Array<{ { key: "timeseries", titleKey: "connection.databaseCategoryTimeseries", - optionValues: ["questdb", "tdengine", "iotdb", "influxdb"], + optionValues: ["questdb", "tdengine", "iotdb", "influxdb", "victoriametrics"], }, { key: "mq", @@ -2670,9 +2715,31 @@ const sqliteExtensionPaths = computed({ form.value.url_params = setSqliteExtensionPaths(form.value.url_params, value); }, }); -const tlsCapableDatabaseTypes = new Set(["mysql", "starrocks", "postgres", "redshift", "gaussdb", "kwdb", "opengauss", "questdb", "dameng", "redis", "etcd", "clickhouse", "elasticsearch", "easysearch", "hbase", "qdrant", "milvus", "weaviate", "chromadb", "influxdb"]); +const tlsCapableDatabaseTypes = new Set([ + "mysql", + "starrocks", + "postgres", + "redshift", + "gaussdb", + "kwdb", + "opengauss", + "questdb", + "dameng", + "redis", + "etcd", + "clickhouse", + "elasticsearch", + "easysearch", + "hbase", + "qdrant", + "milvus", + "weaviate", + "chromadb", + "influxdb", + "victoriametrics", +]); const supportsTlsToggle = computed(() => tlsCapableDatabaseTypes.has(form.value.db_type)); -const supportsCaCertificatePath = computed(() => form.value.db_type === "clickhouse"); +const supportsCaCertificatePath = computed(() => form.value.db_type === "clickhouse" || form.value.db_type === "victoriametrics"); const supportsGenericUrlParams = computed(() => form.value.db_type !== "manticoresearch" && form.value.db_type !== "hbase"); const bareMysqlProfiles = new Set(["doris", "selectdb", "oceanbase"]); const supportsMysqlTlsOptions = computed(() => form.value.db_type === "starrocks" || (form.value.db_type === "mysql" && !bareMysqlProfiles.has(selectedType.value))); @@ -3117,6 +3184,9 @@ function applyConnectionUrlToForm(input: string): boolean { const parsed = parseConnectionUrl(input, selectedType.value); form.value = applyParsedConnectionUrl(form.value, parsed); + if (form.value.db_type === "victoriametrics") { + hydrateVictoriaMetricsFields(form.value.external_config); + } oracleTnsAdminPath.value = parseOracleTnsConnectionString(parsed.connectionString)?.tnsAdmin || ""; selectedType.value = parsed.driverProfile; customDriverName.value = isCustomCompatibleProfile() ? parsed.driverLabel : ""; @@ -3405,6 +3475,11 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo config.password = config.password.trim(); config.database = config.database?.trim() || undefined; } + } else if (config.db_type === "victoriametrics") { + config.external_config = buildVictoriaMetricsExternalConfig(); + config.connection_string = undefined; + config.database = "metrics"; + config.username = config.username.trim(); } else if (config.db_type === "elasticsearch") { config.external_config = buildElasticsearchExternalConfig(elasticsearchConnectionMode.value, elasticsearchKibanaBasePath.value, elasticsearchConnectivityCheckPath.value); } else if (config.db_type === "sqlserver") { @@ -3518,7 +3593,7 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo config.client_cert_path = undefined; config.client_key_path = undefined; } - if (config.db_type !== "mysql" && config.db_type !== "clickhouse" && config.db_type !== "etcd" && config.db_type !== "starrocks" && config.db_type !== "mongodb") { + if (config.db_type !== "mysql" && config.db_type !== "clickhouse" && config.db_type !== "etcd" && config.db_type !== "starrocks" && config.db_type !== "mongodb" && config.db_type !== "victoriametrics") { config.ca_cert_path = undefined; } else { config.ca_cert_path = config.ca_cert_path?.trim() || ""; @@ -6062,6 +6137,43 @@ function openExternalUrl(url: string) { + +