feat: primary node switching in topology graph
This commit is contained in:
@@ -601,13 +601,14 @@ class TopologyViewModel extends ViewModel {
|
||||
|
||||
// Update topology from WebSocket data
|
||||
if (data.members && Array.isArray(data.members)) {
|
||||
logger.debug(`TopologyViewModel: Updating topology with ${data.members.length} members`);
|
||||
logger.debug(`TopologyViewModel: Updating topology with ${data.members.length} members, primary: ${data.primaryNode}`);
|
||||
|
||||
// Build enhanced graph data from updated members
|
||||
this.buildEnhancedGraphData(data.members).then(({ nodes, links }) => {
|
||||
// Build enhanced graph data from updated members with primary node info
|
||||
this.buildEnhancedGraphData(data.members, data.primaryNode).then(({ nodes, links }) => {
|
||||
this.batchUpdate({
|
||||
nodes: nodes,
|
||||
links: links,
|
||||
primaryNode: data.primaryNode,
|
||||
lastUpdateTime: data.timestamp || new Date().toISOString()
|
||||
});
|
||||
}).catch(error => {
|
||||
@@ -658,14 +659,24 @@ class TopologyViewModel extends ViewModel {
|
||||
const response = await window.apiClient.getClusterMembers();
|
||||
logger.debug('TopologyViewModel: Got cluster members response:', response);
|
||||
|
||||
// Get discovery info to find the primary node
|
||||
const discoveryInfo = await window.apiClient.getDiscoveryInfo();
|
||||
logger.debug('TopologyViewModel: Got discovery info:', discoveryInfo);
|
||||
|
||||
const members = response.members || [];
|
||||
const primaryNode = discoveryInfo.primaryNode || null;
|
||||
|
||||
logger.debug(`TopologyViewModel: Building graph with ${members.length} members, primary: ${primaryNode}`);
|
||||
|
||||
// Build enhanced graph data with actual node connections
|
||||
const { nodes, links } = await this.buildEnhancedGraphData(members);
|
||||
const { nodes, links } = await this.buildEnhancedGraphData(members, primaryNode);
|
||||
|
||||
logger.debug(`TopologyViewModel: Built graph with ${nodes.length} nodes and ${links.length} links`);
|
||||
|
||||
this.batchUpdate({
|
||||
nodes: nodes,
|
||||
links: links,
|
||||
primaryNode: primaryNode,
|
||||
lastUpdateTime: new Date().toISOString()
|
||||
});
|
||||
|
||||
@@ -679,7 +690,8 @@ class TopologyViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
// Build enhanced graph data with actual node connections
|
||||
async buildEnhancedGraphData(members) {
|
||||
// Creates a star topology with the primary node at the center
|
||||
async buildEnhancedGraphData(members, primaryNode) {
|
||||
const nodes = [];
|
||||
const links = [];
|
||||
|
||||
@@ -691,6 +703,7 @@ class TopologyViewModel extends ViewModel {
|
||||
members.forEach((member, index) => {
|
||||
if (member && member.ip) {
|
||||
const existingNode = existingNodeMap.get(member.ip);
|
||||
const isPrimary = member.ip === primaryNode;
|
||||
|
||||
nodes.push({
|
||||
id: member.ip,
|
||||
@@ -698,6 +711,7 @@ class TopologyViewModel extends ViewModel {
|
||||
ip: member.ip,
|
||||
status: member.status || 'UNKNOWN',
|
||||
latency: member.latency || 0,
|
||||
isPrimary: isPrimary,
|
||||
// Preserve both legacy 'resources' and preferred 'labels'
|
||||
labels: (member.labels && typeof member.labels === 'object') ? member.labels : (member.resources || {}),
|
||||
resources: member.resources || {},
|
||||
@@ -714,28 +728,32 @@ class TopologyViewModel extends ViewModel {
|
||||
}
|
||||
});
|
||||
|
||||
// Build links - create a mesh topology from the members data
|
||||
// All connections are inferred from the cluster membership
|
||||
// No additional API calls needed - all data comes from websocket updates
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
for (let j = i + 1; j < nodes.length; j++) {
|
||||
const sourceNode = nodes[i];
|
||||
const targetNode = nodes[j];
|
||||
|
||||
// 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({
|
||||
source: sourceNode.id,
|
||||
target: targetNode.id,
|
||||
latency: latency,
|
||||
sourceNode: sourceNode,
|
||||
targetNode: targetNode,
|
||||
bidirectional: true
|
||||
});
|
||||
}
|
||||
// Build links - create a star topology with primary node at center
|
||||
// Only create links from the primary node to each member
|
||||
// The cluster data comes from the primary, so it only knows about its direct connections
|
||||
if (primaryNode) {
|
||||
logger.debug(`TopologyViewModel: Creating star topology with primary ${primaryNode}`);
|
||||
nodes.forEach(node => {
|
||||
// Create a link from primary to each non-primary node
|
||||
if (node.ip !== primaryNode) {
|
||||
const member = members.find(m => m.ip === node.ip);
|
||||
const latency = member?.latency || this.estimateLatency(node, { ip: primaryNode });
|
||||
|
||||
logger.debug(`TopologyViewModel: Creating link from ${primaryNode} to ${node.ip} (latency: ${latency}ms)`);
|
||||
|
||||
links.push({
|
||||
source: primaryNode,
|
||||
target: node.id,
|
||||
latency: latency,
|
||||
sourceNode: nodes.find(n => n.ip === primaryNode),
|
||||
targetNode: node,
|
||||
bidirectional: false // Primary -> Member is directional
|
||||
});
|
||||
}
|
||||
});
|
||||
logger.debug(`TopologyViewModel: Created ${links.length} links from primary node`);
|
||||
} else {
|
||||
logger.warn('TopologyViewModel: No primary node specified, cannot create links');
|
||||
}
|
||||
|
||||
return { nodes, links };
|
||||
@@ -758,6 +776,31 @@ class TopologyViewModel extends ViewModel {
|
||||
clearSelection() {
|
||||
this.set('selectedNode', null);
|
||||
}
|
||||
|
||||
// Switch to a specific node as the new primary
|
||||
async switchToPrimaryNode(nodeIp) {
|
||||
try {
|
||||
logger.debug(`TopologyViewModel: Switching primary node to ${nodeIp}`);
|
||||
const result = await window.apiClient.setPrimaryNode(nodeIp);
|
||||
|
||||
if (result.success) {
|
||||
logger.info(`TopologyViewModel: Successfully switched primary to ${nodeIp}`);
|
||||
|
||||
// Update topology after a short delay to allow backend to update
|
||||
setTimeout(async () => {
|
||||
logger.debug('TopologyViewModel: Refreshing topology after primary switch...');
|
||||
await this.updateNetworkTopology();
|
||||
}, 1000);
|
||||
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(result.message || 'Failed to switch primary node');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('TopologyViewModel: Failed to switch primary node:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Monitoring View Model for cluster resource monitoring
|
||||
|
||||
Reference in New Issue
Block a user