feat: add topology view

This commit is contained in:
2025-08-30 13:06:44 +02:00
parent c1b92b3fef
commit f28b4f8797
18 changed files with 2903 additions and 6 deletions

View File

@@ -473,4 +473,160 @@ class NavigationViewModel extends ViewModel {
getActiveView() {
return this.get('activeView');
}
}
// Topology View Model for network topology visualization
class TopologyViewModel extends ViewModel {
constructor() {
super();
this.setMultiple({
nodes: [],
links: [],
isLoading: false,
error: null,
lastUpdateTime: null,
selectedNode: null
});
}
// Update network topology data
async updateNetworkTopology() {
try {
console.log('TopologyViewModel: updateNetworkTopology called');
this.set('isLoading', true);
this.set('error', null);
// Get cluster members from the primary node
const response = await window.apiClient.getClusterMembers();
console.log('TopologyViewModel: Got cluster members response:', response);
const members = response.members || [];
// Build enhanced graph data with actual node connections
const { nodes, links } = await this.buildEnhancedGraphData(members);
this.batchUpdate({
nodes: nodes,
links: links,
lastUpdateTime: new Date().toISOString()
});
} catch (error) {
console.error('TopologyViewModel: Failed to fetch network topology:', error);
this.set('error', error.message);
} finally {
this.set('isLoading', false);
console.log('TopologyViewModel: updateNetworkTopology completed');
}
}
// Build enhanced graph data with actual node connections
async buildEnhancedGraphData(members) {
const nodes = [];
const links = [];
const nodeConnections = new Map();
// Create nodes from members
members.forEach((member, index) => {
if (member && member.ip) {
nodes.push({
id: member.ip,
hostname: member.hostname || member.ip,
ip: member.ip,
status: member.status || 'UNKNOWN',
latency: member.latency || 0,
resources: member.resources || {},
x: Math.random() * 1200 + 100, // Better spacing for 1400px width
y: Math.random() * 800 + 100 // Better spacing for 1000px height
});
}
});
// Try to get cluster members from each node to build actual connections
for (const node of nodes) {
try {
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) {
console.log('TopologyViewModel: No actual connections found, creating basic mesh');
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];
const estimatedLatency = this.estimateLatency(sourceNode, targetNode);
links.push({
source: sourceNode.id,
target: targetNode.id,
latency: estimatedLatency,
sourceNode: sourceNode,
targetNode: targetNode,
bidirectional: true
});
}
}
}
return { nodes, links };
}
// Estimate latency between two nodes
estimateLatency(sourceNode, targetNode) {
// Simple estimation - in a real implementation, you'd get actual measurements
const baseLatency = 5; // Base latency in ms
const randomVariation = Math.random() * 10; // Random variation
return Math.round(baseLatency + randomVariation);
}
// Select a node in the graph
selectNode(nodeId) {
this.set('selectedNode', nodeId);
}
// Clear node selection
clearSelection() {
this.set('selectedNode', null);
}
}