「原创声明:保留所有权利,禁止转载」
需求背景
领导要求对测试环境下 websocket 里面的数据进行监控,为此,做了一个小工具,方便直观监控数据的推送情况。
Flask 代理服务
# !/usr/bin/python
# -*- coding: utf-8 -*-
"""
@Author : xxxxxx
@Contact : xxxxxx
@File : flask_proxy.py
@Create Time: 2025/4/27 14:16
@Description: flask代理服务
"""
from flask import Flask, request, jsonify
from flask_cors import CORS
import requests
app = Flask(__name__)
CORS(app) # 启用CORS
@app.route('/proxy', methods=['POST'])
def proxy():
# 获取请求数据
data = {
'username': 'xxxxxx',
'password': 'xxxxxx',
'client_secret': 'xxxxxx',
'client_id': 'xxxxxx',
'grant_type': 'xxxxxx'
}
# 转发请求到目标API
response = requests.post(
'https://xxxxxx/protocol/openid-connect/token',
data=data
)
# 返回响应
return jsonify(response.json()), response.status_code
@app.route('/')
def index():
return render_template('device_monitor.html')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7070, debug=True)
绘制 HTML 监控网页
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Device Monitoring Tool</title>
<style>
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
display: flex;
flex-direction: column;
height: 90vh;
}
h1 {
text-align: center;
color: #2c3e50;
margin-bottom: 20px;
}
.input-section {
display: flex;
gap: 10px;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
font-family: 'Microsoft YaHei', sans-serif;
}
button {
padding: 10px 20px;
background-color: #3498db;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
font-family: 'Microsoft YaHei', sans-serif;
}
button:hover {
background-color: #2980b9;
}
.clear-btn {
background-color: #e74c3c;
}
.clear-btn:hover {
background-color: #c0392b;
}
.start-monitoring {
background-color: #27ae60;
}
.start-monitoring:hover {
background-color: #219653;
}
.stop-monitoring {
background-color: #e74c3c;
}
.stop-monitoring:hover {
background-color: #c0392b;
}
.scroll-area {
flex: 1;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 5px;
padding: 15px;
background-color: #f9f9f9;
margin-bottom: 20px;
}
.log-entry {
display: flex;
padding: 12px 15px;
border-radius: 6px;
margin-bottom: 10px;
font-family: 'Microsoft YaHei', monospace;
font-size: 14px;
line-height: 1.5;
background-color: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
border-left: 4px solid #3498db;
}
.log-entry:last-child {
margin-bottom: 0;
}
.log-content {
flex: 1;
display: flex;
flex-direction: column;
}
.timestamp {
color: #7f8c8d;
font-weight: bold;
display: inline-block;
width: 80px;
}
.message-wrapper {
display: flex;
align-items: flex-start;
margin-top: 5px;
}
.message-header {
margin-left: 10px;
flex: 1;
font-weight: bold;
}
.message-data {
margin-left: 10px;
flex: 1;
white-space: pre-wrap;
}
.status {
margin-left: auto;
padding: 3px 10px;
border-radius: 10px;
font-size: 12px;
font-weight: bold;
}
.status-success {
background-color: #e1f5e1;
color: #2e7d32;
border: 1px solid #c8e6c9;
}
.status-error {
background-color: #ffebee;
color: #c62828;
border: 1px solid #ffcdd2;
}
.status-info {
background-color: #e3f2fd;
color: #1565c0;
border: 1px solid #bbdefb;
}
.equipment-number {
padding: 2px 5px;
border-radius: 3px;
font-weight: bold;
}
.equipment-number-self {
background-color: #e8f5e9;
color: #2e7d32;
}
.equipment-number-other {
background-color: #fff3e0;
color: #e65100;
}
.button-group {
display: flex;
gap: 10px;
}
.data-received {
font-weight: bold;
color: #fff;
background-color: #555;
padding: 3px 8px;
border-radius: 4px;
margin-right: 10px;
}
.message-block {
display: inline-block;
padding: 3px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
margin-right: 10px;
}
.message-blue {
background-color: #3498db;
color: white;
}
.message-orange {
background-color: #e67e22;
color: white;
}
</style>
</head>
<body>
<div class="container">
<h1>Device Monitoring Tool</h1>
<div class="input-section">
<input type="text" id="equipmentId" placeholder="Enter Equipment ID">
<button id="connectBtn">Connect</button>
<button id="clearBtn" class="clear-btn">Clear</button>
</div>
<div class="scroll-area" id="logArea">
<!-- Log content will be displayed here -->
</div>
<div class="button-group">
<button id="startMonitorBtn">Start Monitoring</button>
<button id="stopMonitorBtn" disabled>Stop Monitoring</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const equipmentIdInput = document.getElementById('equipmentId');
const connectBtn = document.getElementById('connectBtn');
const clearBtn = document.getElementById('clearBtn');
const logArea = document.getElementById('logArea');
const startMonitorBtn = document.getElementById('startMonitorBtn');
const stopMonitorBtn = document.getElementById('stopMonitorBtn');
let websocket = null;
let isMonitoring = false;
let currentEquipmentId = null;
let retryCount = 0;
const maxRetryAttempts = 5;
const retryDelay = 3000;
let isConnected = false; // connection status
let isUserDisconnected = false; // whether the user disconnected intentionally
function addLog(message, type = 'info', equipmentNumber = null, messageValue = null) {
const now = new Date();
const options = {
timeZone: 'Asia/Shanghai',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
};
const timestamp = now.toLocaleTimeString('zh-CN', options);
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
const timestampSpan = document.createElement('span');
timestampSpan.className = 'timestamp';
timestampSpan.textContent = timestamp;
const logContent = document.createElement('div');
logContent.className = 'log-content';
const messageWrapper = document.createElement('div');
messageWrapper.className = 'message-wrapper';
const messageHeader = document.createElement('span');
messageHeader.className = 'message-header';
messageHeader.textContent = message;
if (type !== 'info') {
const statusSpan = document.createElement('span');
statusSpan.className = `status status-${type}`;
statusSpan.textContent = type.toUpperCase();
messageWrapper.appendChild(statusSpan);
}
messageWrapper.prepend(messageHeader);
logContent.appendChild(messageWrapper);
if (equipmentNumber !== null) {
const equipmentSpan = document.createElement('span');
equipmentSpan.className = `equipment-number ${equipmentNumber === currentEquipmentId ? 'equipment-number-self' : 'equipment-number-other'}`;
equipmentSpan.textContent = `Device ${equipmentNumber}`;
messageWrapper.appendChild(equipmentSpan);
}
if (messageValue !== null && messageValue !== undefined) {
const messageBlock = document.createElement('span');
messageBlock.className = 'message-block message-blue';
messageBlock.textContent = `Message: ${messageValue}`;
messageWrapper.appendChild(messageBlock);
}
logEntry.appendChild(timestampSpan);
logEntry.appendChild(logContent);
logArea.appendChild(logEntry);
logArea.scrollTop = logArea.scrollHeight;
}
function createLogEntry(timestamp, messageInfo, messageData) {
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
const timestampSpan = document.createElement('span');
timestampSpan.className = 'timestamp';
timestampSpan.textContent = timestamp;
const logContent = document.createElement('div');
logContent.className = 'log-content';
logContent.appendChild(messageInfo);
logContent.appendChild(messageData);
logEntry.appendChild(timestampSpan);
logEntry.appendChild(logContent);
return logEntry;
}
clearBtn.addEventListener('click', function() {
logArea.innerHTML = '';
addLog('Logs have been cleared');
});
function isValidEquipmentId(equipmentId) {
return typeof equipmentId === 'string' && equipmentId.trim() !== '';
}
async function connectWebSocket() {
const equipmentId = equipmentIdInput.value.trim();
if (!isValidEquipmentId(equipmentId)) {
addLog('Please enter a valid equipment number', 'error');
return;
}
currentEquipmentId = equipmentId;
try {
const wsUrl = 'wss://xxxxxx/kc3ip-realtime/websocket';
addLog(`Connecting to WebSocket URL: ${wsUrl}`);
websocket = new WebSocket(wsUrl);
websocket.onopen = async function() {
retryCount = 0;
addLog('WebSocket connection established', 'success');
isConnected = true;
connectBtn.textContent = 'Connected';
connectBtn.style.backgroundColor = '#27ae60';
const tokenResponse = await fetch('http://xxxxxx:7070/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
username: 'xxxxxx',
password: 'xxxxxx',
client_secret: 'xxxxxx',
client_id: 'xxxxxx',
grant_type: 'xxxxxx'
})
});
if (!tokenResponse.ok) {
const body = await tokenResponse.text();
addLog(
`Failed to obtain access_token: ${tokenResponse.statusText}, Response: ${body}`,
'error'
);
throw new Error('Failed to obtain access_token');
}
const tokenData = await tokenResponse.json();
const accessToken = tokenData.access_token;
addLog('Successfully obtained access_token', 'success');
const authMsg = {
message: "authorization",
accessToken: accessToken
};
websocket.send(JSON.stringify(authMsg));
addLog('Authorization information has been sent');
};
websocket.onmessage = function(event) {
handleMessage(event.data);
};
websocket.onerror = function(error) {
addLog(`WebSocket error: ${error.message}`, 'error');
if (websocket) {
websocket.close();
}
retryConnection();
};
websocket.onclose = function() {
addLog('WebSocket connection closed');
websocket = null;
isMonitoring = false;
if (!isUserDisconnected) {
retryConnection();
} else {
isConnected = false;
connectBtn.textContent = 'Connect';
connectBtn.style.backgroundColor = '#3498db';
}
};
} catch (error) {
addLog(`Connection failed: ${error.message}`, 'error');
if (websocket) {
websocket.close();
}
retryConnection();
}
}
function retryConnection() {
if (!isUserDisconnected && retryCount < maxRetryAttempts) {
retryCount++;
addLog(`Connection lost, attempting to reconnect (${retryCount}/${maxRetryAttempts})...`);
setTimeout(connectWebSocket, retryDelay);
} else if (!isUserDisconnected) {
addLog('Maximum retry attempts reached, connection failed', 'error');
}
}
function findEquipmentNumber(obj) {
// Prioritize extracting the equipmentNumber field, followed by the equipment field
if (obj.equipmentNumber !== undefined) {
return obj.equipmentNumber;
}
if (obj.equipment !== undefined) {
return obj.equipment;
}
for (const key in obj) {
if (typeof obj[key] === 'object') {
const found = findEquipmentNumber(obj[key]);
if (found !== undefined) {
return found;
}
}
}
return 'Unknown device';
}
function handleMessage(data) {
try {
const message = JSON.parse(data);
const equipmentNumber = findEquipmentNumber(message);
const messageValue = message.message; // Extract the message field
// Create a log entry
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
// Add timestamp
const now = new Date();
const options = {
timeZone: 'Asia/Shanghai',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
};
const timestamp = now.toLocaleTimeString('zh-CN', options);
const timestampSpan = document.createElement('span');
timestampSpan.className = 'timestamp';
timestampSpan.textContent = timestamp;
// Create log content container
const logContent = document.createElement('div');
logContent.className = 'log-content';
// Create message information container
const messageInfo = document.createElement('div');
messageInfo.className = 'message-wrapper';
messageInfo.style.display = 'flex';
messageInfo.style.alignItems = 'center';
// Add "Received device data" text
const dataReceived = document.createElement('span');
dataReceived.className = 'data-received';
dataReceived.textContent = 'Received device data';
// Add message part (with blue background)
const messageBlock = document.createElement('span');
messageBlock.className = 'message-block message-blue';
messageBlock.textContent = `Message: ${messageValue}`;
// Add equipment number part (with orange background)
const equipmentBlock = document.createElement('span');
equipmentBlock.className = 'message-block message-orange';
equipmentBlock.textContent = `Equipment Number: ${equipmentNumber}`;
// Assemble message information
messageInfo.appendChild(dataReceived);
messageInfo.appendChild(messageBlock);
messageInfo.appendChild(equipmentBlock);
// Add message data
const messageData = document.createElement('span');
messageData.className = 'message-data';
messageData.textContent = JSON.stringify(message, null, 2);
// Assemble log content
logContent.appendChild(messageInfo);
logContent.appendChild(messageData);
// Assemble log entry
logEntry.appendChild(timestampSpan);
logEntry.appendChild(logContent);
// Add to log area
logArea.appendChild(logEntry);
logArea.scrollTop = logArea.scrollHeight;
} catch (error) {
addLog(`Failed to process message: ${error.message}`, 'error');
}
}
startMonitorBtn.addEventListener('click', function() {
if (!websocket || websocket.readyState !== WebSocket.OPEN) {
addLog('Please establish a WebSocket connection first', 'error');
return;
}
const equipmentId = equipmentIdInput.value.trim();
if (!isValidEquipmentId(equipmentId)) {
addLog('Please enter a valid equipment number', 'error');
return;
}
const monitorMsg = {
message: "filterEquipment",
param: {
equipments: [equipmentId]
}
};
websocket.send(JSON.stringify(monitorMsg));
addLog(`Monitoring request sent for equipment number: ${equipmentId}`);
isMonitoring = true;
// Update button state and color
startMonitorBtn.disabled = true;
startMonitorBtn.style.backgroundColor = '#27ae60';
startMonitorBtn.textContent = 'Monitoring';
stopMonitorBtn.disabled = false;
stopMonitorBtn.style.backgroundColor = '#e74c3c';
});
stopMonitorBtn.addEventListener('click', function() {
if (!websocket || websocket.readyState !== WebSocket.OPEN) {
addLog('No active WebSocket connection', 'error');
return;
}
if (!isMonitoring) {
addLog('No monitoring is currently in progress', 'error');
return;
}
const stopMsg = {
message: "filterEquipment",
param: {
equipments: []
}
};
websocket.send(JSON.stringify(stopMsg));
addLog('Stop monitoring request sent');
isMonitoring = false;
// Update button state and color
startMonitorBtn.disabled = false;
startMonitorBtn.style.backgroundColor = '';
startMonitorBtn.textContent = 'Start Monitoring';
stopMonitorBtn.disabled = true;
stopMonitorBtn.style.backgroundColor = '';
});
// Modify the connection button click event handler
connectBtn.addEventListener('click', function() {
if (isConnected) {
// If connected, perform disconnect operation
isUserDisconnected = true; // Mark as user-initiated disconnection
if (websocket && websocket.readyState === WebSocket.OPEN) {
websocket.close();
}
addLog('WebSocket connection manually disconnected', 'info');
isConnected = false;
connectBtn.textContent = 'Connect';
connectBtn.style.backgroundColor = '#3498db';
} else {
// If not connected, perform connection operation
retryCount = 0; // Reset retry count
connectWebSocket();
}
});
});
</script>
</body>
</html>
开始连接,并进行监控
停止监控,并断开连接
TesterHome 为用户提供「保留所有权利,禁止转载」的选项。
除非获得原作者的单独授权,任何第三方不得转载标注了「原创声明:保留所有权利,禁止转载」的内容,否则均视为侵权。
具体请参见TesterHome 知识产权保护协议。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。