feat(admin): support openGauss and Kingbase monitoring

This commit is contained in:
amwps290 authored and GitHub committed 2026-07-30 18:40:12 +08:00
1 parent cd3163d737
commit 51315e549f
9 files changed
+558 -73

No files matched your search

@@ -9,23 +9,7 @@ import { useConnectionStore } from "@/stores/connectionStore";
import MetricCard from "@/components/common/MetricCard.vue";
import MetricLineChart from "@/components/chart/MetricLineChart.vue";
import * as api from "@/lib/backend/api";
import {
computePgTps,
computeRate,
formatBytesPerSec,
formatNumber,
formatRate,
formatUptime,
isPgStatusCompatibilityError,
MAX_SAMPLES,
parsePgStatusRow,
pgCacheHitRatio,
PG_STATUS_LEGACY_SQL,
PG_STATUS_SQL,
PG_VARIABLES_SQL,
statusNumber,
type StatusSample,
} from "@/lib/database/postgresServerStatus";
import { computePgTps, computeRate, formatBytesPerSec, formatNumber, formatRate, formatUptime, MAX_SAMPLES, parsePgStatusRow, pgCacheHitRatio, resolveServerDashboardDriverForConnection, statusNumber, type StatusSample } from "@/lib/database/postgresServerStatus";
import { useVerticalOverlayScrollbar } from "@/composables/useVerticalOverlayScrollbar";
const props = defineProps<{
@@ -58,7 +42,9 @@ const {
const fallbackStatusSql = ref<string | null>(null);
let refreshTimer: ReturnType<typeof setInterval> | null = null;
const connectionName = computed(() => connectionStore.getConfig(props.connectionId)?.name ?? "");
const connection = computed(() => connectionStore.getConfig(props.connectionId));
const statusDriver = computed(() => resolveServerDashboardDriverForConnection(connection.value));
const connectionName = computed(() => connection.value?.name ?? "");
const latest = computed(() => samples.value[samples.value.length - 1]);
const previous = computed(() => (samples.value.length >= 2 ? samples.value[samples.value.length - 2] : undefined));
@@ -138,8 +124,10 @@ function formatClock(at: number): string {
}
async function fetchVariables() {
const activeDriver = statusDriver.value;
if (!activeDriver) return;
try {
const result = await api.executeQuery(props.connectionId, "", PG_VARIABLES_SQL, undefined, undefined, { maxRows: 2000 });
const result = await api.executeQuery(props.connectionId, "", activeDriver.variablesSql, undefined, undefined, { maxRows: 2000 });
variables.value = parsePgStatusRow(result);
} catch {
// Non-fatal: cards that depend on variables (max_connections/version) degrade.
@@ -147,20 +135,22 @@ async function fetchVariables() {
}
async function fetchStatus(options: { silent?: boolean } = {}) {
const activeDriver = statusDriver.value;
if (!activeDriver) return;
if (fetching.value) return;
fetching.value = true;
if (!options.silent) loading.value = true;
error.value = "";
try {
await connectionStore.ensureConnected(props.connectionId);
const sql = fallbackStatusSql.value ?? PG_STATUS_SQL;
const sql = fallbackStatusSql.value ?? activeDriver.statusSql;
let result;
try {
result = await api.executeQuery(props.connectionId, "", sql, undefined, undefined, { maxRows: 2000 });
} catch (queryError) {
if (fallbackStatusSql.value || !isPgStatusCompatibilityError(queryError)) throw queryError;
result = await api.executeQuery(props.connectionId, "", PG_STATUS_LEGACY_SQL, undefined, undefined, { maxRows: 2000 });
fallbackStatusSql.value = PG_STATUS_LEGACY_SQL;
if (fallbackStatusSql.value || !activeDriver.fallbackStatusSql || !activeDriver.shouldUseFallbackStatusSql?.(queryError)) throw queryError;
result = await api.executeQuery(props.connectionId, "", activeDriver.fallbackStatusSql, undefined, undefined, { maxRows: 2000 });
fallbackStatusSql.value = activeDriver.fallbackStatusSql;
}
const sample: StatusSample = { at: Date.now(), status: parsePgStatusRow(result) };
const next = [...samples.value, sample];
@@ -212,8 +202,8 @@ onUnmounted(stopAutoRefresh);
<div class="flex h-11 shrink-0 items-center gap-2 border-b bg-muted/20 px-3">
<Gauge class="h-4 w-4 text-primary" />
<div class="truncate text-sm font-semibold">{{ t("serverDashboard.title") }}</div>
<Badge variant="outline" class="h-5 rounded-md px-1.5 text-[11px]">{{ connectionName }}</Badge>
<Badge v-if="serverVersion" variant="secondary" class="h-5 rounded-md px-1.5 text-[11px]">{{ serverVersion }}</Badge>
<Badge variant="outline" class="h-5 max-w-48 truncate rounded-md px-1.5 text-[11px]" :title="connectionName">{{ connectionName }}</Badge>
<Badge v-if="serverVersion" variant="secondary" class="h-5 max-w-64 truncate rounded-md px-1.5 text-[11px]" :title="serverVersion">{{ serverVersion }}</Badge>
<div class="ml-auto flex items-center gap-2">
<span class="text-xs text-muted-foreground">{{ t("serverDashboard.autoRefresh") }}</span>
<Select :model-value="String(autoRefreshInterval)" @update:model-value="onIntervalChange">
@@ -99,7 +99,13 @@ async function load(options: { silent?: boolean } = {}) {
await connectionStore.ensureConnected(props.connection.id);
if (ownSessionId.value === null) {
try {
const idResult = await api.executeQuery(props.connection.id, "", activeDriver.ownSessionSql, undefined, undefined, { maxRows: 1 });
let idResult;
try {
idResult = await api.executeQuery(props.connection.id, "", activeDriver.ownSessionSql, undefined, undefined, { maxRows: 1 });
} catch (error) {
if (!activeDriver.fallbackOwnSessionSql || !activeDriver.shouldUseFallbackOwnSessionSql?.(error)) throw error;
idResult = await api.executeQuery(props.connection.id, "", activeDriver.fallbackOwnSessionSql, undefined, undefined, { maxRows: 1 });
}
const raw = idResult?.rows?.[0]?.[0];
const parsed = Number(raw);
if (Number.isFinite(parsed)) ownSessionId.value = parsed;
@@ -143,17 +149,30 @@ async function confirmKill() {
killing.value = true;
try {
const killSql = activeDriver.buildKillSql(target.id);
let usedFallbackKillSql = false;
const executeKillSql = async (sql: string) => {
const results = await api.executeMulti(props.connection.id, "", sql, undefined, undefined, { maxRows: 1 });
const executionError = processListExecutionError(results);
if (executionError) throw new Error(executionError);
return results;
};
const result = await executeWithProductionSqlGuard({
connection: props.connection,
database: "",
sql: killSql,
source: t("production.sourceAdmin"),
execute: () => api.executeMulti(props.connection.id, "", killSql, undefined, undefined, { maxRows: 1 }),
execute: async () => {
try {
return await executeKillSql(killSql);
} catch (error) {
if (!activeDriver.buildFallbackKillSql || !activeDriver.shouldUseFallbackKillSql?.(error)) throw error;
usedFallbackKillSql = true;
return executeKillSql(activeDriver.buildFallbackKillSql(target.id));
}
},
});
if (result === undefined) return;
const executionError = processListExecutionError(result);
if (executionError) throw new Error(executionError);
const killResultError = activeDriver.killResultError?.(result);
const killResultError = usedFallbackKillSql ? activeDriver.fallbackKillResultError?.(result) : activeDriver.killResultError?.(result);
if (killResultError) throw new Error(killResultError);
toast(t("processList.killSuccess", { id: target.id }), 2500);
killTarget.value = null;
@@ -1138,7 +1138,7 @@ async function openServerDashboard() {
connectionStore.activeConnectionId = node.connectionId;
if (currentDatabaseType() === "nacos") {
queryStore.openNacosDashboard(node.connectionId);
} else if (currentDatabaseType() === "postgres") {
} else if (connectionSupportsPgServerDashboard(connectionStore.getConfig(node.connectionId))) {
queryStore.openPostgresDashboard(node.connectionId);
} else {
queryStore.openMysqlDashboard(node.connectionId);