// 全局变量 let cy = null; let currentTaskId = null; // 初始化 document.addEventListener('DOMContentLoaded', function() { initCytoscape(); initEventListeners(); loadTopology(); loadDeviceList(); // 加载设备列表 }); // 初始化Cytoscape function initCytoscape() { cy = cytoscape({ container: document.getElementById('cy'), elements: [], style: [ { selector: 'node', style: { 'label': 'data(label)', 'background-color': function(ele) { return getNodeColor(ele.data('type')); }, 'width': 70, 'height': 70, 'border-width': 3, 'border-color': '#667eea', 'text-valign': 'bottom', 'text-halign': 'center', 'font-size': '11px', 'font-weight': 'bold', 'text-wrap': 'wrap', 'text-max-width': '80px' } }, { selector: 'edge', style: { 'width': 2, 'line-color': '#999', 'target-arrow-color': '#999', 'target-arrow-shape': 'triangle', 'curve-style': 'bezier', 'label': 'data(protocol)' } }, { selector: 'node:selected', style: { 'border-width': 5, 'border-color': '#FF9800' } } ], layout: { name: 'cose', animate: true, animationDuration: 1000, padding: 30 } }); // 节点点击事件 cy.on('tap', 'node', function(evt) { const node = evt.target; showDeviceDetail(node.data('id')); }); } // 获取节点颜色 function getNodeColor(type) { const colors = { 'cisco': '#4CAF50', 'huawei': '#2196F3', 'h3c': '#9C27B0', 'asa': '#FF5722', 'linux': '#607D8B', 'windows': '#00BCD4' }; return colors[type] || '#999'; } // 初始化事件监听 function initEventListeners() { // 扫描按钮 document.getElementById('btn-scan').addEventListener('click', startScan); // 添加设备按钮 document.getElementById('btn-add-device').addEventListener('click', function() { document.getElementById('modal').classList.add('active'); }); // 关闭模态框 document.querySelector('.close').addEventListener('click', function() { document.getElementById('modal').classList.remove('active'); }); // 添加设备表单 document.getElementById('add-device-form').addEventListener('submit', addDevice); // 导出按钮 document.getElementById('btn-export').addEventListener('click', exportTopology); } // 开始扫描 async function startScan() { const scanRange = document.getElementById('scan-range').value; const sshPort = document.getElementById('ssh-port').value; const username = document.getElementById('username').value; const password = document.getElementById('password').value; if (!scanRange) { alert('请输入IP范围'); return; } try { const response = await fetch('/api/scan', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scan_range: scanRange, ssh_port: parseInt(sshPort), username: username, password: password }) }); const data = await response.json(); currentTaskId = data.task_id; // 轮询进度 pollProgress(); } catch (error) { console.error('扫描失败:', error); alert('扫描失败: ' + error.message); } } // 轮询进度 async function pollProgress() { if (!currentTaskId) return; const poll = async () => { try { const response = await fetch(`/api/scan/${currentTaskId}`); const task = await response.json(); // 更新进度 document.getElementById('scan-status').textContent = task.status; document.getElementById('scan-progress').textContent = task.progress + '%'; document.getElementById('progress-fill').style.width = task.progress + '%'; // 更新设备列表 updateDeviceList(task.devices); // 如果完成,更新拓扑 if (task.status === 'completed' || task.status === 'failed') { loadTopology(); loadDeviceList(); // 刷新设备列表 currentTaskId = null; return; } // 继续轮询 setTimeout(poll, 1000); } catch (error) { console.error('获取进度失败:', error); } }; poll(); } // 更新设备列表 function updateDeviceList(devices) { const listContainer = document.getElementById('device-list'); listContainer.innerHTML = ''; devices.forEach(device => { const item = document.createElement('div'); item.className = 'device-item'; item.innerHTML = `
${device.ip}
${device.type} - ${device.hostname || 'Unknown'}
${device.scan_status}
`; item.addEventListener('click', () => showDeviceDetail(device.id)); listContainer.appendChild(item); }); } // 加载拓扑 async function loadTopology() { try { const response = await fetch('/api/topology'); const graph = await response.json(); // 清空现有元素 cy.elements().remove(); // 添加节点 graph.nodes.forEach(node => { // 显示格式: 主机名 + IP地址 let label = node.ip; // 默认显示IP if (node.hostname && node.hostname !== '') { label = `${node.hostname}\n${node.ip}`; } cy.add({ group: 'nodes', data: { id: node.id, label: label, type: node.type, ip: node.ip, hostname: node.hostname } }); }); // 添加边 graph.edges.forEach(edge => { cy.add({ group: 'edges', data: { id: edge.id, source: edge.source, target: edge.target, protocol: edge.protocol } }); }); // 重新布局 cy.layout({ name: 'cose', animate: true, animationDuration: 1000, padding: 30 }).run(); cy.fit(40); } catch (error) { console.error('加载拓扑失败:', error); } } // 加载设备列表 async function loadDeviceList() { try { const response = await fetch('/api/devices'); const devices = await response.json(); const listContainer = document.getElementById('device-list'); listContainer.innerHTML = ''; if (devices.length === 0) { listContainer.innerHTML = '

暂无设备

'; return; } devices.forEach(device => { const item = document.createElement('div'); item.className = 'device-item'; const interfaceCount = device.interfaces ? device.interfaces.length : 0; const neighborCount = device.neighbors ? device.neighbors.length : 0; item.innerHTML = `
${device.ip}
${device.type} - ${device.hostname || 'Unknown'}
接口: ${interfaceCount} | 邻居: ${neighborCount}
${device.scan_status || 'pending'}
`; item.addEventListener('click', () => showDeviceDetail(device.id || device.ip)); listContainer.appendChild(item); }); } catch (error) { console.error('加载设备列表失败:', error); } } // 显示设备详情 async function showDeviceDetail(deviceId) { try { const response = await fetch(`/api/device/${deviceId}`); const device = await response.json(); const detailPanel = document.getElementById('detail-panel'); const detailContainer = document.getElementById('device-detail'); detailContainer.innerHTML = `

