- 完整实现Apple HIG 2025设计系统 - 添加Modal弹窗组件(毛玻璃效果、流畅动画) - 添加FileUpload文件上传组件(拖拽、批量上传、进度显示) - 实现产品上传功能(支持DWG/GLB/GLTF文件) - 优化产品列表展示(可折叠的产品+SKU结构) - 实现Tab切换功能(3D模型管理、五金件、BOM、文档) - 修复主题系统hydration错误 - 优化按钮和间距,符合Apple设计规范 - 添加分享页面的Apple风格重设计 - 创建测试数据脚本
360 lines
9.7 KiB
TypeScript
360 lines
9.7 KiB
TypeScript
/**
|
||
* 硬件检测引擎
|
||
*
|
||
* 检测设备的 CPU、内存、磁盘和 Docker 环境
|
||
*/
|
||
|
||
import {
|
||
DeviceConnection,
|
||
DeviceProfile,
|
||
HardwareDetectionResult,
|
||
DeploymentMode,
|
||
CpuInfo,
|
||
CpuArchitecture,
|
||
MemoryInfo,
|
||
DiskInfo,
|
||
DockerInfo,
|
||
} from './types'
|
||
import { executeRemoteCommand } from './connection'
|
||
|
||
/**
|
||
* 检测设备硬件配置
|
||
*/
|
||
export async function detectHardware(
|
||
connection: DeviceConnection
|
||
): Promise<HardwareDetectionResult> {
|
||
try {
|
||
// 检测各项硬件信息
|
||
const cpu = await detectCPU(connection)
|
||
const memory = await detectMemory(connection)
|
||
const disk = await detectDisk(connection)
|
||
const docker = await detectDocker(connection)
|
||
|
||
const profile: DeviceProfile = {
|
||
deviceType: connection.type,
|
||
cpu,
|
||
memory,
|
||
disk,
|
||
docker,
|
||
}
|
||
|
||
// 分析硬件配置并给出建议
|
||
const analysis = analyzeHardware(profile)
|
||
|
||
return {
|
||
profile,
|
||
...analysis,
|
||
}
|
||
} catch (error) {
|
||
throw new Error(`硬件检测失败: ${error instanceof Error ? error.message : String(error)}`)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检测 CPU 信息
|
||
*/
|
||
async function detectCPU(connection: DeviceConnection): Promise<CpuInfo> {
|
||
if (connection.type === 'windows') {
|
||
// Windows: 使用 wmic
|
||
const result = await executeRemoteCommand(
|
||
connection,
|
||
'wmic cpu get name,numberofcores /format:csv'
|
||
)
|
||
|
||
if (result.exitCode === 0) {
|
||
const lines = result.stdout.trim().split('\n').filter(line => line.trim())
|
||
if (lines.length >= 2) {
|
||
const data = lines[1].split(',')
|
||
const model = data[1]?.trim() || 'Unknown'
|
||
const cores = parseInt(data[2]?.trim() || '0', 10)
|
||
|
||
return {
|
||
cores,
|
||
model,
|
||
isN100OrLower: model.toLowerCase().includes('n100') || cores <= 4,
|
||
architecture: 'x86_64',
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// Linux/NAS: 使用 lscpu
|
||
const result = await executeRemoteCommand(connection, 'lscpu')
|
||
|
||
if (result.exitCode === 0) {
|
||
const lines = result.stdout.split('\n')
|
||
let cores = 0
|
||
let model = 'Unknown'
|
||
let architecture = 'x86_64'
|
||
|
||
for (const line of lines) {
|
||
if (line.includes('CPU(s):')) {
|
||
const match = line.match(/CPU\(s\):\s+(\d+)/)
|
||
if (match) cores = parseInt(match[1], 10)
|
||
} else if (line.includes('Model name:')) {
|
||
model = line.split(':')[1]?.trim() || 'Unknown'
|
||
} else if (line.includes('Architecture:')) {
|
||
const arch = line.split(':')[1]?.trim() || 'x86_64'
|
||
architecture = arch === 'x86_64' || arch === 'aarch64' ? arch : 'x86_64'
|
||
}
|
||
}
|
||
|
||
return {
|
||
cores,
|
||
model,
|
||
isN100OrLower: model.toLowerCase().includes('n100') || cores <= 4,
|
||
architecture: architecture as CpuArchitecture,
|
||
}
|
||
}
|
||
}
|
||
|
||
// 默认返回值
|
||
return {
|
||
cores: 4,
|
||
model: 'Unknown',
|
||
isN100OrLower: true,
|
||
architecture: 'x86_64',
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检测内存信息
|
||
*/
|
||
async function detectMemory(connection: DeviceConnection): Promise<MemoryInfo> {
|
||
if (connection.type === 'windows') {
|
||
// Windows: 使用 wmic
|
||
const result = await executeRemoteCommand(
|
||
connection,
|
||
'wmic OS get TotalVisibleMemorySize,FreePhysicalMemory /format:csv'
|
||
)
|
||
|
||
if (result.exitCode === 0) {
|
||
const lines = result.stdout.trim().split('\n').filter(line => line.trim())
|
||
if (lines.length >= 2) {
|
||
const data = lines[1].split(',')
|
||
const freeKB = parseInt(data[1]?.trim() || '0', 10)
|
||
const totalKB = parseInt(data[2]?.trim() || '0', 10)
|
||
|
||
const total = Math.round(totalKB / 1024 / 1024) // KB to GB
|
||
const available = Math.round(freeKB / 1024 / 1024)
|
||
const used = total - available
|
||
|
||
return { total, available, used }
|
||
}
|
||
}
|
||
} else {
|
||
// Linux/NAS: 使用 free
|
||
const result = await executeRemoteCommand(connection, 'free -g')
|
||
|
||
if (result.exitCode === 0) {
|
||
const lines = result.stdout.split('\n')
|
||
const memLine = lines.find(line => line.startsWith('Mem:'))
|
||
|
||
if (memLine) {
|
||
const parts = memLine.split(/\s+/).filter(p => p)
|
||
const total = parseInt(parts[1] || '0', 10)
|
||
const used = parseInt(parts[2] || '0', 10)
|
||
const available = parseInt(parts[6] || '0', 10)
|
||
|
||
return { total, available, used }
|
||
}
|
||
}
|
||
}
|
||
|
||
// 默认返回值
|
||
return {
|
||
total: 4,
|
||
available: 3,
|
||
used: 1,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检测磁盘信息
|
||
*/
|
||
async function detectDisk(connection: DeviceConnection): Promise<DiskInfo> {
|
||
if (connection.type === 'windows') {
|
||
// Windows: 使用 wmic (检测 C: 盘)
|
||
const result = await executeRemoteCommand(
|
||
connection,
|
||
'wmic logicaldisk where "DeviceID=\'C:\'" get Size,FreeSpace /format:csv'
|
||
)
|
||
|
||
if (result.exitCode === 0) {
|
||
const lines = result.stdout.trim().split('\n').filter(line => line.trim())
|
||
if (lines.length >= 2) {
|
||
const data = lines[1].split(',')
|
||
const freeBytes = parseInt(data[1]?.trim() || '0', 10)
|
||
const totalBytes = parseInt(data[2]?.trim() || '0', 10)
|
||
|
||
const total = Math.round(totalBytes / 1024 / 1024 / 1024) // Bytes to GB
|
||
const available = Math.round(freeBytes / 1024 / 1024 / 1024)
|
||
const used = total - available
|
||
|
||
return { total, available, used, type: 'unknown' }
|
||
}
|
||
}
|
||
} else {
|
||
// Linux/NAS: 使用 df
|
||
const result = await executeRemoteCommand(connection, 'df -BG /')
|
||
|
||
if (result.exitCode === 0) {
|
||
const lines = result.stdout.split('\n')
|
||
const dataLine = lines[1]
|
||
|
||
if (dataLine) {
|
||
const parts = dataLine.split(/\s+/).filter(p => p)
|
||
const total = parseInt(parts[1]?.replace('G', '') || '0', 10)
|
||
const used = parseInt(parts[2]?.replace('G', '') || '0', 10)
|
||
const available = parseInt(parts[3]?.replace('G', '') || '0', 10)
|
||
|
||
// 尝试检测磁盘类型
|
||
const lsblkResult = await executeRemoteCommand(connection, 'lsblk -d -o name,rota')
|
||
let type: 'ssd' | 'hdd' | 'unknown' = 'unknown'
|
||
|
||
if (lsblkResult.exitCode === 0 && lsblkResult.stdout.includes('0')) {
|
||
type = 'ssd'
|
||
} else if (lsblkResult.exitCode === 0 && lsblkResult.stdout.includes('1')) {
|
||
type = 'hdd'
|
||
}
|
||
|
||
return { total, available, used, type }
|
||
}
|
||
}
|
||
}
|
||
|
||
// 默认返回值
|
||
return {
|
||
total: 100,
|
||
available: 80,
|
||
used: 20,
|
||
type: 'ssd',
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检测 Docker 环境
|
||
*/
|
||
async function detectDocker(connection: DeviceConnection): Promise<DockerInfo> {
|
||
let version = 'unknown'
|
||
let composeVersion = 'unknown'
|
||
let running = false
|
||
let rootless = false
|
||
|
||
// 检测 Docker 版本
|
||
try {
|
||
const versionResult = await executeRemoteCommand(connection, 'docker --version')
|
||
if (versionResult.exitCode === 0) {
|
||
const match = versionResult.stdout.match(/Docker version ([\d.]+)/)
|
||
if (match) {
|
||
version = match[1]
|
||
running = true
|
||
}
|
||
}
|
||
} catch {
|
||
// Docker 未安装或未运行
|
||
}
|
||
|
||
// 检测 Docker Compose 版本
|
||
try {
|
||
const composeResult = await executeRemoteCommand(connection, 'docker-compose --version')
|
||
if (composeResult.exitCode === 0) {
|
||
const match = composeResult.stdout.match(/version ([\d.]+)/)
|
||
if (match) {
|
||
composeVersion = match[1]
|
||
}
|
||
}
|
||
} catch {
|
||
// Docker Compose 未安装
|
||
}
|
||
|
||
// 检测是否为 rootless 模式
|
||
if (running) {
|
||
try {
|
||
const infoResult = await executeRemoteCommand(connection, 'docker info')
|
||
if (infoResult.exitCode === 0) {
|
||
rootless = infoResult.stdout.toLowerCase().includes('rootless')
|
||
}
|
||
} catch {
|
||
// 无法获取 Docker 信息
|
||
}
|
||
}
|
||
|
||
return {
|
||
version,
|
||
composeVersion,
|
||
rootless,
|
||
running,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 分析硬件配置
|
||
*/
|
||
function analyzeHardware(profile: DeviceProfile): {
|
||
recommendedMode: DeploymentMode
|
||
warnings: string[]
|
||
canDeploy: boolean
|
||
reason?: string
|
||
} {
|
||
const warnings: string[] = []
|
||
|
||
// 检查最低内存要求
|
||
if (profile.memory.total < 4) {
|
||
return {
|
||
recommendedMode: 'lightweight',
|
||
warnings: [],
|
||
canDeploy: false,
|
||
reason: `内存不足。系统要求最低 4GB 内存,当前仅有 ${profile.memory.total}GB。`,
|
||
}
|
||
}
|
||
|
||
// 检查磁盘空间
|
||
if (profile.disk.available < 50) {
|
||
warnings.push(`磁盘可用空间不足 50GB,当前仅有 ${profile.disk.available}GB。建议清理磁盘空间。`)
|
||
}
|
||
|
||
// 检查 Docker 环境
|
||
if (!profile.docker.running) {
|
||
return {
|
||
recommendedMode: 'lightweight',
|
||
warnings: [],
|
||
canDeploy: false,
|
||
reason: 'Docker 未运行。请先启动 Docker 服务。',
|
||
}
|
||
}
|
||
|
||
// 根据内存大小推荐部署模式
|
||
let recommendedMode: DeploymentMode
|
||
|
||
if (profile.memory.total < 8) {
|
||
recommendedMode = 'lightweight'
|
||
warnings.push(`内存为 ${profile.memory.total}GB,将使用轻量级部署模式。推荐升级到 8GB 以获得更好性能。`)
|
||
} else {
|
||
recommendedMode = 'standard'
|
||
}
|
||
|
||
// N100 芯片提示
|
||
if (profile.cpu.isN100OrLower) {
|
||
warnings.push('检测到 Intel N100 或类似低功耗处理器,系统已针对此配置优化。')
|
||
}
|
||
|
||
return {
|
||
recommendedMode,
|
||
warnings,
|
||
canDeploy: true,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取系统信息摘要
|
||
*/
|
||
export function getSystemSummary(profile: DeviceProfile): string {
|
||
return `
|
||
设备类型: ${profile.deviceType}
|
||
处理器: ${profile.cpu.model} (${profile.cpu.cores} 核心)
|
||
内存: ${profile.memory.total}GB (可用 ${profile.memory.available}GB)
|
||
磁盘: ${profile.disk.total}GB (可用 ${profile.disk.available}GB, ${profile.disk.type.toUpperCase()})
|
||
Docker: ${profile.docker.version} (Compose ${profile.docker.composeVersion})
|
||
`.trim()
|
||
}
|