diff --git a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue
index 37dba18a83..8a514a2d22 100644
--- a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue
+++ b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue
@@ -174,6 +174,7 @@ import { authorizationPlanSql, authorizationPlanStatus, buildCreateDatabaseAutho
import { connectionSupportsProcessList } from "@/lib/database/processListDrivers";
import { connectionSupportsServerDashboard } from "@/lib/database/mysqlServerStatus";
import { connectionSupportsServerDashboard as connectionSupportsPgServerDashboard } from "@/lib/database/postgresServerStatus";
+import { connectionSupportsXuguServerDashboard } from "@/lib/database/xuguServerStatus";
import { sidebarTreeContextKey } from "@/lib/sidebar/sidebarTreeContext";
import { sidebarTreeArrowAction } from "@/lib/sidebar/sidebarTreeArrowNavigation";
import { batchTableEmptyFeedback, runBatchTableEmpty } from "@/lib/sidebar/batchTableEmpty";
@@ -1729,6 +1730,8 @@ async function openServerDashboard() {
connectionStore.activeConnectionId = node.connectionId;
if (currentDatabaseType() === "nacos") {
queryStore.openNacosDashboard(node.connectionId);
+ } else if (connectionSupportsXuguServerDashboard(connectionStore.getConfig(node.connectionId))) {
+ queryStore.openXuguDashboard(node.connectionId);
} else if (connectionSupportsPgServerDashboard(connectionStore.getConfig(node.connectionId))) {
queryStore.openPostgresDashboard(node.connectionId);
} else {
@@ -5222,7 +5225,10 @@ function buildConnectionSidebarMenu(context: SidebarMenuFactoryContext): boolean
if (currentDatabaseType() === "sqlserver") {
items.push({ label: t("contextMenu.sqlServerTrace"), action: openSqlServerActivityTrace, icon: Activity });
}
- if (node.connectionId && (currentDatabaseType() === "nacos" || connectionSupportsServerDashboard(connectionStore.getConfig(node.connectionId)) || connectionSupportsPgServerDashboard(connectionStore.getConfig(node.connectionId)))) {
+ if (
+ node.connectionId &&
+ (currentDatabaseType() === "nacos" || connectionSupportsXuguServerDashboard(connectionStore.getConfig(node.connectionId)) || connectionSupportsServerDashboard(connectionStore.getConfig(node.connectionId)) || connectionSupportsPgServerDashboard(connectionStore.getConfig(node.connectionId)))
+ ) {
items.push({ label: t("contextMenu.serverDashboard"), action: openServerDashboard, icon: Gauge });
}
if (currentDatabaseType() === "dameng") {
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index 5dbbd17d54..1351a26cca 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -3619,6 +3619,74 @@ export default {
tuplesOutChart: "Tuples out",
blockIoChart: "Block I/O",
},
+ xuguServerDashboard: {
+ title: "Xugu Server Dashboard",
+ off: "Off",
+ unsupported: "This page only supports XuguDB connections.",
+ loadFailed: "Unable to read Xugu runtime status.",
+ partialData: "Some monitoring views could not be read. The current account may lack system-view privileges; readable data is still shown.",
+ readOnlyHint: "Read-only monitoring; no database state is modified",
+ version: "Database version",
+ onlineNodes: "Online nodes",
+ sessions: "Sessions",
+ activeCount: "{count} active",
+ transactions: "Active transactions",
+ lockWaits: "Lock waits (table-level)",
+ lockOwners: "Held locks",
+ nodes: "Node status",
+ node: "Node",
+ type: "Type",
+ state: "State",
+ cpuLoad: "CPU load (reference)",
+ diskRead: "Disk read",
+ diskWrite: "Disk write",
+ memoryAndCache: "Cache & memory",
+ memoryHint: "From system memory views",
+ bufferPool: "Data buffer pool",
+ sga: "SGA",
+ swap: "Swap",
+ usedPercent: "{percent}% used",
+ dirtyPages: "Dirty {value}",
+ runtimeAndWrite: "Runtime & writes",
+ cumulativeHint: "Cumulative counters; they do not reset on refresh",
+ walPending: "WAL pending checkpoint",
+ transactionSpan: "Transaction ID span (risk signal)",
+ freeStores: "Free stores",
+ datafiles: "Data files",
+ sessionsAndTransactions: "Sessions & transactions",
+ activeSessionHint: "Active sessions include this monitor query",
+ idleSessions: "Idle sessions",
+ executingSessions: "Executing sessions",
+ otherSessions: "Other states",
+ oldestStatement: "Oldest active statement started",
+ oldestTransaction: "Oldest active transaction started",
+ locksAndCapacity: "Locks & capacity",
+ tablespaces: "Tablespaces",
+ mediaErrors: "Media errors",
+ lockModes: "Held lock modes",
+ dataTempFree: "DATA/TEMP free {value}",
+ rowLockLimit: "Lock waits cover table-level locks only; investigate row blocking through long transactions.",
+ bootTime: "Boot time",
+ rack: "Rack {rack}",
+ stores: "{count} stores",
+ emptyNodes: "No node data was returned, or the account cannot access SYS_CLUSTERS.",
+ nodeState: {
+ joining: "Joining",
+ running: "Running",
+ error: "Error",
+ offline: "Offline",
+ unknown: "Unknown",
+ },
+ nodeType: {
+ master: "Master",
+ standby: "Standby",
+ storage: "Storage",
+ query: "Query",
+ worker: "Worker",
+ change: "Change",
+ unknown: "Unknown",
+ },
+ },
userAdmin: {
title: "Users & Privileges",
unsupported: "MySQL-compatible and PostgreSQL-compatible connections are supported. SQL Server, Oracle, and other permission models can be added next.",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index 97008f9158..fb37f4beb9 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -3606,6 +3606,74 @@ export default withEnglishFallback({
tuplesOutChart: "元组读取",
blockIoChart: "块 I/O",
},
+ xuguServerDashboard: {
+ title: "虚谷服务器仪表盘",
+ off: "关闭",
+ unsupported: "当前页面仅支持虚谷数据库连接。",
+ loadFailed: "无法读取虚谷运行状态。",
+ partialData: "部分监控视图不可读,当前账号可能缺少系统视图权限;已展示可读取的数据。",
+ readOnlyHint: "只读监控,不会修改数据库状态",
+ version: "数据库版本",
+ onlineNodes: "在线节点",
+ sessions: "会话",
+ activeCount: "活跃 {count}",
+ transactions: "活跃事务",
+ lockWaits: "锁等待(表级)",
+ lockOwners: "持有锁",
+ nodes: "节点运行状态",
+ node: "节点",
+ type: "类型",
+ state: "状态",
+ cpuLoad: "CPU 负载(参考)",
+ diskRead: "磁盘读取",
+ diskWrite: "磁盘写入",
+ memoryAndCache: "缓存与内存",
+ memoryHint: "基于系统内存视图",
+ bufferPool: "数据缓冲池",
+ sga: "SGA",
+ swap: "交换区",
+ usedPercent: "已使用 {percent}%",
+ dirtyPages: "脏页 {value}",
+ runtimeAndWrite: "运行与写入",
+ cumulativeHint: "累计计数,刷新不会重置",
+ walPending: "待检查点 WAL",
+ transactionSpan: "事务 ID 跨度(风险信号)",
+ freeStores: "空闲存储段数",
+ datafiles: "数据文件数",
+ sessionsAndTransactions: "会话与事务",
+ activeSessionHint: "活跃会话含本监控查询",
+ idleSessions: "空闲会话",
+ executingSessions: "执行中会话",
+ otherSessions: "其他状态",
+ oldestStatement: "最早执行语句开始时间",
+ oldestTransaction: "最早活跃事务开始时间",
+ locksAndCapacity: "锁与容量",
+ tablespaces: "表空间数",
+ mediaErrors: "介质错误数",
+ lockModes: "当前持有锁模式",
+ dataTempFree: "DATA/TEMP 空闲 {value}",
+ rowLockLimit: "锁等待仅覆盖表级锁;行级阻塞需结合长事务排查。",
+ bootTime: "启动时间",
+ rack: "机架 {rack}",
+ stores: "存储段 {count}",
+ emptyNodes: "未读取到节点信息,或当前账号无权访问 SYS_CLUSTERS。",
+ nodeState: {
+ joining: "加入中",
+ running: "运行中",
+ error: "错误",
+ offline: "离线",
+ unknown: "未知",
+ },
+ nodeType: {
+ master: "主节点",
+ standby: "备节点",
+ storage: "存储节点",
+ query: "查询节点",
+ worker: "工作节点",
+ change: "变更节点",
+ unknown: "未知",
+ },
+ },
userAdmin: {
title: "用户与权限",
unsupported: "当前支持 MySQL 兼容与 PostgreSQL 兼容连接。SQL Server、Oracle 等会按各自权限模型继续扩展。",
diff --git a/apps/desktop/src/lib/__tests__/database/xuguServerStatus.spec.ts b/apps/desktop/src/lib/__tests__/database/xuguServerStatus.spec.ts
new file mode 100644
index 0000000000..e5d6cb5555
--- /dev/null
+++ b/apps/desktop/src/lib/__tests__/database/xuguServerStatus.spec.ts
@@ -0,0 +1,108 @@
+import { describe, expect, it } from "vitest";
+import {
+ XUGU_ACTIVE_SESSION_SQL,
+ XUGU_CLUSTER_NODES_SQL,
+ XUGU_LOCK_MODE_SUMMARY_SQL,
+ XUGU_LOCK_WAITS_SQL,
+ XUGU_MEMORY_STATUS_SQL,
+ XUGU_RUN_INFO_SQL,
+ XUGU_SESSION_SUMMARY_SQL,
+ XUGU_SESSION_STATUS_SUMMARY_SQL,
+ XUGU_TABLESPACE_SUMMARY_SQL,
+ XUGU_TRANSACTION_SUMMARY_SQL,
+ XUGU_VERSION_SQL,
+ connectionSupportsXuguServerDashboard,
+ xuguClusterNodeStateLabel,
+ xuguClusterNodeTypeLabels,
+ xuguClusterNodesFromResult,
+ xuguLockModeSummaryFromResult,
+ xuguMemoryStatusFromResult,
+ xuguRunInfoFromResult,
+ xuguSessionSummaryFromResult,
+ xuguSessionStatusSummaryFromResult,
+ xuguTablespaceSummaryFromResult,
+ xuguTransactionSummaryFromResult,
+ xuguVersionFromResult,
+} from "@/lib/database/xuguServerStatus";
+
+describe("Xugu server status helpers", () => {
+ it("keeps the first-phase catalog queries scoped to Xugu system views", () => {
+ expect(XUGU_VERSION_SQL).toContain("VERSION()");
+ expect(XUGU_CLUSTER_NODES_SQL).toContain("FROM SYS_CLUSTERS");
+ expect(XUGU_RUN_INFO_SQL).toContain("FROM SYS_ALL_RUN_INFO");
+ expect(XUGU_SESSION_SUMMARY_SQL).toContain("FROM SYS_ALL_SESSIONS");
+ expect(XUGU_ACTIVE_SESSION_SQL).toContain("FROM SYS_ALL_THD_SESSION");
+ expect(XUGU_ACTIVE_SESSION_SQL).toContain("JOIN SYS_ALL_SESSIONS");
+ expect(XUGU_ACTIVE_SESSION_SQL).not.toMatch(/WHERE\s+STATE\s*=\s*1/i);
+ expect(XUGU_TRANSACTION_SUMMARY_SQL).toContain("FROM SYS_ALL_TRANS");
+ expect(XUGU_LOCK_WAITS_SQL).toContain("SYS_ALL_LWAITERS");
+ expect(XUGU_LOCK_MODE_SUMMARY_SQL).toContain("SYS_ALL_LOWNERS");
+ expect(XUGU_SESSION_STATUS_SUMMARY_SQL).toContain("FROM SYS_ALL_SESSIONS");
+ expect(XUGU_MEMORY_STATUS_SQL).toContain("SYS_ALL_MEM_STATUS");
+ expect(XUGU_TABLESPACE_SUMMARY_SQL).toContain("SYS_ALL_TABLESPACES");
+ });
+
+ it("recognizes native and JDBC Xugu connections only", () => {
+ expect(connectionSupportsXuguServerDashboard({ id: "x", name: "Xugu", db_type: "xugu" } as any)).toBe(true);
+ expect(connectionSupportsXuguServerDashboard({ id: "pg", name: "Postgres", db_type: "postgres" } as any)).toBe(false);
+ expect(connectionSupportsXuguServerDashboard(undefined)).toBe(false);
+ });
+
+ it("maps documented node states and roles", () => {
+ expect(xuguClusterNodeStateLabel("2")).toBe("running");
+ expect(xuguClusterNodeStateLabel("4")).toBe("offline");
+ expect(xuguClusterNodeTypeLabels("1")).toEqual(["master"]);
+ expect(xuguClusterNodeTypeLabels("29")).toEqual(["master", "storage", "query", "worker"]);
+ expect(xuguClusterNodeTypeLabels("0")).toEqual([]);
+ expect(xuguClusterNodeStateLabel("999")).toBe("unknown");
+ });
+
+ it("parses status rows by column name instead of column order", () => {
+ const nodes = xuguClusterNodesFromResult({
+ columns: ["NODE_IP", "NODE_ID", "NODE_STATE", "NODE_TYPE", "NODE_PORT", "RACK_NO", "CPU_LOAD", "BOOT_TIME", "STORE_NUM", "MAJOR_NUM"],
+ rows: [["127.0.0.1", 1, 2, 1, 5138, 0, 4, "2026-01-01", 10, 8]],
+ });
+ const runInfo = xuguRunInfoFromResult({
+ columns: ["FREE_STO_N", "NODEID", "ACT_TRANS_NUM", "DISK_R_N", "DISK_R_BYTES", "DISK_W_N", "DISK_W_BYTES", "MAX_TRANS_ID", "MIN_TRANS_ID", "XLOG_WPOS", "XLOG_CKPT"],
+ rows: [[11, 1, 2, 3, 4, 5, 6, 100, 99, 1000, 900]],
+ });
+ const sessions = xuguSessionSummaryFromResult({
+ columns: ["MEMORY_BYTES", "SESSIONS", "NODE_ID", "ACTIVE_SESSIONS", "OLDEST_STATEMENT"],
+ rows: [[1024, 4, 1, 2, "2026-01-01 00:00:00"]],
+ });
+ const transactions = xuguTransactionSummaryFromResult({
+ columns: ["ACTIVE_TRANSACTIONS", "NODE_ID", "OLDEST_TRANSACTION"],
+ rows: [[3, 1, "2026-01-01 00:00:00"]],
+ });
+ const memory = xuguMemoryStatusFromResult({
+ columns: ["SGA_FREE_BYTES", "NODEID", "SGA_TOTAL_BYTES", "BUFFER_FREE_BYTES", "BUFFER_TOTAL_BYTES"],
+ rows: [[20, 1, 100, 40, 200]],
+ });
+ const sessionStatuses = xuguSessionStatusSummaryFromResult({
+ columns: ["SESSIONS", "STATUS", "NODE_ID"],
+ rows: [[3, 112, 1]],
+ });
+ const lockModes = xuguLockModeSummaryFromResult({
+ columns: ["LOCKS", "LOCK_MODE", "NODE_ID"],
+ rows: [[3, "IS", 1]],
+ });
+ const tablespaces = xuguTablespaceSummaryFromResult({
+ columns: ["FREE_BYTES", "TOTAL_BYTES", "MEDIA_ERROR", "DATAFILES", "SPACE_TYPE", "SPACE_NAME", "NODE_ID"],
+ rows: [[10, 100, false, 1, "DATA_SPACE", "DATA1", 1]],
+ });
+
+ expect(nodes[0]).toMatchObject({ nodeId: "1", host: "127.0.0.1", state: "2", majorCount: "8" });
+ expect(runInfo[0]).toMatchObject({ nodeId: "1", activeTransactions: "2", diskReadCount: "3", freeStores: "11", xlogWritePosition: "1000" });
+ expect(sessions[0]).toMatchObject({ nodeId: "1", sessions: "4", activeSessions: "2", memoryBytes: "1024", oldestStatement: "2026-01-01 00:00:00" });
+ expect(transactions[0]).toMatchObject({ nodeId: "1", activeTransactions: "3", oldestTransaction: "2026-01-01 00:00:00" });
+ expect(memory[0]).toMatchObject({ nodeId: "1", sgaTotalBytes: "100", sgaFreeBytes: "20", bufferTotalBytes: "200" });
+ expect(sessionStatuses[0]).toMatchObject({ nodeId: "1", status: "112", sessions: "3" });
+ expect(lockModes[0]).toMatchObject({ nodeId: "1", lockMode: "IS", locks: "3" });
+ expect(tablespaces[0]).toMatchObject({ nodeId: "1", spaceName: "DATA1", spaceType: "DATA_SPACE", totalBytes: "100" });
+ });
+
+ it("returns an empty value for missing or null values", () => {
+ expect(xuguVersionFromResult({ columns: ["VERSION"], rows: [[null]] })).toBe("");
+ expect(xuguVersionFromResult({ columns: ["OTHER"], rows: [["x"]] })).toBe("");
+ });
+});
diff --git a/apps/desktop/src/lib/database/xuguServerStatus.ts b/apps/desktop/src/lib/database/xuguServerStatus.ts
new file mode 100644
index 0000000000..334b548975
--- /dev/null
+++ b/apps/desktop/src/lib/database/xuguServerStatus.ts
@@ -0,0 +1,329 @@
+import type { ConnectionConfig, QueryResult } from "@/types/database";
+import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
+
+export interface XuguClusterNode {
+ nodeId: string;
+ rackNo: string;
+ host: string;
+ port: string;
+ nodeType: string;
+ state: string;
+ cpuLoad: string;
+ bootTime: string;
+ storeCount: string;
+ majorCount: string;
+}
+
+export interface XuguRunInfo {
+ nodeId: string;
+ activeTransactions: string;
+ diskReadCount: string;
+ diskReadBytes: string;
+ diskWriteCount: string;
+ diskWriteBytes: string;
+ maxTransactionId: string;
+ minTransactionId: string;
+ xlogWritePosition: string;
+ xlogCheckpointPosition: string;
+ freeStores: string;
+}
+
+export interface XuguSessionSummary {
+ nodeId: string;
+ sessions: string;
+ activeSessions: string;
+ memoryBytes: string;
+ oldestStatement: string;
+}
+
+export interface XuguTransactionSummary {
+ nodeId: string;
+ activeTransactions: string;
+ oldestTransaction: string;
+}
+
+export interface XuguMemoryStatus {
+ nodeId: string;
+ bufferTotalBytes: string;
+ bufferFreeBytes: string;
+ bufferDirtyBytes: string;
+ bufferLruBytes: string;
+ sgaTotalBytes: string;
+ sgaFreeBytes: string;
+ sgaPeakBytes: string;
+ swapTotalBytes: string;
+ swapFreeBytes: string;
+}
+
+export interface XuguSessionStatusSummary {
+ nodeId: string;
+ status: string;
+ sessions: string;
+}
+
+export interface XuguLockModeSummary {
+ nodeId: string;
+ lockMode: string;
+ locks: string;
+}
+
+export interface XuguTablespaceSummary {
+ nodeId: string;
+ spaceName: string;
+ spaceType: string;
+ datafiles: string;
+ mediaError: string;
+ totalBytes: string;
+ freeBytes: string;
+}
+
+export const XUGU_VERSION_SQL = "SELECT VERSION() AS VERSION FROM DUAL;";
+
+export const XUGU_CLUSTER_NODES_SQL = `
+SELECT
+ NODE_ID,
+ RACK_NO,
+ NODE_IP,
+ NODE_PORT,
+ NODE_TYPE,
+ NODE_STATE,
+ CPU_LOAD,
+ BOOT_TIME,
+ STORE_NUM,
+ MAJOR_NUM
+FROM SYS_CLUSTERS
+ORDER BY NODE_ID;`.trim();
+
+export const XUGU_RUN_INFO_SQL = `
+SELECT
+ NODEID,
+ ACT_TRANS_NUM,
+ DISK_R_N,
+ DISK_R_BYTES,
+ DISK_W_N,
+ DISK_W_BYTES,
+ MAX_TRANS_ID,
+ MIN_TRANS_ID,
+ XLOG_WPOS,
+ XLOG_CKPT,
+ FREE_STO_N
+FROM SYS_ALL_RUN_INFO
+ORDER BY NODEID;`.trim();
+
+/** Per-node session totals. This is optional because restricted users may not see SYS_ALL_SESSIONS. */
+export const XUGU_SESSION_SUMMARY_SQL = `
+SELECT NODEID AS NODE_ID,
+ COUNT(*) AS SESSIONS,
+ SUM(MEM_SIZE) AS MEMORY_BYTES
+FROM SYS_ALL_SESSIONS
+GROUP BY NODEID
+ORDER BY NODEID;`.trim();
+
+/** The thread-session view is the Xugu-specific definition of an active statement. */
+export const XUGU_ACTIVE_SESSION_SQL = `
+SELECT T.NODEID AS NODE_ID,
+ COUNT(*) AS ACTIVE_SESSIONS,
+ MIN(S.CMD_START_T) AS OLDEST_STATEMENT
+FROM SYS_ALL_THD_SESSION T
+JOIN SYS_ALL_SESSIONS S
+ ON T.NODEID = S.NODEID AND T.SESSION_ID = S.SESSION_ID
+GROUP BY T.NODEID
+ORDER BY T.NODEID;`.trim();
+
+export const XUGU_TRANSACTION_SUMMARY_SQL = `
+SELECT NODEID AS NODE_ID,
+ COUNT(*) AS ACTIVE_TRANSACTIONS,
+ MIN(START_T) AS OLDEST_TRANSACTION
+FROM SYS_ALL_TRANS
+GROUP BY NODEID
+ORDER BY NODEID;`.trim();
+
+/** Only table-level lock waiters are exposed by SYS_ALL_LWAITERS. */
+export const XUGU_LOCK_WAITS_SQL = `
+SELECT
+ (SELECT COUNT(*) FROM SYS_ALL_LWAITERS) AS LOCK_WAITS,
+ (SELECT COUNT(*) FROM SYS_ALL_LOWNERS) AS LOCK_OWNERS
+FROM DUAL;`.trim();
+
+export const XUGU_LOCK_MODE_SUMMARY_SQL = `
+SELECT NODEID AS NODE_ID,
+ LOCK_LEVEL AS LOCK_MODE,
+ COUNT(*) AS LOCKS
+FROM SYS_ALL_LOWNERS
+GROUP BY NODEID, LOCK_LEVEL
+ORDER BY NODEID, LOCK_LEVEL;`.trim();
+
+export const XUGU_SESSION_STATUS_SUMMARY_SQL = `
+SELECT NODEID AS NODE_ID,
+ STATUS,
+ COUNT(*) AS SESSIONS
+FROM SYS_ALL_SESSIONS
+GROUP BY NODEID, STATUS
+ORDER BY NODEID, STATUS;`.trim();
+
+export const XUGU_MEMORY_STATUS_SQL = `
+SELECT
+ NODEID,
+ BUFF_SIZE * TOTAL_BUFF_NUM AS BUFFER_TOTAL_BYTES,
+ BUFF_SIZE * FREE_BUFF_NUM AS BUFFER_FREE_BYTES,
+ BUFF_SIZE * DIRTY_BUFF_NUM AS BUFFER_DIRTY_BYTES,
+ BUFF_SIZE * LRU_BUFF_NUM AS BUFFER_LRU_BYTES,
+ SGA_BLK_SIZE * TOTAL_SGA_MEM AS SGA_TOTAL_BYTES,
+ SGA_BLK_SIZE * FREE_SGA_MEM AS SGA_FREE_BYTES,
+ SGA_BLK_SIZE * PEAK_SGA_MEM AS SGA_PEAK_BYTES,
+ SWAP_BLK_SIZE * TOTAL_SWAP_MEM AS SWAP_TOTAL_BYTES,
+ SWAP_BLK_SIZE * FREE_SWAP_MEM AS SWAP_FREE_BYTES
+FROM SYS_ALL_MEM_STATUS
+ORDER BY NODEID;`.trim();
+
+export const XUGU_TABLESPACE_SUMMARY_SQL = `
+SELECT
+ T.NODEID AS NODE_ID,
+ T.SPACE_NAME,
+ T.SPACE_TYPE,
+ T.DATAFILE_NUM AS DATAFILES,
+ T.MEDIA_ERROR,
+ T.TOTAL_CHUNK_NUM * C.CHUNK_SIZE AS TOTAL_BYTES,
+ T.FREE_CHUNK_NUM * C.CHUNK_SIZE AS FREE_BYTES
+FROM SYS_ALL_TABLESPACES T, SYS_CTL_VARS C
+ORDER BY T.NODEID, T.SPACE_NAME;`.trim();
+
+export function connectionSupportsXuguServerDashboard(connection: ConnectionConfig | undefined): boolean {
+ return !!connection && effectiveDatabaseTypeForConnection(connection) === "xugu";
+}
+
+export function xuguClusterNodeStateLabel(state: string): "joining" | "running" | "error" | "offline" | "unknown" {
+ switch (Number(state)) {
+ case 1:
+ return "joining";
+ case 2:
+ return "running";
+ case 3:
+ return "error";
+ case 4:
+ return "offline";
+ default:
+ return "unknown";
+ }
+}
+
+export type XuguClusterNodeRole = "master" | "standby" | "storage" | "query" | "worker" | "change";
+
+export function xuguClusterNodeTypeLabels(type: string): XuguClusterNodeRole[] {
+ const value = Number(type);
+ if (!Number.isFinite(value) || value <= 0) return [];
+ const roles: Array<[number, XuguClusterNodeRole]> = [
+ [1, "master"],
+ [2, "standby"],
+ [4, "storage"],
+ [8, "query"],
+ [16, "worker"],
+ [32, "change"],
+ ];
+ return roles.filter(([bit]) => (value & bit) === bit).map(([, role]) => role);
+}
+
+export function xuguClusterNodesFromResult(result: QueryResult): XuguClusterNode[] {
+ return result.rows.map((row) => ({
+ nodeId: valueAt(result, row, "NODE_ID"),
+ rackNo: valueAt(result, row, "RACK_NO"),
+ host: valueAt(result, row, "NODE_IP"),
+ port: valueAt(result, row, "NODE_PORT"),
+ nodeType: valueAt(result, row, "NODE_TYPE"),
+ state: valueAt(result, row, "NODE_STATE"),
+ cpuLoad: valueAt(result, row, "CPU_LOAD"),
+ bootTime: valueAt(result, row, "BOOT_TIME"),
+ storeCount: valueAt(result, row, "STORE_NUM"),
+ majorCount: valueAt(result, row, "MAJOR_NUM"),
+ }));
+}
+
+export function xuguRunInfoFromResult(result: QueryResult): XuguRunInfo[] {
+ return result.rows.map((row) => ({
+ nodeId: valueAt(result, row, "NODEID"),
+ activeTransactions: valueAt(result, row, "ACT_TRANS_NUM"),
+ diskReadCount: valueAt(result, row, "DISK_R_N"),
+ diskReadBytes: valueAt(result, row, "DISK_R_BYTES"),
+ diskWriteCount: valueAt(result, row, "DISK_W_N"),
+ diskWriteBytes: valueAt(result, row, "DISK_W_BYTES"),
+ maxTransactionId: valueAt(result, row, "MAX_TRANS_ID"),
+ minTransactionId: valueAt(result, row, "MIN_TRANS_ID"),
+ xlogWritePosition: valueAt(result, row, "XLOG_WPOS"),
+ xlogCheckpointPosition: valueAt(result, row, "XLOG_CKPT"),
+ freeStores: valueAt(result, row, "FREE_STO_N"),
+ }));
+}
+
+export function xuguSessionSummaryFromResult(result: QueryResult): XuguSessionSummary[] {
+ return result.rows.map((row) => ({
+ nodeId: valueAt(result, row, "NODE_ID"),
+ sessions: valueAt(result, row, "SESSIONS"),
+ activeSessions: valueAt(result, row, "ACTIVE_SESSIONS"),
+ memoryBytes: valueAt(result, row, "MEMORY_BYTES"),
+ oldestStatement: valueAt(result, row, "OLDEST_STATEMENT"),
+ }));
+}
+
+export function xuguTransactionSummaryFromResult(result: QueryResult): XuguTransactionSummary[] {
+ return result.rows.map((row) => ({
+ nodeId: valueAt(result, row, "NODE_ID"),
+ activeTransactions: valueAt(result, row, "ACTIVE_TRANSACTIONS"),
+ oldestTransaction: valueAt(result, row, "OLDEST_TRANSACTION"),
+ }));
+}
+
+export function xuguMemoryStatusFromResult(result: QueryResult): XuguMemoryStatus[] {
+ return result.rows.map((row) => ({
+ nodeId: valueAt(result, row, "NODEID"),
+ bufferTotalBytes: valueAt(result, row, "BUFFER_TOTAL_BYTES"),
+ bufferFreeBytes: valueAt(result, row, "BUFFER_FREE_BYTES"),
+ bufferDirtyBytes: valueAt(result, row, "BUFFER_DIRTY_BYTES"),
+ bufferLruBytes: valueAt(result, row, "BUFFER_LRU_BYTES"),
+ sgaTotalBytes: valueAt(result, row, "SGA_TOTAL_BYTES"),
+ sgaFreeBytes: valueAt(result, row, "SGA_FREE_BYTES"),
+ sgaPeakBytes: valueAt(result, row, "SGA_PEAK_BYTES"),
+ swapTotalBytes: valueAt(result, row, "SWAP_TOTAL_BYTES"),
+ swapFreeBytes: valueAt(result, row, "SWAP_FREE_BYTES"),
+ }));
+}
+
+export function xuguSessionStatusSummaryFromResult(result: QueryResult): XuguSessionStatusSummary[] {
+ return result.rows.map((row) => ({
+ nodeId: valueAt(result, row, "NODE_ID"),
+ status: valueAt(result, row, "STATUS"),
+ sessions: valueAt(result, row, "SESSIONS"),
+ }));
+}
+
+export function xuguLockModeSummaryFromResult(result: QueryResult): XuguLockModeSummary[] {
+ return result.rows.map((row) => ({
+ nodeId: valueAt(result, row, "NODE_ID"),
+ lockMode: valueAt(result, row, "LOCK_MODE"),
+ locks: valueAt(result, row, "LOCKS"),
+ }));
+}
+
+export function xuguTablespaceSummaryFromResult(result: QueryResult): XuguTablespaceSummary[] {
+ return result.rows.map((row) => ({
+ nodeId: valueAt(result, row, "NODE_ID"),
+ spaceName: valueAt(result, row, "SPACE_NAME"),
+ spaceType: valueAt(result, row, "SPACE_TYPE"),
+ datafiles: valueAt(result, row, "DATAFILES"),
+ mediaError: valueAt(result, row, "MEDIA_ERROR"),
+ totalBytes: valueAt(result, row, "TOTAL_BYTES"),
+ freeBytes: valueAt(result, row, "FREE_BYTES"),
+ }));
+}
+
+export function xuguScalarFromResult(result: QueryResult, column: string): string {
+ return result.rows.length > 0 ? valueAt(result, result.rows[0], column) : "";
+}
+
+export function xuguVersionFromResult(result: QueryResult): string {
+ return xuguScalarFromResult(result, "VERSION");
+}
+
+function valueAt(result: QueryResult, row: unknown[], column: string): string {
+ const index = result.columns.findIndex((candidate) => candidate.toUpperCase() === column.toUpperCase());
+ return index < 0 || row[index] == null ? "" : String(row[index]);
+}
diff --git a/apps/desktop/src/stores/queryStore.ts b/apps/desktop/src/stores/queryStore.ts
index 8c730dade5..ddd473a5d1 100644
--- a/apps/desktop/src/stores/queryStore.ts
+++ b/apps/desktop/src/stores/queryStore.ts
@@ -2786,6 +2786,29 @@ export const useQueryStore = defineStore("query", () => {
return registerOpenTab(tab);
}
+ function openXuguDashboard(connectionId: string) {
+ const existing = tabs.value.find((tab) => tab.mode === "xugu-dashboard" && tab.connectionId === connectionId);
+ if (existing) {
+ switchTab(existing.id);
+ return existing.id;
+ }
+
+ const conn = useConnectionStore().getConfig(connectionId);
+ const id = uuid();
+ const tab: QueryTab = {
+ id,
+ title: conn?.name ? `${conn.name} - ${t("xuguServerDashboard.title")}` : t("xuguServerDashboard.title"),
+ connectionId,
+ database: conn?.database || "",
+ sql: "",
+ isExecuting: false,
+ isCancelling: false,
+ isExplaining: false,
+ mode: "xugu-dashboard",
+ };
+ return registerOpenTab(tab);
+ }
+
function openNacosDashboard(connectionId: string) {
const existing = tabs.value.find((tab) => tab.mode === "nacos-dashboard" && tab.connectionId === connectionId);
if (existing) {
@@ -7731,6 +7754,7 @@ export const useQueryStore = defineStore("query", () => {
openSqlServerActivityTrace,
openMysqlDashboard,
openPostgresDashboard,
+ openXuguDashboard,
openNacosDashboard,
openDamengUsers,
openDamengRoles,
diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts
index aad2138371..92bd048d56 100644
--- a/apps/desktop/src/types/database.ts
+++ b/apps/desktop/src/types/database.ts
@@ -1263,6 +1263,7 @@ export interface QueryTab {
| "sqlserver-trace"
| "mysql-dashboard"
| "postgres-dashboard"
+ | "xugu-dashboard"
| "dolt-version-control";
/** Ephemeral navigation intent; it is consumed by HBaseBrowser and is not persisted. */
hbaseCreateTableOnOpen?: boolean;