/** * @class NetworkMonitor * @description 网络监控组件,用于监控系统网络状态和UDP数据抓包 */ class NetworkMonitor extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.state = { ip: '127.0.0.1', port: 54321, isMonitoring: false, data: [], timer: null, statusMsg: '', selectedData: null, }; } connectedCallback() { this.render(); this.initialize(); } disconnectedCallback() { if (this.state.timer) { clearInterval(this.state.timer); } } render() { this.shadowRoot.innerHTML = `
${this.state.statusMsg}
抓包状态:${this.state.isMonitoring ? '运行中' : '已停止'}

UDP数据包详情

请从左侧选择一个数据包查看详情
`; this.shadowRoot.getElementById('startBtn').onclick = () => this.startMonitor(); this.shadowRoot.getElementById('stopBtn').onclick = () => this.stopMonitor(); this.shadowRoot.getElementById('ip').onchange = (e) => { this.state.ip = e.target.value; }; this.shadowRoot.getElementById('port').onchange = (e) => { this.state.port = parseInt(e.target.value, 10); }; this.renderDataList(); } renderDataList() { const dataList = this.shadowRoot.getElementById('dataList'); if (!dataList) return; if (!this.state.data.length) { dataList.innerHTML = '
暂无数据
'; return; } dataList.innerHTML = this.state.data.map((item, index) => `
时间: ${new Date(item.timestamp).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 })} 来源: ${item.source} 大小: ${item.length || this.getDataSize(item.data)} 字节
`).join(''); // 添加点击事件 dataList.querySelectorAll('.data-item').forEach(item => { item.addEventListener('click', (e) => { const index = parseInt(e.currentTarget.dataset.index); this.selectDataItem(index); }); }); } getDataSize(data) { // 如果数据是十六进制字符串,每两个字符代表一个字节 if (typeof data === 'string' && /^[0-9a-fA-F]+$/.test(data)) { return Math.ceil(data.length / 2); } return 0; } formatDataAsHex(data) { // 如果数据是十六进制字符串,直接处理 if (typeof data === 'string' && /^[0-9a-fA-F]+$/.test(data)) { const bytes = []; for (let i = 0; i < data.length; i += 2) { const byte = parseInt(data.substr(i, 2), 16); bytes.push(byte); } // 分行显示,每行16个字节 const bytesPerLine = 16; const lines = []; for (let i = 0; i < bytes.length; i += bytesPerLine) { const lineBytes = bytes.slice(i, i + bytesPerLine); const lineHex = lineBytes.map(byte => byte.toString(16).padStart(2, '0')).join(' '); const offset = i.toString(16).padStart(4, '0').toUpperCase(); lines.push(`${offset}: ${lineHex}`); } return lines.join('\n'); } return '无效数据格式'; } setStatus(msg) { this.state.statusMsg = msg; this.render(); } async startMonitor() { this.setStatus('正在启动UDP抓包...'); try { const res = await fetch('/api/udp-monitor/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ip: this.state.ip, port: this.state.port }) }); const data = await res.json(); if (data.success) { this.state.isMonitoring = true; this.setStatus(data.message || 'UDP抓包已启动'); this.startPolling(); } else { this.state.isMonitoring = false; this.setStatus(data.error || '启动失败'); } } catch (e) { this.state.isMonitoring = false; this.setStatus('启动UDP抓包失败: ' + e.message); } this.render(); } async stopMonitor() { this.setStatus('正在停止UDP抓包...'); try { const res = await fetch('/api/udp-monitor/stop', { method: 'POST' }); const data = await res.json(); this.state.isMonitoring = false; this.setStatus(data.message || 'UDP抓包已停止'); if (this.state.timer) { clearInterval(this.state.timer); this.state.timer = null; } } catch (e) { this.setStatus('停止UDP抓包失败: ' + e.message); } this.render(); } startPolling() { if (this.state.timer) clearInterval(this.state.timer); this.state.timer = setInterval(() => this.fetchData(), 1000); } async fetchData() { if (!this.state.isMonitoring) return; try { const res = await fetch('/api/udp-monitor/data'); const data = await res.json(); if (data.success) { if (Array.isArray(data.data) && data.data.length > 0) { this.state.data = this.state.data.concat(data.data); // 最多只保留1000条 if (this.state.data.length > 1000) { this.state.data = this.state.data.slice(-1000); } this.renderDataList(); this.updateDetailContent(); } this.state.isMonitoring = data.isMonitoring; this.shadowRoot.getElementById('monitorStatus').textContent = data.isMonitoring ? '运行中' : '已停止'; } } catch (e) { this.setStatus('获取UDP数据失败: ' + e.message); } } initialize() { this.setStatus('请设置IP和端口后点击开始抓包'); // 检查当前监控状态 fetch('/api/udp-monitor/status').then(res => res.json()).then(data => { if (data.success && data.isMonitoring) { this.state.isMonitoring = true; this.state.ip = data.ip || this.state.ip; this.state.port = data.port || this.state.port; this.setStatus('UDP抓包已在运行'); this.startPolling(); this.render(); } }); } reactivate() { this.initialize(); } selectDataItem(index) { this.state.selectedData = index; this.renderDataList(); this.updateDetailContent(); } updateDetailContent() { const detailContent = this.shadowRoot.getElementById('detailContent'); if (!detailContent) return; if (this.state.selectedData === null || !this.state.data[this.state.selectedData]) { detailContent.innerHTML = '
请从左侧选择一个数据包查看详情
'; return; } const item = this.state.data[this.state.selectedData]; detailContent.innerHTML = `
${this.formatHexContent(item.data)}
${this.formatParseContent(item.data)}
`; } getPacketIndex(packet) { // 这里可以根据需要实现数据包序号逻辑 return Math.floor(Math.random() * 1000) + 1; } formatHexContent(data) { // 如果数据是十六进制字符串,直接处理 if (typeof data === 'string' && /^[0-9a-fA-F]+$/.test(data)) { const bytes = []; for (let i = 0; i < data.length; i += 2) { const byte = parseInt(data.substr(i, 2), 16); bytes.push(byte); } // 分行显示,每行16个字节 const bytesPerLine = 16; const lines = []; for (let i = 0; i < bytes.length; i += bytesPerLine) { const lineBytes = bytes.slice(i, i + bytesPerLine); const lineHex = lineBytes.map(byte => byte.toString(16).padStart(2, '0')).join(' '); const offset = i.toString(16).padStart(4, '0').toUpperCase(); lines.push(`${offset}: ${lineHex}`); } return lines.join('\n'); } return '无效数据格式'; } formatParseContent(data) { if (typeof data !== 'string' || !/^[0-9a-fA-F]+$/.test(data) || data.length < 14) { return '
数据包长度不足,无法解析
'; } const bytes = []; for (let i = 0; i < data.length; i += 2) { const byte = parseInt(data.substr(i, 2), 16); bytes.push(byte); } // 解析数据包头 const header = bytes.slice(0, 6); // 前6个字节 const sizeBytes = bytes.slice(6, 8); // 第7-8个字节表示数据包大小 let parseResult = '
数据包头解析
'; // 1. a6-XNSim数据 parseResult += `
1. ${header[0].toString(16).padStart(2, '0')} - XNSim数据
`; // 2. c0-C909数据 c1-C919数据 const dataType = header[1]; let dataTypeDesc = ''; if (dataType === 0xc0) { dataTypeDesc = 'C909数据'; } else if (dataType === 0xc1) { dataTypeDesc = 'C919数据'; } else { dataTypeDesc = `未知数据类型(${dataType.toString(16).padStart(2, '0')})`; } parseResult += `
2. ${dataType.toString(16).padStart(2, '0')} - ${dataTypeDesc}
`; // 3. ATA章节号 const ataChapter = header[2]; parseResult += `
3. ${ataChapter.toString(16).padStart(2, '0')} - ATA章节号 (ATA${ataChapter.toString().padStart(2, '0')})
`; // 4. 模型编号 const modelNumber = header[3]; parseResult += `
4. ${modelNumber.toString(16).padStart(2, '0')} - 模型编号 (${modelNumber})
`; // 5. 结构体类别 const structType = header[4]; let structTypeDesc = ''; switch (structType) { case 0x00: structTypeDesc = '输入结构体'; break; case 0x01: structTypeDesc = '输出结构体'; break; case 0x02: structTypeDesc = '心跳结构体'; break; default: structTypeDesc = `未知结构体类型(${structType.toString(16).padStart(2, '0')})`; } parseResult += `
5. ${structType.toString(16).padStart(2, '0')} - 结构体类别 (${structTypeDesc})
`; // 6. 数据传输方向 const direction = header[5]; let directionDesc = ''; switch (direction) { case 0x00: directionDesc = '输入'; break; case 0x01: directionDesc = '输出'; break; default: directionDesc = `未知方向(${direction.toString(16).padStart(2, '0')})`; } parseResult += `
6. ${direction.toString(16).padStart(2, '0')} - 数据传输方向 (${directionDesc})
`; // 7. 数据包大小 const packetSize = (sizeBytes[0] << 8) | sizeBytes[1]; parseResult += `
7. ${sizeBytes[0].toString(16).padStart(2, '0')} ${sizeBytes[1].toString(16).padStart(2, '0')} - 数据包大小 (${packetSize} 字节)
`; return parseResult; } } customElements.define('network-monitor', NetworkMonitor);