XNSim/XNSimHtml/components/data-monitor.js

667 lines
25 KiB
JavaScript
Raw Normal View History

2025-04-28 12:25:20 +08:00
class DataMonitor extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
2025-04-28 16:41:21 +08:00
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": "测试数据"}';
2025-04-28 12:25:20 +08:00
}
connectedCallback() {
this.render();
}
2025-04-28 16:41:21 +08:00
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);
}
}
2025-04-28 12:25:20 +08:00
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;
2025-04-28 16:41:21 +08:00
display: flex;
flex-direction: column;
2025-04-28 12:25:20 +08:00
}
2025-04-28 16:41:21 +08:00
.mode-switcher {
2025-04-28 12:25:20 +08:00
display: flex;
2025-04-28 16:41:21 +08:00
gap: 10px;
2025-04-28 12:25:20 +08:00
margin-bottom: 20px;
}
2025-04-28 16:41:21 +08:00
.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;
2025-04-28 12:25:20 +08:00
font-weight: bold;
2025-04-28 16:41:21 +08:00
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 {
2025-04-28 12:25:20 +08:00
color: #333;
2025-04-28 16:41:21 +08:00
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;
2025-04-28 12:25:20 +08:00
}
</style>
<div class="monitor-container">
2025-04-28 16:41:21 +08:00
<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>`}
2025-04-28 12:25:20 +08:00
</div>
</div>
`;
2025-04-28 16:41:21 +08:00
// 添加切换模式的事件监听
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());
}
}
2025-04-28 12:25:20 +08:00
}
}
customElements.define('data-monitor', DataMonitor);