Add MySQL configuration management and smart sync functionality
- Implemented configuration storage for MySQL settings using electron-store. - Added automatic MySQL connection on app startup if a saved configuration exists. - Enhanced the shutdown process to sync data to the remote database before closing. - Introduced smart sync functionality to handle bidirectional synchronization based on the latest updated_at timestamps. - Updated IPC methods for saving and retrieving MySQL configurations. - Modified the Settings component to load and save MySQL configurations, and trigger host list refresh after connection.
This commit is contained in:
parent
95f842f6cb
commit
b7f6e9fcf6
52
main.js
52
main.js
@ -3,12 +3,21 @@
|
|||||||
*/
|
*/
|
||||||
const { app, BrowserWindow, ipcMain, Menu } = require('electron');
|
const { app, BrowserWindow, ipcMain, Menu } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const Store = require('electron-store');
|
||||||
const databaseService = require('./src/services/database');
|
const databaseService = require('./src/services/database');
|
||||||
const sshService = require('./src/services/ssh');
|
const sshService = require('./src/services/ssh');
|
||||||
|
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
const isDev = process.env.NODE_ENV !== 'production' || !app.isPackaged;
|
const isDev = process.env.NODE_ENV !== 'production' || !app.isPackaged;
|
||||||
|
|
||||||
|
// 配置存储
|
||||||
|
const configStore = new Store({
|
||||||
|
name: 'easyshell-config',
|
||||||
|
defaults: {
|
||||||
|
mysqlConfig: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// 活动的SSH连接
|
// 活动的SSH连接
|
||||||
const activeConnections = new Map();
|
const activeConnections = new Map();
|
||||||
|
|
||||||
@ -49,6 +58,21 @@ app.whenReady().then(async () => {
|
|||||||
// 初始化本地数据库 (异步)
|
// 初始化本地数据库 (异步)
|
||||||
await databaseService.initLocalDatabase();
|
await databaseService.initLocalDatabase();
|
||||||
|
|
||||||
|
// 尝试自动连接 MySQL(如果有保存的配置)
|
||||||
|
const savedConfig = configStore.get('mysqlConfig');
|
||||||
|
if (savedConfig && savedConfig.host) {
|
||||||
|
try {
|
||||||
|
const result = await databaseService.connectMySQL(savedConfig);
|
||||||
|
if (result.success) {
|
||||||
|
console.log('✅ 自动连接 MySQL 成功');
|
||||||
|
// 自动同步
|
||||||
|
await databaseService.syncFromRemote();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('⚠️ 自动连接 MySQL 失败:', err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createWindow();
|
createWindow();
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
@ -58,9 +82,21 @@ app.whenReady().then(async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', async () => {
|
||||||
// 关闭所有SSH连接
|
// 关闭所有SSH连接
|
||||||
sshService.disconnectAll();
|
sshService.disconnectAll();
|
||||||
|
|
||||||
|
// 关闭前自动同步到远程
|
||||||
|
if (databaseService.isRemoteConnected) {
|
||||||
|
try {
|
||||||
|
console.log('📤 正在同步数据到远程...');
|
||||||
|
await databaseService.syncToRemote();
|
||||||
|
console.log('✅ 数据同步完成');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ 关闭前同步失败:', err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 关闭数据库
|
// 关闭数据库
|
||||||
databaseService.close();
|
databaseService.close();
|
||||||
|
|
||||||
@ -93,6 +129,16 @@ ipcMain.handle('window:isMaximized', () => {
|
|||||||
|
|
||||||
// ========== 数据库 IPC ==========
|
// ========== 数据库 IPC ==========
|
||||||
|
|
||||||
|
// 配置管理
|
||||||
|
ipcMain.handle('db:saveConfig', (event, config) => {
|
||||||
|
configStore.set('mysqlConfig', config);
|
||||||
|
return { success: true };
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('db:getConfig', () => {
|
||||||
|
return configStore.get('mysqlConfig');
|
||||||
|
});
|
||||||
|
|
||||||
// MySQL连接
|
// MySQL连接
|
||||||
ipcMain.handle('db:connectMySQL', async (event, config) => {
|
ipcMain.handle('db:connectMySQL', async (event, config) => {
|
||||||
return await databaseService.connectMySQL(config);
|
return await databaseService.connectMySQL(config);
|
||||||
@ -115,6 +161,10 @@ ipcMain.handle('db:syncFromRemote', async () => {
|
|||||||
return await databaseService.syncFromRemote();
|
return await databaseService.syncFromRemote();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('db:smartSync', async () => {
|
||||||
|
return await databaseService.smartSync();
|
||||||
|
});
|
||||||
|
|
||||||
// 主机管理
|
// 主机管理
|
||||||
ipcMain.handle('hosts:getAll', () => {
|
ipcMain.handle('hosts:getAll', () => {
|
||||||
return databaseService.getAllHosts();
|
return databaseService.getAllHosts();
|
||||||
|
|||||||
@ -15,11 +15,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
|
|
||||||
// 数据库操作
|
// 数据库操作
|
||||||
db: {
|
db: {
|
||||||
|
saveConfig: (config) => ipcRenderer.invoke('db:saveConfig', config),
|
||||||
|
getConfig: () => ipcRenderer.invoke('db:getConfig'),
|
||||||
connectMySQL: (config) => ipcRenderer.invoke('db:connectMySQL', config),
|
connectMySQL: (config) => ipcRenderer.invoke('db:connectMySQL', config),
|
||||||
disconnectMySQL: () => ipcRenderer.invoke('db:disconnectMySQL'),
|
disconnectMySQL: () => ipcRenderer.invoke('db:disconnectMySQL'),
|
||||||
isRemoteConnected: () => ipcRenderer.invoke('db:isRemoteConnected'),
|
isRemoteConnected: () => ipcRenderer.invoke('db:isRemoteConnected'),
|
||||||
syncToRemote: () => ipcRenderer.invoke('db:syncToRemote'),
|
syncToRemote: () => ipcRenderer.invoke('db:syncToRemote'),
|
||||||
syncFromRemote: () => ipcRenderer.invoke('db:syncFromRemote'),
|
syncFromRemote: () => ipcRenderer.invoke('db:syncFromRemote'),
|
||||||
|
smartSync: () => ipcRenderer.invoke('db:smartSync'),
|
||||||
},
|
},
|
||||||
|
|
||||||
// 主机管理
|
// 主机管理
|
||||||
|
|||||||
@ -31,8 +31,12 @@ function App() {
|
|||||||
if (window.electronAPI) {
|
if (window.electronAPI) {
|
||||||
const connected = await window.electronAPI.db.isRemoteConnected();
|
const connected = await window.electronAPI.db.isRemoteConnected();
|
||||||
setIsRemoteConnected(connected);
|
setIsRemoteConnected(connected);
|
||||||
|
// 如果已连接,刷新主机列表(因为启动时可能已自动同步)
|
||||||
|
if (connected) {
|
||||||
|
loadHosts();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, [loadHosts]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadHosts();
|
loadHosts();
|
||||||
@ -254,6 +258,7 @@ function App() {
|
|||||||
setIsRemoteConnected(connected);
|
setIsRemoteConnected(connected);
|
||||||
if (connected) loadHosts();
|
if (connected) loadHosts();
|
||||||
}}
|
}}
|
||||||
|
onHostsUpdate={loadHosts}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
FiX,
|
FiX,
|
||||||
@ -13,7 +13,7 @@ import {
|
|||||||
FiAlertCircle,
|
FiAlertCircle,
|
||||||
} from 'react-icons/fi';
|
} from 'react-icons/fi';
|
||||||
|
|
||||||
function Settings({ onClose, isRemoteConnected, onConnectionChange }) {
|
function Settings({ onClose, isRemoteConnected, onConnectionChange, onHostsUpdate }) {
|
||||||
const [activeTab, setActiveTab] = useState('database');
|
const [activeTab, setActiveTab] = useState('database');
|
||||||
const [connecting, setConnecting] = useState(false);
|
const [connecting, setConnecting] = useState(false);
|
||||||
const [syncing, setSyncing] = useState(false);
|
const [syncing, setSyncing] = useState(false);
|
||||||
@ -26,6 +26,19 @@ function Settings({ onClose, isRemoteConnected, onConnectionChange }) {
|
|||||||
database: 'easyshell',
|
database: 'easyshell',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 加载保存的配置
|
||||||
|
useEffect(() => {
|
||||||
|
const loadConfig = async () => {
|
||||||
|
if (window.electronAPI) {
|
||||||
|
const savedConfig = await window.electronAPI.db.getConfig();
|
||||||
|
if (savedConfig) {
|
||||||
|
setMysqlConfig(savedConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleConnect = async () => {
|
const handleConnect = async () => {
|
||||||
if (!window.electronAPI) return;
|
if (!window.electronAPI) return;
|
||||||
|
|
||||||
@ -33,10 +46,20 @@ function Settings({ onClose, isRemoteConnected, onConnectionChange }) {
|
|||||||
setMessage(null);
|
setMessage(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 保存配置
|
||||||
|
await window.electronAPI.db.saveConfig(mysqlConfig);
|
||||||
|
|
||||||
const result = await window.electronAPI.db.connectMySQL(mysqlConfig);
|
const result = await window.electronAPI.db.connectMySQL(mysqlConfig);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
setMessage({ type: 'success', text: '数据库连接成功!已自动创建数据库和表结构' });
|
setMessage({ type: 'success', text: '数据库连接成功!正在同步数据...' });
|
||||||
onConnectionChange(true);
|
onConnectionChange(true);
|
||||||
|
|
||||||
|
// 连接成功后自动同步
|
||||||
|
const syncResult = await window.electronAPI.db.syncFromRemote();
|
||||||
|
if (syncResult.success) {
|
||||||
|
setMessage({ type: 'success', text: `连接成功!已同步 ${syncResult.hosts} 条主机信息` });
|
||||||
|
onHostsUpdate?.(); // 刷新主机列表
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setMessage({ type: 'error', text: `连接失败: ${result.error}` });
|
setMessage({ type: 'error', text: `连接失败: ${result.error}` });
|
||||||
onConnectionChange(false);
|
onConnectionChange(false);
|
||||||
@ -87,8 +110,9 @@ function Settings({ onClose, isRemoteConnected, onConnectionChange }) {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
setMessage({
|
setMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
text: `同步成功!已下载 ${result.hosts} 条主机信息和 ${result.commands} 条命令`
|
text: `同步成功!已同步 ${result.hosts} 条主机信息`
|
||||||
});
|
});
|
||||||
|
onHostsUpdate?.(); // 刷新主机列表
|
||||||
} else {
|
} else {
|
||||||
setMessage({ type: 'error', text: `同步失败: ${result.error}` });
|
setMessage({ type: 'error', text: `同步失败: ${result.error}` });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -223,7 +223,8 @@ class DatabaseService {
|
|||||||
description TEXT,
|
description TEXT,
|
||||||
last_connected_at DATETIME,
|
last_connected_at DATETIME,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY uk_host (host)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@ -274,7 +275,102 @@ class DatabaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步数据到远程
|
* 智能双向同步 - 以 host 为唯一标识,比较 updated_at 取最新记录
|
||||||
|
*/
|
||||||
|
async smartSync() {
|
||||||
|
if (!this.isRemoteConnected) {
|
||||||
|
return { success: false, error: '未连接到远程数据库' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let uploaded = 0;
|
||||||
|
let downloaded = 0;
|
||||||
|
|
||||||
|
// 获取所有本地主机
|
||||||
|
const localHosts = this.runQuery('SELECT * FROM hosts');
|
||||||
|
// 获取所有远程主机
|
||||||
|
const [remoteHosts] = await this.mysqlConnection.execute('SELECT * FROM hosts');
|
||||||
|
|
||||||
|
// 创建 host 地址到记录的映射
|
||||||
|
const localMap = new Map();
|
||||||
|
for (const h of localHosts) {
|
||||||
|
localMap.set(h.host, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteMap = new Map();
|
||||||
|
for (const h of remoteHosts) {
|
||||||
|
remoteMap.set(h.host, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 处理本地有的记录
|
||||||
|
for (const local of localHosts) {
|
||||||
|
const remote = remoteMap.get(local.host);
|
||||||
|
|
||||||
|
if (!remote) {
|
||||||
|
// 远程没有,上传到远程
|
||||||
|
await this.mysqlConnection.execute(`
|
||||||
|
INSERT INTO hosts (name, host, port, username, password, private_key, group_name, color, description, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`, [local.name, local.host, local.port, local.username, local.password,
|
||||||
|
local.private_key, local.group_name, local.color, local.description,
|
||||||
|
local.updated_at || new Date().toISOString()]);
|
||||||
|
uploaded++;
|
||||||
|
} else {
|
||||||
|
// 远程有,比较时间
|
||||||
|
const localTime = new Date(local.updated_at || 0).getTime();
|
||||||
|
const remoteTime = new Date(remote.updated_at || 0).getTime();
|
||||||
|
|
||||||
|
if (localTime > remoteTime) {
|
||||||
|
// 本地更新,上传到远程
|
||||||
|
await this.mysqlConnection.execute(`
|
||||||
|
UPDATE hosts SET name=?, port=?, username=?, password=?, private_key=?,
|
||||||
|
group_name=?, color=?, description=?, updated_at=?
|
||||||
|
WHERE host=?
|
||||||
|
`, [local.name, local.port, local.username, local.password, local.private_key,
|
||||||
|
local.group_name, local.color, local.description, local.updated_at, local.host]);
|
||||||
|
uploaded++;
|
||||||
|
} else if (remoteTime > localTime) {
|
||||||
|
// 远程更新,下载到本地
|
||||||
|
this.sqliteDb.run(`
|
||||||
|
UPDATE hosts SET name=?, port=?, username=?, password=?, private_key=?,
|
||||||
|
group_name=?, color=?, description=?, updated_at=?, is_synced=1
|
||||||
|
WHERE host=?
|
||||||
|
`, [remote.name, remote.port, remote.username, remote.password, remote.private_key,
|
||||||
|
remote.group_name, remote.color, remote.description,
|
||||||
|
remote.updated_at?.toISOString() || new Date().toISOString(), local.host]);
|
||||||
|
downloaded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记为已同步
|
||||||
|
this.sqliteDb.run('UPDATE hosts SET is_synced = 1 WHERE id = ?', [local.id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 处理远程有但本地没有的记录
|
||||||
|
for (const remote of remoteHosts) {
|
||||||
|
if (!localMap.has(remote.host)) {
|
||||||
|
// 本地没有,下载到本地
|
||||||
|
this.sqliteDb.run(`
|
||||||
|
INSERT INTO hosts (name, host, port, username, password, private_key, group_name, color, description, updated_at, is_synced)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
|
||||||
|
`, [remote.name, remote.host, remote.port, remote.username, remote.password,
|
||||||
|
remote.private_key, remote.group_name, remote.color, remote.description,
|
||||||
|
remote.updated_at?.toISOString() || new Date().toISOString()]);
|
||||||
|
downloaded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveDatabase();
|
||||||
|
console.log(`✅ 智能同步完成: 上传 ${uploaded}, 下载 ${downloaded}`);
|
||||||
|
return { success: true, uploaded, downloaded };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 智能同步失败:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步数据到远程 - 只上传本地更新的
|
||||||
*/
|
*/
|
||||||
async syncToRemote() {
|
async syncToRemote() {
|
||||||
if (!this.isRemoteConnected) {
|
if (!this.isRemoteConnected) {
|
||||||
@ -282,38 +378,48 @@ class DatabaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取本地未同步的主机
|
// 获取本地所有主机
|
||||||
const localHosts = this.runQuery('SELECT * FROM hosts WHERE is_synced = 0');
|
const localHosts = this.runQuery('SELECT * FROM hosts');
|
||||||
|
let synced = 0;
|
||||||
|
|
||||||
for (const host of localHosts) {
|
for (const host of localHosts) {
|
||||||
await this.mysqlConnection.execute(`
|
// 检查远程是否存在
|
||||||
INSERT INTO hosts (name, host, port, username, password, private_key, group_name, color, description)
|
const [existing] = await this.mysqlConnection.execute(
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
'SELECT host, updated_at FROM hosts WHERE host = ?', [host.host]
|
||||||
ON DUPLICATE KEY UPDATE
|
);
|
||||||
name = VALUES(name),
|
|
||||||
port = VALUES(port),
|
if (existing.length === 0) {
|
||||||
username = VALUES(username),
|
// 远程不存在,插入
|
||||||
password = VALUES(password),
|
await this.mysqlConnection.execute(`
|
||||||
private_key = VALUES(private_key),
|
INSERT INTO hosts (name, host, port, username, password, private_key, group_name, color, description, updated_at)
|
||||||
group_name = VALUES(group_name),
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
color = VALUES(color),
|
`, [host.name, host.host, host.port, host.username, host.password,
|
||||||
description = VALUES(description)
|
host.private_key, host.group_name, host.color, host.description,
|
||||||
`, [host.name, host.host, host.port, host.username, host.password, host.private_key, host.group_name, host.color, host.description]);
|
host.updated_at || new Date().toISOString()]);
|
||||||
|
synced++;
|
||||||
|
} else {
|
||||||
|
// 远程存在,比较时间
|
||||||
|
const localTime = new Date(host.updated_at || 0).getTime();
|
||||||
|
const remoteTime = new Date(existing[0].updated_at || 0).getTime();
|
||||||
|
|
||||||
|
if (localTime > remoteTime) {
|
||||||
|
// 本地更新,才覆盖远程
|
||||||
|
await this.mysqlConnection.execute(`
|
||||||
|
UPDATE hosts SET name=?, port=?, username=?, password=?, private_key=?,
|
||||||
|
group_name=?, color=?, description=?, updated_at=?
|
||||||
|
WHERE host=?
|
||||||
|
`, [host.name, host.port, host.username, host.password, host.private_key,
|
||||||
|
host.group_name, host.color, host.description, host.updated_at, host.host]);
|
||||||
|
synced++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 标记为已同步
|
// 标记为已同步
|
||||||
this.sqliteDb.run('UPDATE hosts SET is_synced = 1 WHERE id = ?', [host.id]);
|
this.sqliteDb.run('UPDATE hosts SET is_synced = 1 WHERE id = ?', [host.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.saveDatabase();
|
this.saveDatabase();
|
||||||
|
return { success: true, synced };
|
||||||
// 记录同步日志
|
|
||||||
this.sqliteDb.run(
|
|
||||||
`INSERT INTO sync_log (sync_type, status, details) VALUES ('upload', 'success', ?)`,
|
|
||||||
[JSON.stringify({ synced_hosts: localHosts.length })]
|
|
||||||
);
|
|
||||||
this.saveDatabase();
|
|
||||||
|
|
||||||
return { success: true, synced: localHosts.length };
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 同步到远程失败:', error);
|
console.error('❌ 同步到远程失败:', error);
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
@ -321,7 +427,7 @@ class DatabaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从远程同步数据
|
* 从远程同步数据 - 以 host 为唯一标识,取最新记录
|
||||||
*/
|
*/
|
||||||
async syncFromRemote() {
|
async syncFromRemote() {
|
||||||
if (!this.isRemoteConnected) {
|
if (!this.isRemoteConnected) {
|
||||||
@ -331,34 +437,42 @@ class DatabaseService {
|
|||||||
try {
|
try {
|
||||||
// 获取远程主机
|
// 获取远程主机
|
||||||
const [remoteHosts] = await this.mysqlConnection.execute('SELECT * FROM hosts');
|
const [remoteHosts] = await this.mysqlConnection.execute('SELECT * FROM hosts');
|
||||||
|
let synced = 0;
|
||||||
|
|
||||||
for (const host of remoteHosts) {
|
for (const remote of remoteHosts) {
|
||||||
this.sqliteDb.run(`
|
// 检查本地是否存在
|
||||||
INSERT OR REPLACE INTO hosts (id, name, host, port, username, password, private_key, group_name, color, description, is_synced)
|
const local = this.runQuerySingle('SELECT * FROM hosts WHERE host = ?', [remote.host]);
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
|
|
||||||
`, [host.id, host.name, host.host, host.port, host.username, host.password, host.private_key, host.group_name, host.color, host.description]);
|
if (!local) {
|
||||||
}
|
// 本地不存在,插入
|
||||||
|
this.sqliteDb.run(`
|
||||||
// 同步命令
|
INSERT INTO hosts (name, host, port, username, password, private_key, group_name, color, description, updated_at, is_synced)
|
||||||
const [remoteCommands] = await this.mysqlConnection.execute('SELECT * FROM commands');
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
|
||||||
|
`, [remote.name, remote.host, remote.port, remote.username, remote.password,
|
||||||
for (const cmd of remoteCommands) {
|
remote.private_key, remote.group_name, remote.color, remote.description,
|
||||||
this.sqliteDb.run(`
|
remote.updated_at?.toISOString() || new Date().toISOString()]);
|
||||||
INSERT OR REPLACE INTO commands (id, command, description, category, usage_count)
|
synced++;
|
||||||
VALUES (?, ?, ?, ?, ?)
|
} else {
|
||||||
`, [cmd.id, cmd.command, cmd.description, cmd.category, cmd.usage_count]);
|
// 本地存在,比较时间
|
||||||
|
const localTime = new Date(local.updated_at || 0).getTime();
|
||||||
|
const remoteTime = new Date(remote.updated_at || 0).getTime();
|
||||||
|
|
||||||
|
if (remoteTime >= localTime) {
|
||||||
|
// 远程更新或相同,覆盖本地
|
||||||
|
this.sqliteDb.run(`
|
||||||
|
UPDATE hosts SET name=?, port=?, username=?, password=?, private_key=?,
|
||||||
|
group_name=?, color=?, description=?, updated_at=?, is_synced=1
|
||||||
|
WHERE host=?
|
||||||
|
`, [remote.name, remote.port, remote.username, remote.password, remote.private_key,
|
||||||
|
remote.group_name, remote.color, remote.description,
|
||||||
|
remote.updated_at?.toISOString() || new Date().toISOString(), remote.host]);
|
||||||
|
synced++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.saveDatabase();
|
this.saveDatabase();
|
||||||
|
return { success: true, hosts: synced };
|
||||||
// 记录同步日志
|
|
||||||
this.sqliteDb.run(
|
|
||||||
`INSERT INTO sync_log (sync_type, status, details) VALUES ('download', 'success', ?)`,
|
|
||||||
[JSON.stringify({ hosts: remoteHosts.length, commands: remoteCommands.length })]
|
|
||||||
);
|
|
||||||
this.saveDatabase();
|
|
||||||
|
|
||||||
return { success: true, hosts: remoteHosts.length, commands: remoteCommands.length };
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 从远程同步失败:', error);
|
console.error('❌ 从远程同步失败:', error);
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user