533 lines
19 KiB
JavaScript
533 lines
19 KiB
JavaScript
"use strict";
|
|
const electron = require("electron");
|
|
const path = require("path");
|
|
let mainWindow = null;
|
|
let dbConnections = /* @__PURE__ */ new Map();
|
|
const isDev = !electron.app.isPackaged;
|
|
const gotTheLock = electron.app.requestSingleInstanceLock();
|
|
if (!gotTheLock) {
|
|
electron.app.quit();
|
|
} else {
|
|
electron.app.on("second-instance", () => {
|
|
if (mainWindow) {
|
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
mainWindow.focus();
|
|
}
|
|
});
|
|
}
|
|
function createWindow() {
|
|
mainWindow = new electron.BrowserWindow({
|
|
width: 1400,
|
|
height: 900,
|
|
minWidth: 1100,
|
|
minHeight: 700,
|
|
frame: false,
|
|
backgroundColor: "#1f1f1f",
|
|
webPreferences: {
|
|
preload: path.join(__dirname, "../preload/preload.js"),
|
|
contextIsolation: true,
|
|
nodeIntegration: false
|
|
}
|
|
});
|
|
if (isDev) {
|
|
mainWindow.loadURL("http://localhost:5173");
|
|
mainWindow.webContents.openDevTools();
|
|
} else {
|
|
mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
|
|
}
|
|
}
|
|
electron.app.whenReady().then(createWindow);
|
|
electron.app.on("window-all-closed", () => {
|
|
if (process.platform !== "darwin") electron.app.quit();
|
|
});
|
|
electron.app.on("activate", () => {
|
|
if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
});
|
|
electron.ipcMain.handle("window:minimize", () => mainWindow == null ? void 0 : mainWindow.minimize());
|
|
electron.ipcMain.handle("window:maximize", () => {
|
|
(mainWindow == null ? void 0 : mainWindow.isMaximized()) ? mainWindow.unmaximize() : mainWindow == null ? void 0 : mainWindow.maximize();
|
|
});
|
|
electron.ipcMain.handle("window:close", () => mainWindow == null ? void 0 : mainWindow.close());
|
|
function resolveHost(host) {
|
|
return host === "localhost" ? "127.0.0.1" : host;
|
|
}
|
|
electron.ipcMain.handle("db:test", async (_, config) => {
|
|
const host = resolveHost(config.host);
|
|
try {
|
|
if (config.type === "mysql" || config.type === "mariadb") {
|
|
const mysql = require("mysql2/promise");
|
|
const conn = await mysql.createConnection({
|
|
host,
|
|
port: config.port,
|
|
user: config.username,
|
|
password: config.password,
|
|
database: config.database || void 0,
|
|
connectTimeout: 1e4
|
|
});
|
|
await conn.ping();
|
|
await conn.end();
|
|
return { success: true, message: "连接成功" };
|
|
} else if (config.type === "postgres") {
|
|
const { Client } = require("pg");
|
|
const client = new Client({
|
|
host,
|
|
port: config.port,
|
|
user: config.username,
|
|
password: config.password,
|
|
database: config.database || "postgres",
|
|
connectionTimeoutMillis: 1e4
|
|
});
|
|
await client.connect();
|
|
await client.end();
|
|
return { success: true, message: "连接成功" };
|
|
} else if (config.type === "mongodb") {
|
|
const { MongoClient } = require("mongodb");
|
|
const uri = config.username ? `mongodb://${config.username}:${config.password}@${host}:${config.port}/${config.database || "admin"}` : `mongodb://${host}:${config.port}/${config.database || "admin"}`;
|
|
const client = new MongoClient(uri, { serverSelectionTimeoutMS: 1e4 });
|
|
await client.connect();
|
|
await client.close();
|
|
return { success: true, message: "连接成功" };
|
|
} else if (config.type === "redis") {
|
|
const Redis = require("ioredis");
|
|
const client = new Redis({
|
|
host,
|
|
port: config.port,
|
|
password: config.password || void 0,
|
|
connectTimeout: 1e4,
|
|
lazyConnect: true
|
|
});
|
|
await client.connect();
|
|
await client.ping();
|
|
await client.quit();
|
|
return { success: true, message: "连接成功" };
|
|
} else if (config.type === "sqlserver") {
|
|
const sql = require("mssql");
|
|
const poolConfig = {
|
|
server: host,
|
|
port: config.port,
|
|
user: config.username,
|
|
password: config.password,
|
|
database: config.database || "master",
|
|
options: { encrypt: false, trustServerCertificate: true },
|
|
connectionTimeout: 1e4
|
|
};
|
|
const pool = await sql.connect(poolConfig);
|
|
await pool.close();
|
|
return { success: true, message: "连接成功" };
|
|
}
|
|
return { success: false, message: `暂不支持 ${config.type}` };
|
|
} catch (err) {
|
|
return { success: false, message: err.message };
|
|
}
|
|
});
|
|
electron.ipcMain.handle("db:connect", async (_, config) => {
|
|
const host = resolveHost(config.host);
|
|
try {
|
|
if (config.type === "mysql" || config.type === "mariadb") {
|
|
const mysql = require("mysql2/promise");
|
|
const conn = await mysql.createConnection({
|
|
host,
|
|
port: config.port,
|
|
user: config.username,
|
|
password: config.password,
|
|
database: config.database || void 0
|
|
});
|
|
dbConnections.set(config.id, { type: "mysql", conn });
|
|
return { success: true, message: "连接成功" };
|
|
} else if (config.type === "postgres") {
|
|
const { Client } = require("pg");
|
|
const client = new Client({
|
|
host,
|
|
port: config.port,
|
|
user: config.username,
|
|
password: config.password,
|
|
database: config.database || "postgres"
|
|
});
|
|
await client.connect();
|
|
dbConnections.set(config.id, { type: "postgres", conn: client });
|
|
return { success: true, message: "连接成功" };
|
|
} else if (config.type === "mongodb") {
|
|
const { MongoClient } = require("mongodb");
|
|
const uri = config.username ? `mongodb://${config.username}:${config.password}@${host}:${config.port}/${config.database || "admin"}` : `mongodb://${host}:${config.port}/${config.database || "admin"}`;
|
|
const client = new MongoClient(uri);
|
|
await client.connect();
|
|
dbConnections.set(config.id, { type: "mongodb", conn: client });
|
|
return { success: true, message: "连接成功" };
|
|
} else if (config.type === "redis") {
|
|
const Redis = require("ioredis");
|
|
const client = new Redis({
|
|
host,
|
|
port: config.port,
|
|
password: config.password || void 0,
|
|
lazyConnect: true
|
|
});
|
|
await client.connect();
|
|
dbConnections.set(config.id, { type: "redis", conn: client });
|
|
return { success: true, message: "连接成功" };
|
|
} else if (config.type === "sqlserver") {
|
|
const sql = require("mssql");
|
|
const poolConfig = {
|
|
server: host,
|
|
port: config.port,
|
|
user: config.username,
|
|
password: config.password,
|
|
database: config.database || "master",
|
|
options: { encrypt: false, trustServerCertificate: true }
|
|
};
|
|
const pool = await sql.connect(poolConfig);
|
|
dbConnections.set(config.id, { type: "sqlserver", conn: pool });
|
|
return { success: true, message: "连接成功" };
|
|
}
|
|
return { success: false, message: `暂不支持 ${config.type}` };
|
|
} catch (err) {
|
|
return { success: false, message: err.message };
|
|
}
|
|
});
|
|
electron.ipcMain.handle("db:disconnect", async (_, id) => {
|
|
const db = dbConnections.get(id);
|
|
if (db) {
|
|
try {
|
|
await db.conn.end();
|
|
} catch {
|
|
}
|
|
dbConnections.delete(id);
|
|
}
|
|
});
|
|
electron.ipcMain.handle("db:query", async (_, id, sql) => {
|
|
var _a;
|
|
const db = dbConnections.get(id);
|
|
if (!db) return { columns: [], rows: [], error: "未连接" };
|
|
try {
|
|
if (db.type === "mysql") {
|
|
const [rows, fields] = await db.conn.query(sql);
|
|
const columns = (fields == null ? void 0 : fields.map((f) => f.name)) || [];
|
|
return { columns, rows: Array.isArray(rows) ? rows : [] };
|
|
} else if (db.type === "postgres") {
|
|
const result = await db.conn.query(sql);
|
|
const columns = ((_a = result.fields) == null ? void 0 : _a.map((f) => f.name)) || [];
|
|
return { columns, rows: result.rows };
|
|
}
|
|
return { columns: [], rows: [], error: "不支持的类型" };
|
|
} catch (err) {
|
|
return { columns: [], rows: [], error: err.message };
|
|
}
|
|
});
|
|
electron.ipcMain.handle("db:getDatabases", async (_, id) => {
|
|
const db = dbConnections.get(id);
|
|
if (!db) return [];
|
|
try {
|
|
if (db.type === "mysql") {
|
|
const [rows] = await db.conn.query("SHOW DATABASES");
|
|
return rows.map((r) => r.Database);
|
|
} else if (db.type === "postgres") {
|
|
const result = await db.conn.query("SELECT datname FROM pg_database WHERE datistemplate = false");
|
|
return result.rows.map((r) => r.datname);
|
|
}
|
|
} catch {
|
|
}
|
|
return [];
|
|
});
|
|
electron.ipcMain.handle("db:getTables", async (_, id, database) => {
|
|
const db = dbConnections.get(id);
|
|
if (!db) return [];
|
|
try {
|
|
if (db.type === "mysql") {
|
|
await db.conn.query(`USE \`${database}\``);
|
|
const [rows] = await db.conn.query(`
|
|
SELECT TABLE_NAME as name, TABLE_ROWS as \`rows\`
|
|
FROM information_schema.TABLES
|
|
WHERE TABLE_SCHEMA = ?
|
|
`, [database]);
|
|
return rows.map((r) => ({ name: r.name, rows: r.rows || 0 }));
|
|
} else if (db.type === "postgres") {
|
|
const result = await db.conn.query(`
|
|
SELECT tablename as name,
|
|
(SELECT reltuples::bigint FROM pg_class WHERE relname = tablename) as rows
|
|
FROM pg_tables WHERE schemaname = 'public'
|
|
`);
|
|
return result.rows.map((r) => ({ name: r.name, rows: parseInt(r.rows) || 0 }));
|
|
}
|
|
} catch (err) {
|
|
console.error("getTables error:", err);
|
|
}
|
|
return [];
|
|
});
|
|
electron.ipcMain.handle("db:getColumns", async (_, id, database, table) => {
|
|
const db = dbConnections.get(id);
|
|
if (!db) return [];
|
|
try {
|
|
if (db.type === "mysql") {
|
|
const [rows] = await db.conn.query(`
|
|
SELECT COLUMN_NAME as name, DATA_TYPE as type, IS_NULLABLE as nullable,
|
|
COLUMN_KEY as \`key\`, COLUMN_COMMENT as comment
|
|
FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
ORDER BY ORDINAL_POSITION
|
|
`, [database, table]);
|
|
return rows.map((r) => ({
|
|
name: r.name,
|
|
type: r.type,
|
|
nullable: r.nullable === "YES",
|
|
key: r.key || void 0,
|
|
comment: r.comment || void 0
|
|
}));
|
|
} else if (db.type === "postgres") {
|
|
const result = await db.conn.query(`
|
|
SELECT c.column_name as name, c.data_type as type, c.is_nullable as nullable,
|
|
pgd.description as comment
|
|
FROM information_schema.columns c
|
|
LEFT JOIN pg_catalog.pg_statio_all_tables st ON c.table_schema = st.schemaname AND c.table_name = st.relname
|
|
LEFT JOIN pg_catalog.pg_description pgd ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
|
|
WHERE c.table_schema = 'public' AND c.table_name = $1
|
|
ORDER BY c.ordinal_position
|
|
`, [table]);
|
|
return result.rows.map((r) => ({
|
|
name: r.name,
|
|
type: r.type,
|
|
nullable: r.nullable === "YES",
|
|
comment: r.comment || void 0
|
|
}));
|
|
}
|
|
} catch (err) {
|
|
console.error("getColumns error:", err);
|
|
}
|
|
return [];
|
|
});
|
|
electron.ipcMain.handle("db:getTableData", async (_, id, database, table, page = 1, pageSize = 100) => {
|
|
var _a, _b;
|
|
const db = dbConnections.get(id);
|
|
if (!db) return { data: [], total: 0 };
|
|
try {
|
|
const offset = (page - 1) * pageSize;
|
|
if (db.type === "mysql") {
|
|
const [countResult] = await db.conn.query(`SELECT COUNT(*) as total FROM \`${database}\`.\`${table}\``);
|
|
const total = ((_a = countResult[0]) == null ? void 0 : _a.total) || 0;
|
|
const [rows] = await db.conn.query(`SELECT * FROM \`${database}\`.\`${table}\` LIMIT ? OFFSET ?`, [pageSize, offset]);
|
|
return { data: rows, total };
|
|
} else if (db.type === "postgres") {
|
|
const countResult = await db.conn.query(`SELECT COUNT(*) as total FROM "${table}"`);
|
|
const total = parseInt((_b = countResult.rows[0]) == null ? void 0 : _b.total) || 0;
|
|
const result = await db.conn.query(`SELECT * FROM "${table}" LIMIT $1 OFFSET $2`, [pageSize, offset]);
|
|
return { data: result.rows, total };
|
|
}
|
|
} catch (err) {
|
|
console.error("getTableData error:", err);
|
|
}
|
|
return { data: [], total: 0 };
|
|
});
|
|
const fs = require("fs");
|
|
const configPath = path.join(electron.app.getPath("userData"), "connections.json");
|
|
electron.ipcMain.handle("config:save", async (_, connections) => {
|
|
fs.writeFileSync(configPath, JSON.stringify(connections, null, 2));
|
|
});
|
|
electron.ipcMain.handle("config:load", async () => {
|
|
try {
|
|
if (fs.existsSync(configPath)) {
|
|
return JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
}
|
|
} catch {
|
|
}
|
|
return [];
|
|
});
|
|
electron.ipcMain.handle("file:open", async () => {
|
|
const result = await electron.dialog.showOpenDialog(mainWindow, {
|
|
title: "打开 SQL 文件",
|
|
filters: [
|
|
{ name: "SQL 文件", extensions: ["sql"] },
|
|
{ name: "所有文件", extensions: ["*"] }
|
|
],
|
|
properties: ["openFile"]
|
|
});
|
|
if (result.canceled || result.filePaths.length === 0) {
|
|
return null;
|
|
}
|
|
const filePath = result.filePaths[0];
|
|
try {
|
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
return { path: filePath, content, name: path.basename(filePath) };
|
|
} catch (err) {
|
|
return { error: err.message };
|
|
}
|
|
});
|
|
electron.ipcMain.handle("file:save", async (_, filePath, content) => {
|
|
let targetPath = filePath;
|
|
if (!targetPath) {
|
|
const result = await electron.dialog.showSaveDialog(mainWindow, {
|
|
title: "保存 SQL 文件",
|
|
defaultPath: "query.sql",
|
|
filters: [
|
|
{ name: "SQL 文件", extensions: ["sql"] },
|
|
{ name: "所有文件", extensions: ["*"] }
|
|
]
|
|
});
|
|
if (result.canceled || !result.filePath) {
|
|
return null;
|
|
}
|
|
targetPath = result.filePath;
|
|
}
|
|
try {
|
|
fs.writeFileSync(targetPath, content, "utf-8");
|
|
return { path: targetPath, name: path.basename(targetPath) };
|
|
} catch (err) {
|
|
return { error: err.message };
|
|
}
|
|
});
|
|
electron.ipcMain.handle("db:backup", async (_, id, database) => {
|
|
const db = dbConnections.get(id);
|
|
if (!db) return { error: "未连接数据库" };
|
|
const result = await electron.dialog.showSaveDialog(mainWindow, {
|
|
title: "备份数据库",
|
|
defaultPath: `${database}_backup_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.sql`,
|
|
filters: [
|
|
{ name: "SQL 文件", extensions: ["sql"] },
|
|
{ name: "所有文件", extensions: ["*"] }
|
|
]
|
|
});
|
|
if (result.canceled || !result.filePath) {
|
|
return { cancelled: true };
|
|
}
|
|
try {
|
|
let sqlContent = "";
|
|
sqlContent += `-- Database Backup: ${database}
|
|
`;
|
|
sqlContent += `-- Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()}
|
|
`;
|
|
sqlContent += `-- Tool: EasySQL
|
|
|
|
`;
|
|
if (db.type === "mysql" || db.type === "mariadb") {
|
|
await db.conn.query(`USE \`${database}\``);
|
|
const [tables] = await db.conn.query(`SHOW TABLES`);
|
|
const tableKey = `Tables_in_${database}`;
|
|
sqlContent += `SET FOREIGN_KEY_CHECKS = 0;
|
|
|
|
`;
|
|
for (const tableRow of tables) {
|
|
const tableName = tableRow[tableKey];
|
|
const [createResult] = await db.conn.query(`SHOW CREATE TABLE \`${tableName}\``);
|
|
const createStatement = createResult[0]["Create Table"];
|
|
sqlContent += `-- Table: ${tableName}
|
|
`;
|
|
sqlContent += `DROP TABLE IF EXISTS \`${tableName}\`;
|
|
`;
|
|
sqlContent += `${createStatement};
|
|
|
|
`;
|
|
const [rows] = await db.conn.query(`SELECT * FROM \`${tableName}\``);
|
|
if (rows.length > 0) {
|
|
const columns = Object.keys(rows[0]);
|
|
for (const row of rows) {
|
|
const values = columns.map((col) => {
|
|
const val = row[col];
|
|
if (val === null) return "NULL";
|
|
if (typeof val === "number") return val;
|
|
if (val instanceof Date) return `'${val.toISOString().slice(0, 19).replace("T", " ")}'`;
|
|
return `'${String(val).replace(/'/g, "''").replace(/\\/g, "\\\\")}'`;
|
|
}).join(", ");
|
|
sqlContent += `INSERT INTO \`${tableName}\` (\`${columns.join("`, `")}\`) VALUES (${values});
|
|
`;
|
|
}
|
|
sqlContent += "\n";
|
|
}
|
|
}
|
|
sqlContent += `SET FOREIGN_KEY_CHECKS = 1;
|
|
`;
|
|
} else if (db.type === "postgres") {
|
|
const tablesResult = await db.conn.query(`
|
|
SELECT tablename FROM pg_tables WHERE schemaname = 'public'
|
|
`);
|
|
for (const tableRow of tablesResult.rows) {
|
|
const tableName = tableRow.tablename;
|
|
const dataResult = await db.conn.query(`SELECT * FROM "${tableName}"`);
|
|
if (dataResult.rows.length > 0) {
|
|
const columns = Object.keys(dataResult.rows[0]);
|
|
sqlContent += `-- Table: ${tableName}
|
|
`;
|
|
for (const row of dataResult.rows) {
|
|
const values = columns.map((col) => {
|
|
const val = row[col];
|
|
if (val === null) return "NULL";
|
|
if (typeof val === "number") return val;
|
|
if (val instanceof Date) return `'${val.toISOString().slice(0, 19).replace("T", " ")}'`;
|
|
return `'${String(val).replace(/'/g, "''")}'`;
|
|
}).join(", ");
|
|
sqlContent += `INSERT INTO "${tableName}" ("${columns.join('", "')}") VALUES (${values});
|
|
`;
|
|
}
|
|
sqlContent += "\n";
|
|
}
|
|
}
|
|
}
|
|
fs.writeFileSync(result.filePath, sqlContent, "utf-8");
|
|
return { success: true, path: result.filePath };
|
|
} catch (err) {
|
|
return { error: err.message };
|
|
}
|
|
});
|
|
electron.ipcMain.handle("db:exportTable", async (_, id, database, tableName, format) => {
|
|
const db = dbConnections.get(id);
|
|
if (!db) return { error: "未连接数据库" };
|
|
const ext = format === "excel" ? "xlsx" : format;
|
|
const result = await electron.dialog.showSaveDialog(mainWindow, {
|
|
title: `导出表 ${tableName}`,
|
|
defaultPath: `${tableName}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.${ext}`,
|
|
filters: [
|
|
{ name: format === "excel" ? "Excel 文件" : format === "sql" ? "SQL 文件" : "CSV 文件", extensions: [ext] },
|
|
{ name: "所有文件", extensions: ["*"] }
|
|
]
|
|
});
|
|
if (result.canceled || !result.filePath) {
|
|
return { cancelled: true };
|
|
}
|
|
try {
|
|
let rows = [];
|
|
let columns = [];
|
|
if (db.type === "mysql" || db.type === "mariadb") {
|
|
await db.conn.query(`USE \`${database}\``);
|
|
const [data] = await db.conn.query(`SELECT * FROM \`${tableName}\``);
|
|
rows = data;
|
|
if (rows.length > 0) columns = Object.keys(rows[0]);
|
|
} else if (db.type === "postgres") {
|
|
const data = await db.conn.query(`SELECT * FROM "${tableName}"`);
|
|
rows = data.rows;
|
|
if (rows.length > 0) columns = Object.keys(rows[0]);
|
|
}
|
|
if (format === "sql") {
|
|
let content = `-- Table: ${tableName}
|
|
`;
|
|
content += `-- Exported: ${(/* @__PURE__ */ new Date()).toLocaleString()}
|
|
|
|
`;
|
|
for (const row of rows) {
|
|
const values = columns.map((col) => {
|
|
const val = row[col];
|
|
if (val === null) return "NULL";
|
|
if (typeof val === "number") return val;
|
|
return `'${String(val).replace(/'/g, "''")}'`;
|
|
}).join(", ");
|
|
content += `INSERT INTO \`${tableName}\` (\`${columns.join("`, `")}\`) VALUES (${values});
|
|
`;
|
|
}
|
|
fs.writeFileSync(result.filePath, content, "utf-8");
|
|
} else if (format === "csv") {
|
|
let content = columns.join(",") + "\n";
|
|
for (const row of rows) {
|
|
const values = columns.map((col) => {
|
|
const val = row[col];
|
|
if (val === null) return "";
|
|
const str = String(val);
|
|
if (str.includes(",") || str.includes('"') || str.includes("\n")) {
|
|
return `"${str.replace(/"/g, '""')}"`;
|
|
}
|
|
return str;
|
|
});
|
|
content += values.join(",") + "\n";
|
|
}
|
|
fs.writeFileSync(result.filePath, content, "utf-8");
|
|
}
|
|
return { success: true, path: result.filePath };
|
|
} catch (err) {
|
|
return { error: err.message };
|
|
}
|
|
});
|