667 lines
25 KiB
JavaScript
667 lines
25 KiB
JavaScript
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);
|