feat: events visualization
This commit is contained in:
@@ -1235,4 +1235,147 @@ class WiFiConfigViewModel extends ViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
window.WiFiConfigViewModel = WiFiConfigViewModel;
|
||||
window.WiFiConfigViewModel = WiFiConfigViewModel;
|
||||
|
||||
// Events View Model for websocket event visualization
|
||||
class EventViewModel extends ViewModel {
|
||||
constructor() {
|
||||
super();
|
||||
this.setMultiple({
|
||||
events: new Map(), // Map of topic -> { parts: [], count: number, lastSeen: timestamp }
|
||||
isLoading: false,
|
||||
error: null,
|
||||
lastUpdateTime: null
|
||||
});
|
||||
|
||||
// Set up WebSocket listeners for real-time updates
|
||||
this.setupWebSocketListeners();
|
||||
}
|
||||
|
||||
// Set up WebSocket event listeners
|
||||
setupWebSocketListeners() {
|
||||
if (!window.wsClient) {
|
||||
// Retry after a short delay to allow wsClient to initialize
|
||||
setTimeout(() => this.setupWebSocketListeners(), 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Listen for all websocket messages
|
||||
window.wsClient.on('message', (data) => {
|
||||
const topic = data.topic || data.type;
|
||||
|
||||
if (topic) {
|
||||
this.addTopic(topic, data);
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for connection status changes
|
||||
window.wsClient.on('connected', () => {
|
||||
logger.info('EventViewModel: WebSocket connected');
|
||||
});
|
||||
|
||||
window.wsClient.on('disconnected', () => {
|
||||
logger.debug('EventViewModel: WebSocket disconnected');
|
||||
});
|
||||
}
|
||||
|
||||
// Add a topic (parsed by "/" separator)
|
||||
addTopic(topic, data = null) {
|
||||
// Get current events as a new Map to ensure change detection
|
||||
const events = new Map(this.get('events'));
|
||||
|
||||
// Handle nested events from cluster/event
|
||||
let fullTopic = topic;
|
||||
if (topic === 'cluster/event' && data && data.data) {
|
||||
try {
|
||||
const parsedData = typeof data.data === 'string' ? JSON.parse(data.data) : data.data;
|
||||
if (parsedData && parsedData.event) {
|
||||
// Create nested topic chain: cluster/event/api/neopattern
|
||||
fullTopic = `${topic}/${parsedData.event}`;
|
||||
}
|
||||
} catch (e) {
|
||||
// If parsing fails, just use the original topic
|
||||
}
|
||||
}
|
||||
|
||||
const parts = fullTopic.split('/').filter(p => p);
|
||||
|
||||
if (events.has(fullTopic)) {
|
||||
// Update existing event - create new object to ensure change detection
|
||||
const existing = events.get(fullTopic);
|
||||
events.set(fullTopic, {
|
||||
topic: existing.topic,
|
||||
parts: existing.parts,
|
||||
count: existing.count + 1,
|
||||
firstSeen: existing.firstSeen,
|
||||
lastSeen: new Date().toISOString(),
|
||||
lastData: data
|
||||
});
|
||||
} else {
|
||||
// Create new event entry
|
||||
events.set(fullTopic, {
|
||||
topic: fullTopic,
|
||||
parts: parts,
|
||||
count: 1,
|
||||
firstSeen: new Date().toISOString(),
|
||||
lastSeen: new Date().toISOString(),
|
||||
lastData: data
|
||||
});
|
||||
}
|
||||
|
||||
// Use set to trigger change notification
|
||||
this.set('events', events);
|
||||
this.set('lastUpdateTime', new Date().toISOString());
|
||||
}
|
||||
|
||||
// Get all topic parts (unique segments after splitting by "/")
|
||||
getTopicParts() {
|
||||
const events = this.get('events');
|
||||
const allParts = new Set();
|
||||
|
||||
for (const [topic, data] of events) {
|
||||
data.parts.forEach(part => allParts.add(part));
|
||||
}
|
||||
|
||||
return Array.from(allParts).map(part => {
|
||||
// Count how many events contain this part
|
||||
let count = 0;
|
||||
for (const [topic, data] of events) {
|
||||
if (data.parts.includes(part)) {
|
||||
count += data.count;
|
||||
}
|
||||
}
|
||||
|
||||
return { part, count };
|
||||
});
|
||||
}
|
||||
|
||||
// Clear all events
|
||||
clearTopics() {
|
||||
this.set('events', new Map());
|
||||
this.set('lastUpdateTime', new Date().toISOString());
|
||||
}
|
||||
|
||||
// Get connections between topic parts (for graph edges)
|
||||
getTopicConnections() {
|
||||
const topics = this.get('topics');
|
||||
const connections = [];
|
||||
|
||||
for (const [topic, data] of topics) {
|
||||
const parts = data.parts;
|
||||
// Create connections between adjacent parts
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
connections.push({
|
||||
source: parts[i],
|
||||
target: parts[i + 1],
|
||||
topic: topic,
|
||||
count: data.count
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return connections;
|
||||
}
|
||||
}
|
||||
|
||||
window.EventViewModel = EventViewModel;
|
||||
Reference in New Issue
Block a user