feat(db): 支持 VictoriaMetrics 数据库
* 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>
This commit is contained in:
1 parent
930aa48c20
commit
2ede64f1cb
64 files changed
+1293
-64
No files matched your search
@@ -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<InfluxDbVersion>("1");
|
||||
const influxDbOrg = ref("");
|
||||
const victoriaMetricsApiPath = ref("/prometheus");
|
||||
const victoriaMetricsLookback = ref("1h");
|
||||
|
||||
function resetInfluxDbFields(config?: Partial<InfluxDbExternalConfig>) {
|
||||
influxDbVersion.value = config?.version === "2" ? "2" : "1";
|
||||
@@ -1328,6 +1332,33 @@ function buildInfluxDbExternalConfig(): InfluxDbExternalConfig {
|
||||
return { version: "2", org };
|
||||
}
|
||||
|
||||
function resetVictoriaMetricsFields(config?: Partial<VictoriaMetricsExternalConfig>) {
|
||||
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<VictoriaMetricsExternalConfig> & { 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<string, string> = {
|
||||
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<DatabaseType>(["mysql", "starrocks", "postgres", "redshift", "gaussdb", "kwdb", "opengauss", "questdb", "dameng", "redis", "etcd", "clickhouse", "elasticsearch", "easysearch", "hbase", "qdrant", "milvus", "weaviate", "chromadb", "influxdb"]);
|
||||
const tlsCapableDatabaseTypes = new Set<DatabaseType>([
|
||||
"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) {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="form.db_type === 'victoriametrics'">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelClass">{{ t("connection.host") }}</Label>
|
||||
<Input v-model="form.host" class="col-span-2" />
|
||||
<Input v-model.number="form.port" type="number" class="col-span-1" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<span />
|
||||
<label class="col-span-3 flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" v-model="form.ssl" />
|
||||
<span>{{ t("connection.sslEnable") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelClass">{{ t("connection.user") }}</Label>
|
||||
<Input v-model="form.username" class="col-span-3" autocomplete="username" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelClass">{{ t("connection.password") }}</Label>
|
||||
<PasswordInput v-model="form.password" class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<Label :class="connectionLabelSmallClass">{{ t("connection.victoriametricsApiPath") }}</Label>
|
||||
<div class="col-span-3 space-y-1.5">
|
||||
<Input v-model="victoriaMetricsApiPath" placeholder="/prometheus" />
|
||||
<p class="text-xs leading-5 text-muted-foreground">{{ t("connection.victoriametricsApiPathHint") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<Label :class="connectionLabelSmallClass">{{ t("connection.victoriametricsLookback") }}</Label>
|
||||
<div class="col-span-3 space-y-1.5">
|
||||
<Input v-model="victoriaMetricsLookback" class="w-28" placeholder="1h" />
|
||||
<p class="text-xs leading-5 text-muted-foreground">{{ t("connection.victoriametricsLookbackHint") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- InfluxDB: v1 username/password or v2 token/org/bucket -->
|
||||
<template v-else-if="form.db_type === 'influxdb'">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
|
||||
Reference in new issue
Block a user