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,
})
})
}