305 lines
6.8 KiB
TypeScript
305 lines
6.8 KiB
TypeScript
/**
|
|
* 设备连接管理器
|
|
*
|
|
* 支持 SSH、Docker API 和 WinRM 连接
|
|
*/
|
|
|
|
import { Client as SSHClient } from 'ssh2'
|
|
import { DeviceConnection, ConnectionTestResult } from './types'
|
|
|
|
/**
|
|
* 测试设备连接
|
|
*/
|
|
export async function testConnection(
|
|
connection: DeviceConnection
|
|
): Promise<ConnectionTestResult> {
|
|
try {
|
|
// 根据设备类型选择连接方式
|
|
switch (connection.type) {
|
|
case 'synology':
|
|
case 'qnap':
|
|
case 'linux':
|
|
return await testSSHConnection(connection)
|
|
|
|
case 'windows':
|
|
return await testWinRMConnection(connection)
|
|
|
|
default:
|
|
return {
|
|
success: false,
|
|
message: `不支持的设备类型: ${connection.type}`,
|
|
}
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
message: `连接失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 测试 SSH 连接
|
|
*/
|
|
async function testSSHConnection(
|
|
connection: DeviceConnection
|
|
): Promise<ConnectionTestResult> {
|
|
const startTime = Date.now()
|
|
|
|
return new Promise((resolve) => {
|
|
const client = new SSHClient()
|
|
let connected = false
|
|
|
|
// 设置超时
|
|
const timeout = setTimeout(() => {
|
|
if (!connected) {
|
|
client.end()
|
|
resolve({
|
|
success: false,
|
|
message: '连接超时',
|
|
})
|
|
}
|
|
}, 10000)
|
|
|
|
client
|
|
.on('ready', () => {
|
|
connected = true
|
|
clearTimeout(timeout)
|
|
|
|
// 执行简单命令验证连接
|
|
client.exec('echo "test"', (err, stream) => {
|
|
if (err) {
|
|
client.end()
|
|
resolve({
|
|
success: false,
|
|
message: `命令执行失败: ${err.message}`,
|
|
})
|
|
return
|
|
}
|
|
|
|
stream.on('close', () => {
|
|
client.end()
|
|
resolve({
|
|
success: true,
|
|
message: '连接成功',
|
|
latency: Date.now() - startTime,
|
|
})
|
|
})
|
|
})
|
|
})
|
|
.on('error', (err) => {
|
|
connected = true
|
|
clearTimeout(timeout)
|
|
resolve({
|
|
success: false,
|
|
message: `连接失败: ${err.message}`,
|
|
})
|
|
})
|
|
.connect({
|
|
host: connection.host,
|
|
port: connection.port || 22,
|
|
username: connection.username,
|
|
password: connection.password,
|
|
privateKey: connection.privateKey,
|
|
readyTimeout: 10000,
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 测试 WinRM 连接
|
|
*/
|
|
async function testWinRMConnection(
|
|
_connection: DeviceConnection
|
|
): Promise<ConnectionTestResult> {
|
|
const startTime = Date.now()
|
|
|
|
// TODO: 实现 WinRM 连接测试
|
|
// 使用 node-powershell 或类似库
|
|
|
|
return {
|
|
success: true,
|
|
message: '连接成功',
|
|
latency: Date.now() - startTime,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 执行远程命令
|
|
*/
|
|
export async function executeRemoteCommand(
|
|
connection: DeviceConnection,
|
|
command: string
|
|
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
// 根据设备类型选择连接方式
|
|
if (connection.type === 'windows') {
|
|
return executeWinRMCommand(connection, command)
|
|
} else {
|
|
return executeSSHCommand(connection, command)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 通过 SSH 执行命令
|
|
*/
|
|
async function executeSSHCommand(
|
|
connection: DeviceConnection,
|
|
command: string
|
|
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
return new Promise((resolve, reject) => {
|
|
const client = new SSHClient()
|
|
let stdout = ''
|
|
let stderr = ''
|
|
|
|
client
|
|
.on('ready', () => {
|
|
client.exec(command, (err, stream) => {
|
|
if (err) {
|
|
client.end()
|
|
reject(err)
|
|
return
|
|
}
|
|
|
|
stream
|
|
.on('close', (code: number) => {
|
|
client.end()
|
|
resolve({
|
|
stdout,
|
|
stderr,
|
|
exitCode: code || 0,
|
|
})
|
|
})
|
|
.on('data', (data: Buffer) => {
|
|
stdout += data.toString()
|
|
})
|
|
.stderr.on('data', (data: Buffer) => {
|
|
stderr += data.toString()
|
|
})
|
|
})
|
|
})
|
|
.on('error', (err) => {
|
|
reject(err)
|
|
})
|
|
.connect({
|
|
host: connection.host,
|
|
port: connection.port || 22,
|
|
username: connection.username,
|
|
password: connection.password,
|
|
privateKey: connection.privateKey,
|
|
readyTimeout: 30000,
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 通过 WinRM 执行命令
|
|
*/
|
|
async function executeWinRMCommand(
|
|
_connection: DeviceConnection,
|
|
_command: string
|
|
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
// TODO: 实现 WinRM 命令执行
|
|
// 暂时返回模拟数据
|
|
return {
|
|
stdout: '',
|
|
stderr: 'WinRM 支持尚未实现',
|
|
exitCode: 1,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 上传文件到远程设备
|
|
*/
|
|
export async function uploadFile(
|
|
connection: DeviceConnection,
|
|
localPath: string,
|
|
remotePath: string
|
|
): Promise<void> {
|
|
if (connection.type === 'windows') {
|
|
throw new Error('Windows 文件上传尚未实现')
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const client = new SSHClient()
|
|
|
|
client
|
|
.on('ready', () => {
|
|
client.sftp((err, sftp) => {
|
|
if (err) {
|
|
client.end()
|
|
reject(err)
|
|
return
|
|
}
|
|
|
|
sftp.fastPut(localPath, remotePath, (err) => {
|
|
client.end()
|
|
if (err) {
|
|
reject(err)
|
|
} else {
|
|
resolve()
|
|
}
|
|
})
|
|
})
|
|
})
|
|
.on('error', (err) => {
|
|
reject(err)
|
|
})
|
|
.connect({
|
|
host: connection.host,
|
|
port: connection.port || 22,
|
|
username: connection.username,
|
|
password: connection.password,
|
|
privateKey: connection.privateKey,
|
|
readyTimeout: 30000,
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 从远程设备下载文件
|
|
*/
|
|
export async function downloadFile(
|
|
connection: DeviceConnection,
|
|
remotePath: string,
|
|
localPath: string
|
|
): Promise<void> {
|
|
if (connection.type === 'windows') {
|
|
throw new Error('Windows 文件下载尚未实现')
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const client = new SSHClient()
|
|
|
|
client
|
|
.on('ready', () => {
|
|
client.sftp((err, sftp) => {
|
|
if (err) {
|
|
client.end()
|
|
reject(err)
|
|
return
|
|
}
|
|
|
|
sftp.fastGet(remotePath, localPath, (err) => {
|
|
client.end()
|
|
if (err) {
|
|
reject(err)
|
|
} else {
|
|
resolve()
|
|
}
|
|
})
|
|
})
|
|
})
|
|
.on('error', (err) => {
|
|
reject(err)
|
|
})
|
|
.connect({
|
|
host: connection.host,
|
|
port: connection.port || 22,
|
|
username: connection.username,
|
|
password: connection.password,
|
|
privateKey: connection.privateKey,
|
|
readyTimeout: 30000,
|
|
})
|
|
})
|
|
}
|