Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/plugins/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ my-terminal-plugin/

| type | 说明 | 典型场景 |
| -------- | -------------------------------------------- | ----------------------- |
| `args` | 模板替换后作为命令行参数 | PuTTY、DBeaver |
| `args` | 模板替换后作为命令行参数 | PuTTY、DBX、DBeaver |
| `script` | 执行 `scripts/` 下平台脚本,传入 JSON 上下文 | iTerm2、复杂 GUI 自动化 |
| `url` | 构建 URL Scheme 并 `open` | Navicat |
| `file` | 先写临时文件再打开 | RDP `.rdp` |
Expand All @@ -168,6 +168,7 @@ my-terminal-plugin/
| `{port}` | 端口 |
| `{file}` | 临时文件路径(`file` 类型) |
| `{dbname}` | 数据库名 |
| `{dbx_url}` | URL 编码的 DBX 一次性连接链接 |
| `{use_ssl}` | 是否 SSL |
| `{allow_invalid_cert}` | 是否允许无效证书 |

Expand All @@ -180,7 +181,7 @@ my-terminal-plugin/
"version": 1,
"selections": {
"terminal:ssh": "builtin.putty",
"databases:mysql": "builtin.dbeaver"
"databases:mysql": "windows.dbx"
},
"plugins": {
"com.example.xshell": {
Expand Down Expand Up @@ -320,6 +321,6 @@ sequenceDiagram
| macOS | remotedesktop | 系统 RDP(open) |
| Linux | terminal | 系统 terminal |
| Linux | remotedesktop | xfreerdp, tigervnc |
| 全平台 | databases | dbeaver(需用户配路径) |
| 全平台 | databases | dbx(默认)、dbeaver(可选,需配置本机路径) |

其余(XShell、Navicat、MobaXterm 等)以**可选插件**形式提供下载或用户自行打包安装。
21 changes: 21 additions & 0 deletions electron/src/apps/local-app-launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@ function navicatUrl(payload) {
return `navicat://conn.${protocol}?Conn.Host=${payload.endpoint.host}&Conn.Name=${sanitizedName(payload.name)}&Conn.Port=${payload.endpoint.port}&Conn.Username=${username(payload)}`;
}

function dbxUrl(payload) {
const aliases = { postgresql: "postgres", sqlserver: "mssql", dameng: "dm", mariadb: "mysql" };
const query = new URLSearchParams({
name: sanitizedName(payload.name),
type: aliases[payload.protocol] || payload.protocol,
host: String(payload.endpoint?.host || ""),
port: String(payload.endpoint?.port || ""),
user: username(payload),
password: String(payload.token?.value || ""),
// Magnus routes Oracle connections by the token supplied as the service name.
database: payload.protocol === "oracle" ? username(payload) : String(payload.asset?.info?.db_name || ""),
one_time: "true"
});
if (payload.protocol === "mongodb") {
query.set("url_params", "authSource=admin&loadBalanced=true&retryWrites=false");
}
// The legacy argument splitter treats '*' as an argument separator, even inside quotes.
return `dbx://connection/new?${query.toString().replaceAll("*", "%2A")}`;
}

function valuesFor(payload): Record<string, string> {
return {
name: sanitizedName(payload.name),
Expand All @@ -36,6 +56,7 @@ function valuesFor(payload): Record<string, string> {
port: String(payload.endpoint?.port || ""),
dbname: payload.protocol === "oracle" ? username(payload) : String(payload.asset?.info?.db_name || ""),
dbeaver_protocol: payload.protocol === "sqlserver" ? "mssql_jdbc_ms_new" : payload.protocol,
dbx_url: dbxUrl(payload),
url: navicatUrl(payload)
};
}
Expand Down
128 changes: 128 additions & 0 deletions electron/tests/application-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import assert from "node:assert/strict";
import { once } from "node:events";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { createServer } from "node:net";
import os from "node:os";
import path from "node:path";
import test from "node:test";
Expand All @@ -11,6 +13,132 @@ import { systemFontInternals } from "../src/apps/system-fonts.ts";

const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");

test(
"installed DBX initiates a database connection through the plugin launcher",
{
skip: !process.env.JMS_TEST_DBX_EXECUTABLE
},
async (context) => {
const appData = await mkdtemp(path.join(os.tmpdir(), "jms-dbx-native-"));
context.after(() => rm(appData, { recursive: true, force: true }));
const server = createServer((socket) => socket.destroy());
context.after(() => server.close());
server.listen(0, "127.0.0.1");
await once(server, "listening");
const app = { getPath: () => appData, isPackaged: false };
const service = new ApplicationConfigService(app, projectRoot);
await service.initialize();
await service.updateSelection({
category: "databases",
protocol: "mysql",
name: "dbx",
pluginId: undefined,
path: process.env.JMS_TEST_DBX_EXECUTABLE
});
const received = once(server, "connection", { signal: AbortSignal.timeout(8000) });
const payload = {
protocol: "mysql",
client: "dbx",
name: `Luna DBX connection probe ${Date.now()}`,
endpoint: { host: "127.0.0.1", port: (server.address() as { port: number }).port },
token: { id: "luna-probe", value: "test-only" },
asset: { info: { db_name: "probe" } }
};
const launcher = new LocalApplicationLauncher(app, projectRoot, service, null);
await Promise.all([received, launcher.launch(`jms2://${Buffer.from(JSON.stringify(payload)).toString("base64")}`)]);
}
);

for (const platform of ["macos", "linux", "windows"]) {
test(`${platform} defaults to DBX and launches encoded, one-time database connections`, async (context) => {
const appData = await mkdtemp(path.join(os.tmpdir(), "jms-dbx-"));
context.after(() => rm(appData, { recursive: true, force: true }));
const app = { getPath: () => appData, isPackaged: false };
const service = new ApplicationConfigService(app, projectRoot);
service.builtInDir = async () => path.join(projectRoot, "plugins", platform);
await service.initialize();
const config = await service.getConfig();
const dbx = config.databases.find((item) => item.name === "dbx");
assert.ok(dbx);
assert.ok(config.databases.some((item) => item.name === "dbeaver"));
assert.equal(dbx.launch_type, "args");
assert.equal(dbx.executable_type, "user_path");
const types = {
mysql: "mysql",
mariadb: "mysql",
postgresql: "postgres",
oracle: "oracle",
sqlserver: "mssql",
dameng: "dm",
clickhouse: "clickhouse",
mongodb: "mongodb",
redis: "redis"
};
assert.deepEqual(new Set(dbx.protocol), new Set(Object.keys(types)));
assert.deepEqual(dbx.match_first, dbx.protocol);
const launcher = new LocalApplicationLauncher(app, projectRoot, service, null);
for (const [protocol, type] of Object.entries(types)) {
const payload = {
protocol,
name: "测试 DBX &连接",
endpoint: { host: "2001:db8::1", port: 5525 },
token: { id: "token&+/*'\"\\中文", value: " secret &one_time=false+?#%/*'\"\\中文 ", protocol },
asset: { info: { db_name: protocol === "redis" ? "0" : "库 名&+/*'\"\\" } }
};
let launches = 0;
launcher.launchExecutable = async (application, argumentString) => {
assert.equal(application.name, "dbx");
const args = localAppLauncherInternals.splitArguments(argumentString);
assert.equal(args.length, 1);
const url = new URL(args[0]);
assert.equal(url.protocol, "dbx:");
assert.equal(url.hostname, "connection");
assert.equal(url.pathname, "/new");
assert.deepEqual(Object.fromEntries(url.searchParams), {
name: "测试DBX&连接",
type,
host: payload.endpoint.host,
port: "5525",
user: payload.token.id,
password: payload.token.value,
database: protocol === "oracle" ? payload.token.id : payload.asset.info.db_name,
one_time: "true",
...(protocol === "mongodb" ? { url_params: "authSource=admin&loadBalanced=true&retryWrites=false" } : {})
});
launches++;
};
for (const client of [undefined, "dbx"]) {
await launcher.launch(`jms2://${Buffer.from(JSON.stringify({ ...payload, client })).toString("base64")}`);
}
if (protocol === "mariadb") {
await launcher.launch(
`jms2://${Buffer.from(JSON.stringify({ ...payload, protocol: "mysql" })).toString("base64")}`
);
}
assert.equal(launches, protocol === "mariadb" ? 3 : 2);
}

// Upgrades retain saved DBeaver paths, other defaults, and explicitly disabled protocols.
const preferences = {
version: 1,
selections: {
"databases:mysql": `${platform}.dbeaver`,
"databases:postgresql": `${platform}.${platform === "windows" ? "navicat17" : "terminal-db"}`,
"databases:redis": ""
},
enabled_selections: { "databases:mysql": [`${platform}.dbeaver`] },
plugins: { [`${platform}.dbeaver`]: { path: process.execPath, enabled: true } }
};
await service.saveState(preferences);
const state = await service.loadState();
for (const [key, value] of Object.entries(preferences.selections)) assert.equal(state.selections[key], value);
assert.deepEqual(state.plugins, preferences.plugins);
assert.deepEqual(state.enabled_selections, preferences.enabled_selections);
assert.equal((await launcher.resolveApplication({ protocol: "mysql" })).name, "dbeaver");
await assert.rejects(launcher.resolveApplication({ protocol: "redis" }), /no configured application/);
});
}

for (const [platform, protocol, expectedClient] of [
["macos", "ssh", "terminal"],
["linux", "ssh", "terminal"],
Expand Down
21 changes: 21 additions & 0 deletions plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,27 @@ macos.tigervnc/
应用发现、选择和启动均由 Electron/Node 直接读取这里的插件配置。SSH helper 使用
Electron 自带的 Node 运行时,不再维护应用启动配置的副本。

## 数据库客户端

新安装默认选择 DBX,DBeaver Community 继续作为可选插件提供。已有用户保存的客户端选择、
禁用状态和可执行文件路径保持不变。使用 DBX 前请先安装桌面端,并在设置中确认其可执行文件路径;
便携版或自定义安装目录需要手动选择路径,Oracle、达梦需在 DBX 中安装对应驱动。

DBX 使用 `dbx://connection/new` 启动,连接参数采用 URL 编码,并设置 `one_time=true`
自动连接、断开后删除连接信息。用户名和密码使用 JumpServer 连接 Token,端点使用 Magnus 代理地址。
Oracle 的 service name 沿用 Token ID,以便 Magnus 路由;协议别名与服务端 DBX applet 保持一致。
MongoDB 沿用现有客户端的 `authSource=admin&loadBalanced=true&retryWrites=false` 代理参数。
浏览器页面仍通过现有的 JumpServer 客户端唤起流程连接,由 Electron 启动本机 DBX。

可对已安装的 DBX 运行原生连接检查(macOS 示例):

```sh
JMS_TEST_DBX_EXECUTABLE=/Applications/DBX.app/Contents/MacOS/dbx pnpm --dir electron exec node --import tsx --test --test-name-pattern='installed DBX' tests/application-config.test.ts
```

该检查通过完整插件启动链路发送一次性测试连接,使用临时配置和本地 TCP 监听器确认 DBX
确实发起连接,不需要 Magnus。它不验证代理鉴权或真实数据库查询。

## 文档

- [架构设计](../docs/plugins/DESIGN.md)
Expand Down
5 changes: 5 additions & 0 deletions plugins/linux/index.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"version": 1,
"plugins": [
{
"id": "linux.dbx",
"name": "dbx",
"category": "databases"
},
{
"id": "linux.dbeaver",
"name": "dbeaver",
Expand Down
17 changes: 17 additions & 0 deletions plugins/linux/linux.dbx/connect.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"platform": "linux",
"match_first": [],
"is_default": true,
"is_set": false,
"is_internal": false,
"executable": {
"type": "user_path",
"default": "/usr/bin/dbx",
"required": true
},
"launch": {
"type": "args",
"template": "\"{dbx_url}\""
},
"display_name": "DBX"
}
Binary file added plugins/linux/linux.dbx/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions plugins/linux/linux.dbx/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "linux.dbx",
"name": "dbx",
"display_name": "DBX",
"version": "1.0.0",
"min_client_version": "5.0.0",
"author": "JumpServer",
"homepage": "https://dbxio.com",
"download_url": "https://dbxio.com",
"category": "databases",
"protocols": ["mysql", "mariadb", "postgresql", "oracle", "sqlserver", "dameng", "clickhouse", "mongodb", "redis"],
"builtin": true,
"comment": {
"zh": "轻量级跨平台数据库客户端,集成连接管理、SQL 编辑、数据表格和结构工具。请先安装 DBX,确认可执行文件路径后启用;Oracle、达梦需要在 DBX 中安装对应驱动。",
"en": "Lightweight cross-platform database client with connection management, SQL editing, data grids and schema tools. Install DBX and verify the executable path before enabling. Oracle and Dameng require their drivers in DBX."
}
}
15 changes: 9 additions & 6 deletions plugins/linux/plugins-state.defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
"terminal:ssh": "linux.terminal",
"terminal:telnet": "linux.terminal",
"remotedesktop:vnc": "linux.tigervnc",
"databases:oracle": "linux.terminal-db",
"databases:mysql": "linux.terminal-db",
"databases:postgresql": "linux.terminal-db",
"databases:mariadb": "linux.terminal-db",
"databases:redis": "linux.terminal-db",
"databases:mongodb": "linux.terminal-db"
"databases:oracle": "linux.dbx",
"databases:mysql": "linux.dbx",
"databases:postgresql": "linux.dbx",
"databases:mariadb": "linux.dbx",
"databases:redis": "linux.dbx",
"databases:mongodb": "linux.dbx",
"databases:sqlserver": "linux.dbx",
"databases:dameng": "linux.dbx",
"databases:clickhouse": "linux.dbx"
},
"plugins": {}
}
5 changes: 5 additions & 0 deletions plugins/macos/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
"name": "another_redis",
"category": "databases"
},
{
"id": "macos.dbx",
"name": "dbx",
"category": "databases"
},
{
"id": "macos.dbeaver",
"name": "dbeaver",
Expand Down
17 changes: 17 additions & 0 deletions plugins/macos/macos.dbx/connect.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"platform": "macos",
"match_first": [],
"is_default": true,
"is_set": false,
"is_internal": false,
"executable": {
"type": "user_path",
"default": "/Applications/DBX.app/Contents/MacOS/dbx",
"required": true
},
"launch": {
"type": "args",
"template": "\"{dbx_url}\""
},
"display_name": "DBX"
}
Binary file added plugins/macos/macos.dbx/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions plugins/macos/macos.dbx/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "macos.dbx",
"name": "dbx",
"display_name": "DBX",
"version": "1.0.0",
"min_client_version": "5.0.0",
"author": "JumpServer",
"homepage": "https://dbxio.com",
"download_url": "https://dbxio.com",
"category": "databases",
"protocols": ["mysql", "mariadb", "postgresql", "oracle", "sqlserver", "dameng", "clickhouse", "mongodb", "redis"],
"builtin": true,
"comment": {
"zh": "轻量级跨平台数据库客户端,集成连接管理、SQL 编辑、数据表格和结构工具。请先安装 DBX,确认可执行文件路径后启用;Oracle、达梦需要在 DBX 中安装对应驱动。",
"en": "Lightweight cross-platform database client with connection management, SQL editing, data grids and schema tools. Install DBX and verify the executable path before enabling. Oracle and Dameng require their drivers in DBX."
}
}
16 changes: 9 additions & 7 deletions plugins/macos/plugins-state.defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
"remotedesktop:rdp": "macos.mstsc",
"remotedesktop:vnc": "macos.tigervnc",
"filetransfer:sftp": "macos.iterm-sftp",
"databases:oracle": "macos.terminal-db",
"databases:mysql": "macos.terminal-db",
"databases:postgresql": "macos.terminal-db",
"databases:mariadb": "macos.terminal-db",
"databases:redis": "macos.terminal-db",
"databases:sqlserver": "macos.terminal-db",
"databases:mongodb": "macos.terminal-db"
"databases:oracle": "macos.dbx",
"databases:mysql": "macos.dbx",
"databases:postgresql": "macos.dbx",
"databases:mariadb": "macos.dbx",
"databases:redis": "macos.dbx",
"databases:sqlserver": "macos.dbx",
"databases:mongodb": "macos.dbx",
"databases:dameng": "macos.dbx",
"databases:clickhouse": "macos.dbx"
},
"plugins": {}
}
2 changes: 1 addition & 1 deletion plugins/schema/manifest.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"id": {
"type": "string",
"pattern": "^[a-z][a-z0-9]*(\\.[a-z][a-z0-9]*)+$",
"description": "Globally unique plugin id, e.g. builtin.dbeaver (built-in) or com.example.xshell (third-party)"
"description": "Globally unique plugin id, e.g. windows.dbx (built-in) or com.example.xshell (third-party)"
},
"name": {
"type": "string",
Expand Down
5 changes: 5 additions & 0 deletions plugins/windows/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
"name": "another_redis",
"category": "databases"
},
{
"id": "windows.dbx",
"name": "dbx",
"category": "databases"
},
{
"id": "windows.dbeaver",
"name": "dbeaver",
Expand Down
Loading
Loading