feat: node_discovered indicator in graph
This commit is contained in:
@@ -230,6 +230,7 @@ class TopologyGraphComponent extends Component {
|
||||
this.subscribeToProperty('isLoading', this.handleLoadingState.bind(this));
|
||||
this.subscribeToProperty('error', this.handleError.bind(this));
|
||||
this.subscribeToProperty('selectedNode', this.updateSelection.bind(this));
|
||||
this.subscribeToProperty('discoveryEvent', this.handleDiscoveryEvent.bind(this));
|
||||
} else {
|
||||
// Component not yet initialized, store for later
|
||||
logger.debug('TopologyGraphComponent: Component not initialized, storing pending subscriptions');
|
||||
@@ -238,7 +239,8 @@ class TopologyGraphComponent extends Component {
|
||||
['links', this.renderGraph.bind(this)],
|
||||
['isLoading', this.handleLoadingState.bind(this)],
|
||||
['error', this.handleError.bind(this)],
|
||||
['selectedNode', this.updateSelection.bind(this)]
|
||||
['selectedNode', this.updateSelection.bind(this)],
|
||||
['discoveryEvent', this.handleDiscoveryEvent.bind(this)]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1259,6 +1261,93 @@ class TopologyGraphComponent extends Component {
|
||||
.attr('stroke', d => d.id === selectedNodeId ? '#2196F3' : '#fff');
|
||||
}
|
||||
|
||||
handleDiscoveryEvent(discoveryEvent) {
|
||||
if (!discoveryEvent || !discoveryEvent.nodeIp) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit discovery dots from the discovered node to all other nodes
|
||||
this.emitDiscoveryDots(discoveryEvent.nodeIp);
|
||||
}
|
||||
|
||||
emitDiscoveryDots(sourceNodeIp) {
|
||||
if (!this.svg || !this.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodes = this.viewModel.get('nodes') || [];
|
||||
const sourceNode = nodes.find(n => n.ip === sourceNodeIp);
|
||||
|
||||
if (!sourceNode || !sourceNode.x || !sourceNode.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the main graph group (that has the transform applied)
|
||||
const mainGroup = this.svg.select('g');
|
||||
|
||||
// Get or create animation group inside the main transformed group
|
||||
let animGroup = mainGroup.select('.discovery-animation-group');
|
||||
if (animGroup.empty()) {
|
||||
animGroup = mainGroup.append('g').attr('class', 'discovery-animation-group');
|
||||
}
|
||||
|
||||
// Emit a dot to each other node
|
||||
nodes.forEach(targetNode => {
|
||||
if (targetNode.ip !== sourceNodeIp && targetNode.x && targetNode.y) {
|
||||
this.animateDiscoveryDot(animGroup, sourceNode, targetNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
animateDiscoveryDot(animGroup, sourceNode, targetNode) {
|
||||
// Create a small circle at the source node
|
||||
const dot = animGroup.append('circle')
|
||||
.attr('class', 'discovery-dot')
|
||||
.attr('r', 6)
|
||||
.attr('cx', sourceNode.x)
|
||||
.attr('cy', sourceNode.y)
|
||||
.attr('fill', '#FFD700')
|
||||
.attr('opacity', 1)
|
||||
.attr('stroke', '#FFF')
|
||||
.attr('stroke-width', 2);
|
||||
|
||||
// Trigger response dot early (after 70% of the journey)
|
||||
setTimeout(() => {
|
||||
this.animateResponseDot(animGroup, targetNode, sourceNode);
|
||||
}, 1050); // 1500ms * 0.7 = 1050ms
|
||||
|
||||
// Animate the dot to the target node
|
||||
dot.transition()
|
||||
.duration(1500)
|
||||
.ease(d3.easeCubicInOut)
|
||||
.attr('cx', targetNode.x)
|
||||
.attr('cy', targetNode.y)
|
||||
.attr('opacity', 0)
|
||||
.remove();
|
||||
}
|
||||
|
||||
animateResponseDot(animGroup, sourceNode, targetNode) {
|
||||
// Create a response dot at the target (now source) node
|
||||
const responseDot = animGroup.append('circle')
|
||||
.attr('class', 'discovery-dot-response')
|
||||
.attr('r', 5)
|
||||
.attr('cx', sourceNode.x)
|
||||
.attr('cy', sourceNode.y)
|
||||
.attr('fill', '#4CAF50')
|
||||
.attr('opacity', 1)
|
||||
.attr('stroke', '#FFF')
|
||||
.attr('stroke-width', 2);
|
||||
|
||||
// Animate back to the original source
|
||||
responseDot.transition()
|
||||
.duration(1000)
|
||||
.ease(d3.easeCubicInOut)
|
||||
.attr('cx', targetNode.x)
|
||||
.attr('cy', targetNode.y)
|
||||
.attr('opacity', 0)
|
||||
.remove();
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -623,13 +623,15 @@ class TopologyViewModel extends ViewModel {
|
||||
window.wsClient.on('nodeDiscovery', (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') {
|
||||
logger.debug('TopologyViewModel: Node discovered, waiting for clusterUpdate event');
|
||||
} else if (data.action === 'stale') {
|
||||
logger.debug('TopologyViewModel: Node became stale, waiting for clusterUpdate event');
|
||||
// Trigger animation for 'discovered' or 'active' actions (node is alive)
|
||||
// Skip animation for 'stale' or 'inactive' actions
|
||||
if (data.action === 'discovered' || data.action === 'active') {
|
||||
// Emit discovery animation event
|
||||
this.set('discoveryEvent', {
|
||||
nodeIp: data.nodeIp,
|
||||
timestamp: data.timestamp || new Date().toISOString(),
|
||||
action: data.action
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user