XNSim/XNSimHtml/components/data-monitor.js
2025-04-28 16:41:21 +08:00

667 lines
25 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class DataMonitor extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.currentMode = 'udp'; // 默认显示UDP模式
this.udpPort = 54321; // 默认UDP端口
this.udpIp = '127.0.0.1'; // 默认监听所有接口
this.isMonitoring = false; // 监控状态
this.udpData = []; // 存储接收到的UDP数据
// UDP数据注入默认值
this.injectIp = '127.0.0.1';
this.injectPort = 12345;
this.injectData = '{"message": "测试数据"}';
}
connectedCallback() {
this.render();
}
switchMode(mode) {
this.currentMode = mode;
this.render();
}
async startMonitoring() {
if (this.isMonitoring) return;
try {
const response = await fetch('/api/udp-monitor/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
port: this.udpPort,
ip: this.udpIp
}),
});
const data = await response.json();
if (data.success) {
this.isMonitoring = true;
this.updateMonitoringStatus();
this.setupDataFetch();
} else {
this.showError(data.error || '启动监控失败');
}
} catch (error) {
this.showError('无法连接到服务器');
console.error('启动UDP监控失败:', error);
}
}
async stopMonitoring() {
if (!this.isMonitoring) return;
try {
const response = await fetch('/api/udp-monitor/stop', {
method: 'POST',
});
const data = await response.json();
if (data.success) {
this.isMonitoring = false;
this.updateMonitoringStatus();
if (this.dataFetchInterval) {
clearInterval(this.dataFetchInterval);
this.dataFetchInterval = null;
}
} else {
this.showError(data.error || '停止监控失败');
}
} catch (error) {
this.showError('无法连接到服务器');
console.error('停止UDP监控失败:', error);
}
}
async injectUdpData() {
try {
// 验证数据格式
let parsedData;
try {
parsedData = JSON.parse(this.injectData);
} catch (e) {
this.showError('数据格式无效请输入有效的JSON');
return;
}
const response = await fetch('/api/udp-monitor/inject', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
targetIp: this.injectIp,
targetPort: this.injectPort,
data: parsedData
}),
});
const data = await response.json();
if (data.success) {
this.showSuccess('数据已成功发送');
} else {
this.showError(data.error || '发送数据失败');
}
} catch (error) {
this.showError('无法连接到服务器');
console.error('发送UDP数据失败:', error);
}
}
setupDataFetch() {
// 清除可能存在的之前的定时器
if (this.dataFetchInterval) {
clearInterval(this.dataFetchInterval);
}
// 每秒拉取一次新数据
this.dataFetchInterval = setInterval(async () => {
try {
const response = await fetch('/api/udp-monitor/data');
const data = await response.json();
if (data.success && data.data) {
this.updateDataDisplay(data.data);
}
} catch (error) {
console.error('获取UDP数据失败:', error);
}
}, 1000);
}
updateDataDisplay(newData) {
if (!newData || newData.length === 0) return;
// 更新数据并限制显示的数据条数
this.udpData = [...this.udpData, ...newData].slice(-100);
const dataContainer = this.shadowRoot.querySelector('.data-container');
if (!dataContainer) return;
dataContainer.innerHTML = this.udpData.map(item => {
return `<div class="data-item">
<span class="timestamp">${new Date(item.timestamp).toLocaleTimeString()}</span>
<span class="source">${item.source}</span>
<span class="data">${this.formatData(item.data)}</span>
</div>`;
}).join('');
// 自动滚动到底部
dataContainer.scrollTop = dataContainer.scrollHeight;
}
formatData(data) {
// 简单显示为字符串,真实实现可能更复杂
if (typeof data === 'object') {
return JSON.stringify(data);
}
return data;
}
updateMonitoringStatus() {
const statusLabel = this.shadowRoot.querySelector('.status-label');
const startButton = this.shadowRoot.querySelector('#start-monitoring');
const stopButton = this.shadowRoot.querySelector('#stop-monitoring');
const portInput = this.shadowRoot.querySelector('#udp-port');
const ipInput = this.shadowRoot.querySelector('#udp-ip');
if (statusLabel) {
statusLabel.textContent = this.isMonitoring ? '监控中' : '未监控';
statusLabel.className = `status-label ${this.isMonitoring ? 'active' : 'inactive'}`;
}
if (startButton) {
startButton.disabled = this.isMonitoring;
}
if (stopButton) {
stopButton.disabled = !this.isMonitoring;
}
if (portInput) {
portInput.disabled = this.isMonitoring;
}
if (ipInput) {
ipInput.disabled = this.isMonitoring;
}
}
showError(message) {
const errorElement = this.shadowRoot.querySelector('.error-message');
if (errorElement) {
errorElement.textContent = message;
errorElement.style.display = 'block';
errorElement.className = 'message-box error-message';
// 3秒后自动隐藏错误消息
setTimeout(() => {
errorElement.style.display = 'none';
}, 3000);
}
}
showSuccess(message) {
const successElement = this.shadowRoot.querySelector('.success-message');
if (successElement) {
successElement.textContent = message;
successElement.style.display = 'block';
// 3秒后自动隐藏成功消息
setTimeout(() => {
successElement.style.display = 'none';
}, 3000);
}
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
height: 100%;
overflow: auto;
padding: 16px;
box-sizing: border-box;
}
.monitor-container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 16px;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.mode-switcher {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.mode-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.mode-button.active {
background-color: #1890ff;
color: white;
font-weight: bold;
}
.mode-button:not(.active) {
background-color: #f0f0f0;
color: #333;
}
.mode-content {
flex-grow: 1;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 16px;
display: flex;
flex-direction: column;
}
.udp-content {
display: flex;
gap: 16px;
height: 100%;
}
.monitor-section, .inject-section {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.section-divider {
width: 1px;
background-color: #e0e0e0;
margin: 0 8px;
}
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
color: #333;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.mode-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
color: #333;
}
.placeholder-content {
color: #888;
font-style: italic;
}
.control-panel {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 16px;
flex-wrap: wrap;
}
.port-input, .ip-input {
display: flex;
align-items: center;
}
.port-input label, .ip-input label {
margin-right: 8px;
font-size: 14px;
white-space: nowrap;
}
.port-input input, .ip-input input {
padding: 6px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
}
.port-input input {
width: 80px;
}
.ip-input input {
width: 140px;
}
.action-button {
padding: 6px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.action-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.start-button {
background-color: #52c41a;
color: white;
}
.stop-button {
background-color: #f5222d;
color: white;
}
.inject-button {
background-color: #1890ff;
color: white;
}
.status-container {
display: flex;
align-items: center;
margin-left: auto;
}
.status-label {
font-size: 14px;
padding: 4px 8px;
border-radius: 10px;
}
.status-label.active {
background-color: #e6f7ff;
color: #1890ff;
}
.status-label.inactive {
background-color: #f5f5f5;
color: #999;
}
.message-box {
margin-top: 8px;
font-size: 14px;
padding: 8px 12px;
border-radius: 4px;
display: none;
}
.error-message {
background-color: #fff2f0;
border: 1px solid #ffccc7;
color: #f5222d;
}
.success-message {
background-color: #f6ffed;
border: 1px solid #b7eb8f;
color: #52c41a;
}
.data-view {
flex-grow: 1;
margin-top: 16px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.data-container {
flex-grow: 1;
border: 1px solid #f0f0f0;
border-radius: 4px;
padding: 8px;
overflow-y: auto;
background-color: #fafafa;
font-family: monospace;
}
.data-item {
padding: 4px 0;
border-bottom: 1px solid #f0f0f0;
display: flex;
}
.data-item .timestamp {
color: #888;
margin-right: 8px;
min-width: 80px;
}
.data-item .source {
color: #1890ff;
margin-right: 8px;
min-width: 120px;
}
.data-item .data {
color: #333;
word-break: break-all;
}
.data-input {
margin-top: 16px;
}
.data-input label {
display: block;
margin-bottom: 8px;
font-size: 14px;
}
.data-input textarea {
width: 100%;
height: 120px;
padding: 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
resize: vertical;
font-family: monospace;
font-size: 14px;
}
.inject-actions {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
</style>
<div class="monitor-container">
<div class="mode-switcher">
<button class="mode-button ${this.currentMode === 'udp' ? 'active' : ''}" id="udp-mode">UDP监控</button>
<button class="mode-button ${this.currentMode === 'fastdds' ? 'active' : ''}" id="fastdds-mode">FastDDS监控</button>
</div>
<div class="mode-content">
${this.currentMode === 'udp'
? `<div class="mode-title">UDP数据监控</div>
<div class="udp-content">
<!-- 左侧UDP数据监控 -->
<div class="monitor-section">
<div class="section-title">UDP数据监控</div>
<div class="control-panel">
<div class="ip-input">
<label for="udp-ip">监听IP:</label>
<input type="text" id="udp-ip" value="${this.udpIp}"
${this.isMonitoring ? 'disabled' : ''} placeholder="0.0.0.0">
</div>
<div class="port-input">
<label for="udp-port">监听端口:</label>
<input type="number" id="udp-port" min="1024" max="65535" value="${this.udpPort}"
${this.isMonitoring ? 'disabled' : ''}>
</div>
<button id="start-monitoring" class="action-button start-button"
${this.isMonitoring ? 'disabled' : ''}>开始监控</button>
<button id="stop-monitoring" class="action-button stop-button"
${!this.isMonitoring ? 'disabled' : ''}>停止监控</button>
<div class="status-container">
<span class="status-label ${this.isMonitoring ? 'active' : 'inactive'}">
${this.isMonitoring ? '监控中' : '未监控'}
</span>
</div>
</div>
<div class="message-box error-message"></div>
<div class="data-view">
<div class="data-container"></div>
</div>
</div>
<!-- 分隔线 -->
<div class="section-divider"></div>
<!-- 右侧UDP数据注入 -->
<div class="inject-section">
<div class="section-title">UDP数据注入</div>
<div class="control-panel">
<div class="ip-input">
<label for="inject-ip">目标IP:</label>
<input type="text" id="inject-ip" value="${this.injectIp}" placeholder="127.0.0.1">
</div>
<div class="port-input">
<label for="inject-port">目标端口:</label>
<input type="number" id="inject-port" min="1024" max="65535" value="${this.injectPort}">
</div>
</div>
<div class="data-input">
<label for="inject-data">数据内容 (JSON格式):</label>
<textarea id="inject-data">${this.injectData}</textarea>
</div>
<div class="message-box success-message"></div>
<div class="inject-actions">
<button id="inject-data-btn" class="action-button inject-button">发送数据</button>
</div>
</div>
</div>`
: `<div class="mode-title">FastDDS数据监控</div>
<div class="placeholder-content">FastDDS数据监控内容待实现</div>`}
</div>
</div>
`;
// 添加切换模式的事件监听
this.shadowRoot.getElementById('udp-mode').addEventListener('click', () => this.switchMode('udp'));
this.shadowRoot.getElementById('fastdds-mode').addEventListener('click', () => this.switchMode('fastdds'));
// 如果是UDP模式添加控制按钮的事件监听
if (this.currentMode === 'udp') {
// 监控部分事件监听
// IP输入事件
const ipInput = this.shadowRoot.getElementById('udp-ip');
if (ipInput) {
ipInput.addEventListener('change', (e) => {
const value = e.target.value.trim();
// 简单的IP地址验证可以接受0.0.0.0或具体IP
if (value === '0.0.0.0' || /^(\d{1,3}\.){3}\d{1,3}$/.test(value)) {
this.udpIp = value;
} else {
e.target.value = this.udpIp;
this.showError('请输入有效的IP地址');
}
});
}
// 端口输入事件
const portInput = this.shadowRoot.getElementById('udp-port');
if (portInput) {
portInput.addEventListener('change', (e) => {
const value = parseInt(e.target.value, 10);
if (value >= 1024 && value <= 65535) {
this.udpPort = value;
} else {
e.target.value = this.udpPort;
this.showError('端口号必须在1024-65535之间');
}
});
}
// 开始监控按钮
const startButton = this.shadowRoot.getElementById('start-monitoring');
if (startButton) {
startButton.addEventListener('click', () => this.startMonitoring());
}
// 停止监控按钮
const stopButton = this.shadowRoot.getElementById('stop-monitoring');
if (stopButton) {
stopButton.addEventListener('click', () => this.stopMonitoring());
}
// 数据注入部分事件监听
// 注入IP输入事件
const injectIpInput = this.shadowRoot.getElementById('inject-ip');
if (injectIpInput) {
injectIpInput.addEventListener('change', (e) => {
const value = e.target.value.trim();
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(value)) {
this.injectIp = value;
} else {
e.target.value = this.injectIp;
this.showError('请输入有效的IP地址');
}
});
}
// 注入端口输入事件
const injectPortInput = this.shadowRoot.getElementById('inject-port');
if (injectPortInput) {
injectPortInput.addEventListener('change', (e) => {
const value = parseInt(e.target.value, 10);
if (value >= 1024 && value <= 65535) {
this.injectPort = value;
} else {
e.target.value = this.injectPort;
this.showError('端口号必须在1024-65535之间');
}
});
}
// 注入数据输入事件
const injectDataInput = this.shadowRoot.getElementById('inject-data');
if (injectDataInput) {
injectDataInput.addEventListener('change', (e) => {
this.injectData = e.target.value;
});
}
// 发送数据按钮
const injectButton = this.shadowRoot.getElementById('inject-data-btn');
if (injectButton) {
injectButton.addEventListener('click', () => this.injectUdpData());
}
}
}
}
customElements.define('data-monitor', DataMonitor);