Compare commits

..

1 Commits

Author SHA1 Message Date
fecbc1e73d feat: live topology view through websocket updates 2025-10-23 21:42:48 +02:00
2 changed files with 38 additions and 93 deletions

View File

@@ -1025,22 +1025,13 @@ class TopologyGraphComponent extends Component {
.attr('stroke', d => d.id === selectedNodeId ? '#2196F3' : '#fff'); .attr('stroke', d => d.id === selectedNodeId ? '#2196F3' : '#fff');
} }
// NOTE: This method is deprecated and should not be used
// The topology graph is now entirely websocket-driven
// Refresh button was removed and all updates come from websocket events
handleRefresh() { handleRefresh() {
logger.debug('TopologyGraphComponent: handleRefresh called'); logger.warn('TopologyGraphComponent: handleRefresh called - this method is deprecated');
logger.warn('TopologyGraphComponent: Topology updates should come from websocket events only');
if (!this.isInitialized) { // No-op - do not make API calls
logger.debug('TopologyGraphComponent: Component not yet initialized, ensuring initialization...');
this.ensureInitialized().then(() => {
// Refresh after initialization
this.viewModel.updateNetworkTopology();
}).catch(error => {
logger.error('TopologyGraphComponent: Failed to initialize for refresh:', error);
});
return;
}
logger.debug('TopologyGraphComponent: Calling updateNetworkTopology...');
this.viewModel.updateNetworkTopology();
} }
handleLoadingState(isLoading) { handleLoadingState(isLoading) {

View File

@@ -622,26 +622,21 @@ class TopologyViewModel extends ViewModel {
window.wsClient.on('nodeDiscovery', (data) => { window.wsClient.on('nodeDiscovery', (data) => {
logger.debug('TopologyViewModel: Received WebSocket node discovery event:', data); logger.debug('TopologyViewModel: Received WebSocket node discovery event:', data);
// Node discovery events are logged but no action needed
// The subsequent clusterUpdate event will provide the updated member list
// and update the topology through the websocket data flow
if (data.action === 'discovered') { if (data.action === 'discovered') {
// A new node was discovered - trigger a topology update logger.debug('TopologyViewModel: Node discovered, waiting for clusterUpdate event');
setTimeout(() => {
this.updateNetworkTopology();
}, 500);
} else if (data.action === 'stale') { } else if (data.action === 'stale') {
// A node became stale - trigger a topology update logger.debug('TopologyViewModel: Node became stale, waiting for clusterUpdate event');
setTimeout(() => {
this.updateNetworkTopology();
}, 500);
} }
}); });
// Listen for connection status changes // Listen for connection status changes
window.wsClient.on('connected', () => { window.wsClient.on('connected', () => {
logger.debug('TopologyViewModel: WebSocket connected'); logger.debug('TopologyViewModel: WebSocket connected');
// Trigger an immediate update when connection is restored // Connection restored - the server will send a clusterUpdate event shortly
setTimeout(() => { // No need to make an API call, just wait for the websocket data
this.updateNetworkTopology();
}, 1000);
}); });
window.wsClient.on('disconnected', () => { window.wsClient.on('disconnected', () => {
@@ -650,9 +645,11 @@ class TopologyViewModel extends ViewModel {
} }
// Update network topology data // Update network topology data
// NOTE: This method makes an API call and should only be used for initial load
// All subsequent updates should come from websocket events (clusterUpdate)
async updateNetworkTopology() { async updateNetworkTopology() {
try { try {
logger.debug('TopologyViewModel: updateNetworkTopology called'); logger.debug('TopologyViewModel: updateNetworkTopology called (API call)');
this.set('isLoading', true); this.set('isLoading', true);
this.set('error', null); this.set('error', null);
@@ -685,7 +682,6 @@ class TopologyViewModel extends ViewModel {
async buildEnhancedGraphData(members) { async buildEnhancedGraphData(members) {
const nodes = []; const nodes = [];
const links = []; const links = [];
const nodeConnections = new Map();
// Get existing nodes to preserve their positions // Get existing nodes to preserve their positions
const existingNodes = this.get('nodes') || []; const existingNodes = this.get('nodes') || [];
@@ -718,71 +714,29 @@ class TopologyViewModel extends ViewModel {
} }
}); });
// Try to get cluster members from each node to build actual connections // Build links - create a mesh topology from the members data
for (const node of nodes) { // All connections are inferred from the cluster membership
try { // No additional API calls needed - all data comes from websocket updates
const nodeResponse = await window.apiClient.getClusterMembersFromNode(node.ip);
if (nodeResponse && nodeResponse.members) {
nodeConnections.set(node.ip, nodeResponse.members);
}
} catch (error) {
console.warn(`Failed to get cluster members from node ${node.ip}:`, error);
// Continue with other nodes
}
}
// Build links based on actual connections
for (const [sourceIp, sourceMembers] of nodeConnections) {
for (const targetMember of sourceMembers) {
if (targetMember.ip && targetMember.ip !== sourceIp) {
// Check if we already have this link
const existingLink = links.find(link =>
(link.source === sourceIp && link.target === targetMember.ip) ||
(link.source === targetMember.ip && link.target === sourceIp)
);
if (!existingLink) {
const sourceNode = nodes.find(n => n.id === sourceIp);
const targetNode = nodes.find(n => n.id === targetMember.ip);
if (sourceNode && targetNode) {
const latency = targetMember.latency || this.estimateLatency(sourceNode, targetNode);
links.push({
source: sourceIp,
target: targetMember.ip,
latency: latency,
sourceNode: sourceNode,
targetNode: targetNode,
bidirectional: true
});
}
}
}
}
}
// If no actual connections found, create a basic mesh
if (links.length === 0) {
logger.debug('TopologyViewModel: No actual connections found, creating basic mesh');
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) { for (let j = i + 1; j < nodes.length; j++) {
const sourceNode = nodes[i]; const sourceNode = nodes[i];
const targetNode = nodes[j]; const targetNode = nodes[j];
const estimatedLatency = this.estimateLatency(sourceNode, targetNode); // Use the latency from the member data if available, otherwise estimate
const sourceMember = members.find(m => m.ip === sourceNode.ip);
const targetMember = members.find(m => m.ip === targetNode.ip);
const latency = targetMember?.latency || sourceMember?.latency || this.estimateLatency(sourceNode, targetNode);
links.push({ links.push({
source: sourceNode.id, source: sourceNode.id,
target: targetNode.id, target: targetNode.id,
latency: estimatedLatency, latency: latency,
sourceNode: sourceNode, sourceNode: sourceNode,
targetNode: targetNode, targetNode: targetNode,
bidirectional: true bidirectional: true
}); });
} }
} }
}
return { nodes, links }; return { nodes, links };
} }