基本信息

IP: ${device.ip}

主机名: ${device.hostname || 'N/A'}

类型: ${device.type}

系统: ${device.os_version || 'N/A'}

运行时间: ${device.uptime || 'N/A'}

接口信息 (${device.interfaces.length})

${device.interfaces.map(iface => `
${iface.name}

状态: ${iface.status}

IP: ${iface.ip || 'N/A'}

MAC: ${iface.mac || 'N/A'}

速度: ${iface.speed || 'N/A'}

`).join('')}

邻居设备 (${device.neighbors.length})

${device.neighbors.map(neighbor => `
${neighbor.remote_device}

本地接口: ${neighbor.local_interface}

远程接口: ${neighbor.remote_interface}

协议: ${neighbor.protocol}

`).join('')}
`; detailPanel.classList.add('active'); } catch (error) { console.error('获取设备详情失败:', error); } } // 添加设备 async function addDevice(event) { event.preventDefault(); const ip = document.getElementById('device-ip').value; const type = document.getElementById('device-type').value; const username = document.getElementById('device-username').value; const password = document.getElementById('device-password').value; try { const response = await fetch('/api/device', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ip: ip, type: type, username: username, password: password }) }); if (response.ok) { document.getElementById('modal').classList.remove('active'); document.getElementById('add-device-form').reset(); loadTopology(); loadDeviceList(); // 刷新设备列表 alert('设备添加成功'); } else { const error = await response.json(); alert('添加失败: ' + error.message); } } catch (error) { console.error('添加设备失败:', error); alert('添加失败: ' + error.message); } } // 导出拓扑 function exportTopology() { const json = cy.json(); const dataStr = JSON.stringify(json, null, 2); const dataBlob = new Blob([dataStr], { type: 'application/json' }); const link = document.createElement('a'); link.href = URL.createObjectURL(dataBlob); link.download = 'topology.json'; link.click(); }