| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- // 全局变量
- let cy = null;
- let currentTaskId = null;
- // 初始化
- document.addEventListener('DOMContentLoaded', function() {
- initCytoscape();
- initEventListeners();
- loadTopology();
- });
- // 初始化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();
- 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 = `
- <div class="ip">${device.ip}</div>
- <div class="type">${device.type} - ${device.hostname || 'Unknown'}</div>
- <div class="status status-${device.scan_status.replace(' ', '-')}">${device.scan_status}</div>
- `;
- 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 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 = `
- <div class="detail-section">
- <h4>基本信息</h4>
- <p><strong>IP:</strong> ${device.ip}</p>
- <p><strong>主机名:</strong> ${device.hostname || 'N/A'}</p>
- <p><strong>类型:</strong> ${device.type}</p>
- <p><strong>系统:</strong> ${device.os_version || 'N/A'}</p>
- <p><strong>运行时间:</strong> ${device.uptime || 'N/A'}</p>
- </div>
- <div class="detail-section">
- <h4>接口信息 (${device.interfaces.length})</h4>
- ${device.interfaces.map(iface => `
- <div class="interface-item">
- <div class="name">${iface.name}</div>
- <div class="info">
- <p>状态: <span class="status-${iface.status.replace(' ', '-')}">${iface.status}</span></p>
- <p>IP: ${iface.ip || 'N/A'}</p>
- <p>MAC: ${iface.mac || 'N/A'}</p>
- <p>速度: ${iface.speed || 'N/A'}</p>
- </div>
- </div>
- `).join('')}
- </div>
- <div class="detail-section">
- <h4>邻居设备 (${device.neighbors.length})</h4>
- ${device.neighbors.map(neighbor => `
- <div class="interface-item">
- <div class="name">${neighbor.remote_device}</div>
- <div class="info">
- <p>本地接口: ${neighbor.local_interface}</p>
- <p>远程接口: ${neighbor.remote_interface}</p>
- <p>协议: ${neighbor.protocol}</p>
- </div>
- </div>
- `).join('')}
- </div>
- `;
- 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();
- 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();
- }
|