本文实现的方法可以边异步加载数据边绘制拓扑图。 有若干点需要说明一下:
1. 一次性获取所有数据并绘制拓扑图, 请参见文章: <使用 JsPlumb 绘制拓扑图的通用方法> ; 本文实现的最终显示效果与之类似, 所使用的基本方法与之类似。
2. 在此次实现中, 可以一边异步加载数据一边绘制拓扑图, 是动态可扩展的;
3. 所有影响节点位置、布局的配置均放置在最前面, 便于修改, 避免在代码中穿梭, 浪费时间;
4. 布局算法比之前的实现更加完善;
5. 此实现由于与业务逻辑绑得比较紧, 可复用的部分不多, 但是可以作为一个模板, 用在读者自己的场景中, 自行修改相应的节点类型、URL等。
6. 添加了附着点的点击事件处理, 可以刷新显示关联实体;
7. 主流程很简单: 发送 AJAX 请求获取数据 ---> 创建节点(实际上就是DIV) ---> 计算节点位置、布局 ---> 添加节点附着点 ---> 缓存节点连接 ---> 连接所有现有的缓存节点连接。 多个 AJAX 请求的处理是异步的, 顺序没有控制。
8. 代码:
1 /** 2 * 使用 jsPlumb 根据指定的拓扑数据结构绘制拓扑图 3 * 使用 drawTopo_asyn(vmName, regionNo, parentDivId) 方法 4 */ 5 /** 6 * 初始化拓扑图实例及外观设置 7 */ 8 (function() { 9 10 jsPlumb.importDefaults({ 11 12 DragOptions : { cursor: 'pointer', zIndex:2000 }, 13 14 EndpointStyles : [{ fillStyle:'#225588' }, { fillStyle:'#558822' }], 15 16 Endpoints : [ [ "Dot", { radius:2 } ], [ "Dot", { radius: 2 } ]], 17 18 ConnectionOverlays : [ 19 [ "Label", { location:1 } ], 20 [ "Label", { 21 location:0.1, 22 id:"label", 23 cssClass:"aLabel" 24 }] 25 ] 26 }); 27 28 var connectorPaintStyle = { 29 lineWidth: 1, 30 strokeStyle: "#096EBB", 31 joinstyle:"round", 32 outlineColor: "#096EBB", 33 outlineWidth: 1 34 }; 35 36 var connectorHoverStyle = { 37 lineWidth: 2, 38 strokeStyle: "#5C96BC", 39 outlineWidth: 2, 40 outlineColor:"white" 41 }; 42 43 var endpointHoverStyle = { 44 fillStyle:"#5C96BC" 45 }; 46 47 window.topoDrawUtil = { 48 49 sourceEndpoint: { 50 endpoint:"Dot", 51 paintStyle:{ 52 strokeStyle:"#1e8151", 53 fillStyle:"transparent", 54 radius: 4, 55 lineWidth:2 56 }, 57 isSource:true, 58 maxConnections:-1, 59 connector:[ "Flowchart", { stub:[40, 60], gap:1, cornerRadius:5, alwaysRespectStubs:true } ], 60 connectorStyle: connectorPaintStyle, 61 hoverPaintStyle: endpointHoverStyle, 62 connectorHoverStyle: connectorHoverStyle, 63 dragOptions:{}, 64 overlays:[ 65 [ "Label", { 66 location:[0.5, 1.5], 67 label:"", 68 cssClass:"endpointSourceLabel" 69 } ] 70 ] 71 }, 72 73 targetEndpoint: { 74 endpoint: "Dot", 75 paintStyle: { fillStyle:"#1e8151",radius: 2 }, 76 hoverPaintStyle: endpointHoverStyle, 77 maxConnections:-1, 78 dropOptions:{ hoverClass:"hover", activeClass:"active" }, 79 isTarget:true, 80 overlays:[ 81 [ "Label", { location:[0.5, -0.5], label:"", cssClass:"endpointTargetLabel" } ] 82 ] 83 }, 84 85 initConnection: function(connection) { 86 connection.getOverlay("label").setLabel(connection.sourceId + "-" + connection.targetId); 87 connection.bind("editCompleted", function(o) { 88 if (typeof console != "undefined") 89 console.log("connection edited. path is now ", o.path); 90 }); 91 }, 92 93 removeAllEndPoints: function(nodeDivId) { 94 jsPlumb.removeAllEndpoints($('#'+nodeDivId)); 95 }, 96 addEndpoints: function(toId, sourceAnchors, targetAnchors) { 97 for (var i = 0; i < sourceAnchors.length; i++) { 98 var sourceUUID = toId + sourceAnchors[i]; 99 var endPoint = jsPlumb.addEndpoint(toId, this.sourceEndpoint, { anchor:sourceAnchors[i], uuid:sourceUUID }); 100 endPoint.bind("click", function(endpoint) { 101 var anchorType = endpoint.anchor.type; 102 var nodeType = toId.split('_')[0]; 103 var content = toId.split('_')[1]; 104 if (nodeType == VM_TYPE) { 105 switch (anchorType) { 106 case 'Right': 107 cacheKey = 'VM-DEVICE-'+ vmNodeData.key; 108 cacheConnectionData[cacheKey] = null; 109 linkDevices(vmNodeData, vmNodeData.key); 110 break; 111 case 'Top': 112 cacheKey = 'VM-ACCOUNT-'+ vmNodeData.key; 113 cacheConnectionData[cacheKey] = null; 114 vmName = vmNodeData.key; 115 regionNo = vmNodeData.data.region_no; 116 linkAccount(vmNodeData, vmName, regionNo); 117 break; 118 case 'Bottom': 119 cacheKey = 'VM-NC-'+ vmNodeData.key; 120 cacheConnectionData[cacheKey] = null; 121 ncId = vmNodeData.data.nc_id; 122 regionNo = vmNodeData.data.region_no; 123 linkNc(vmNodeData, ncId, regionNo); 124 break; 125 case 'Left': 126 cacheKey = 'VM-VIP-'+ vmNodeData.key; 127 cacheConnectionData[cacheKey] = null; 128 vmInnerIp = vmNodeData.data.vm_inner_ip; 129 linkVips(vmNodeData, vmInnerIp); 130 break; 131 default: 132 break; 133 } 134 } 135 else if (nodeType == DEVICE_TYPE) { 136 if (anchorType == 'Bottom') { 137 cacheKey = 'DEVICE-SNAPSHOT-'+ content; 138 cacheConnectionData[cacheKey] = null; 139 deviceNodeData = deviceNodeDataMapping[content]; 140 linkSnapshot(deviceNodeData.data.aliUid, content, deviceNodeData); 141 } 142 } 143 }); 144 } 145 for (var j = 0; j < targetAnchors.length; j++) { 146 var targetUUID = toId + targetAnchors[j]; 147 jsPlumb.addEndpoint(toId, this.targetEndpoint, { anchor:targetAnchors[j], uuid:targetUUID }); 148 } 149 } 150 }; 151 })(); 152 ////////////////////////////////////////////////////////////////////////////// 153 // 这里将所有用到的数据结构汇聚在这里, 避免修改时需要在代码中穿行, 浪费时间 154 /** 155 * 重新刷新VM关联实体时需要使用到VM的信息,这里进行全局缓存,避免重复查询 156 * 重新刷新VM关联实体时VM必定存在, vmNodeData 也必定是最近一次查询的结果 157 */ 158 var vmNodeData = {}; 159 /** 160 * 重新刷新磁盘关联快照实体时需要使用到磁盘的信息,这里进行全局缓存,避免重复查询 161 * 重新刷新磁盘关联快照实体时磁盘必定存在,且必定是最近一次查询的结果 162 * eg. {'instanceId': { "ecsInstanceId": "vmName", "houyiDiskId": "102-80012003", 163 "aliUid": aliUidNum, "instanceId": "d-28ilj8rsf", ... }} 164 */ 165 var deviceNodeDataMapping = {}; 166 /** 167 * 拓扑图中的节点类型 168 */ 169 var nodeTypeArray = ['VM', 'DEVICE', 'NC', 'VIP', 'SNAPSHOT', 'CLUSTER', 'AVZ', 'ACCOUNT']; 170 var VM_TYPE = nodeTypeArray[0]; 171 var DEVICE_TYPE = nodeTypeArray[1]; 172 var NC_TYPE = nodeTypeArray[2]; 173 var VIP_TYPE = nodeTypeArray[3]; 174 var SNAPSHOT_TYPE= nodeTypeArray[4]; 175 var CLUSTER_TYPE= nodeTypeArray[5]; 176 var AVZ_TYPE= nodeTypeArray[6]; 177 var ACCOUNT_TYPE= nodeTypeArray[7]; 178 /** 179 * cacheConnectionData 节点之间的已有连接数目缓存, 在计算节点位置及布局方法 computeLayout 中用到 180 * eg. { 181 * 'VM-DEVICE-vmkey': 2, 'VM-NC-vmkey':1, 'VM-VIP-vmkey':2 , 'VM-ACCOUNT-vmkey': 1, 182 * } 183 * 表示已经有2磁盘/1NC/2VIP/1ACCOUNT 与VM(key 为 vmkey)连接, 这些信息用于计算与VM相连接的同类型的下一个实体的相对位置 184 */ 185 var cacheConnectionData = {}; 186 /** 187 * 连接关系的存储, 在 cacheConnections , reconnectAll 方法中用到 188 * 由于重复节点会移动到新的位置,原有连接会出现断连现象, 因此采用"每次刷新拉取新实体时重连所有连线" 的策略, 可以保证实时性, 只要连接数不多重复连接的开销是可以接受的. 189 * connections = [[startPoint1, endPoint1], [startPoint2, endPoint2], ..., [startPointN, endPointN]]; 190 */ 191 var connections = []; 192 /** 193 * 实体与实体上附着点方向的设置 194 * DEVICE_TYPE: [['Right', 'Bottom'], ['Left']] 的含义是: 195 * 对于DEVICE实体: 作为起始节点时, 附着点可以在右方中点(连接CLUSTER), 下方中点(连接快照); 作为终止节点时, 附着点仅在左方中点(连接VM) 196 */ 197 var entityEndPointsMapping = { 198 "VM": [['Top', 'Bottom', 'Right', 'Left'], []], 199 "DEVICE": [['Right', 'Bottom'], ['Left']], 200 "NC": [['Bottom'], ['Top']], 201 "VIP": [[], ['Right']], 202 "SNAPSHOT": [[], ['Top']], 203 "CLUSTER": [[], ['Left', 'Top']], 204 "AVZ": [['Bottom'], ['Top']], 205 "ACCOUNT": [[], ['Bottom']] 206 }; 207 /** 208 * 连接线附着点方向设置 209 * "VM-ACCOUNT": ['Top', 'Bottom'] 的含义是: 210 * VM 的上方附着点 与 ACCOUNT 的下方附着点的连接 211 */ 212 var connectionDirectionMapping = { 213 "VM-ACCOUNT": ['Top', 'Bottom'], 214 "VM-NC": ['Bottom', 'Top'], 215 "NC-CLUSTER": ['Bottom', 'Top'], 216 "VM-DEVICE": ['Right', 'Left'], 217 "DEVICE-CLUSTER": ['Right', 'Left'], 218 "VM-VIP": ['Left', 'Right'], 219 "DEVICE-SNAPSHOT": ['Bottom', 'Top'] 220 } 221 /** 222 * 节点之间的水平与垂直相对位置 223 */ 224 var largeVerticalDistance = 270; 225 var verticalDistance = 220; 226 var horizontalDistance = 300; 227 var shortVerticalDistance = 50; 228 var shortHorizontalDistance = 220; 229 /** 230 * 节点之间的水平或垂直相对位置和距离的设置 231 * "VM-DEVICE": [largeVerticalDistance, horizontalDistance] 232 */ 233 var connectionDistanceMapping = { 234 "VM-ACCOUNT": [-verticalDistance, 0], 235 "VM-NC": [shortVerticalDistance, 0], 236 "NC-CLUSTER": [shortVerticalDistance, 0], 237 "VM-DEVICE": [largeVerticalDistance, horizontalDistance], 238 "DEVICE-CLUSTER": [-108, horizontalDistance], 239 "VM-VIP": [verticalDistance, -horizontalDistance], 240 "DEVICE-SNAPSHOT": [shortVerticalDistance, shortHorizontalDistance] 241 } 242 /** 243 * 根节点位置 244 */ 245 rootPosition = [220, 360]; 246 rootTop = rootPosition[0]; 247 rootLeft = rootPosition[1]; 248 var parentDiv = null; 249 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// 250 function drawtopo_asyn(vmName, regionNo, parentDivId) { 251 252 parentDiv = $('#'+parentDivId); 253 254 var vmInfoReq = { 255 'url': httpPrefix + '/controllers/vm/obtainVmData', 256 'params' : { 257 'vm_name' : vmName, 258 'region_no': regionNo 259 } 260 }; 261 var vmjq = doAjaxRequest(vmInfoReq); 262 var vmInfoLoadedAfter = function(resultJson) { 263 264 vmNodeData = resultJson.result.data; 265 if (vmNodeData == null) { 266 alert('没有找到VM的相关信息!'); 267 return ; 268 } 269 vmNode = createDiv(vmNodeData); 270 271 vmDivId = obtainNodeDivId(vmNodeData); 272 $('#'+vmDivId).css('top', rootTop + 'px'); 273 $('#'+vmDivId).css('left', rootLeft + 'px'); 274 275 linkAccount(vmNodeData, vmName, regionNo); 276 277 ncId = vmNodeData.data.nc_id; 278 linkNc(vmNodeData, ncId, regionNo); 279 280 // vmName = 'ATX-28n2dhdq8'; 281 linkDevices(vmNodeData, vmName); 282 283 vmInnerIp = vmNodeData.data.vm_inner_ip; 284 linkVips(vmNodeData, vmInnerIp); 285 286 }; 287 vmjq.done(vmInfoLoadedAfter); 288 } 289 function linkAccount(vmNodeData, vmName, regionNo) { 290 var accountInfoReq = { 291 'url': httpPrefix + '/controllers/vm/obtainAliyunAccountInfo', 292 'params': { 293 'vm_name' : vmName, 294 'region_no': regionNo 295 } 296 }; 297 var accountjq = doAjaxRequest(accountInfoReq); 298 accountjq.done(function(resultJson) { 299 300 // for test 301 // resultJson = {"result":{"msg":"successful","code":200,"data":{"errorCode":null,"errorMsg":null,"aliyunID":"[email protected]","kp":null},"success":true}}; 302 303 if (resultJson.result.success) { 304 accountData = resultJson.result.data; 305 accountNodeData = createAccountData(accountData); 306 accountNode = createDiv(accountNodeData); 307 cacheConnections(vmNodeData, accountNodeData, obtainConnectionDirections(vmNodeData, accountNodeData)); 308 reconnectAll(connections); 309 } 310 else { 311 $('#error').append('获取关联云账号信息失败!'); 312 } 313 }).fail(function() { 314 $('#error').append('获取关联云账号信息失败! '); 315 }); 316 317 } 318 function linkNc(vmNodeData, ncId, regionNo) { 319 var ncInfoReq = { 320 'url': httpPrefix + '/controllers/nc/listNc', 321 'params': { 322 'region_no': regionNo, 323 'nc_id': ncId, 324 'start': 0, 325 'page': 1, 326 'limit': 1 327 } 328 }; 329 var ncjq = doAjaxRequest(ncInfoReq); 330 ncjq.done(function(resultJson) { 331 ncDataList = resultJson.data; 332 if (ncDataList.length > 0) { 333 ncData = ncDataList[0]; 334 ncNodeData = createNcData(ncData); 335 ncNode = createDiv(ncNodeData); 336 cacheConnections(vmNodeData, ncNodeData, obtainConnectionDirections(vmNodeData, ncNodeData)); 337 338 ncClusterNodeData = createNcClusterData(ncData); 339 ncClusterNode = createDiv(ncClusterNodeData); 340 cacheConnections(ncNodeData, ncClusterNodeData, obtainConnectionDirections(ncNodeData, ncClusterNodeData)); 341 reconnectAll(connections); 342 } 343 else { 344 $('#error').append('获取关联NC实体失败!'); 345 } 346 }).fail(function() { 347 $('#error').append('获取关联NC实体失败!'); 348 }); 349 } 350 function linkDevices(vmNodeData, vmName) { 351 var deviceInfoReq = { 352 'url' : httpPrefix + '/controllers/disk/search', 353 'params': { 354 'vmName': vmName 355 } 356 } 357 var regionPeNickName = vmNodeData.data.region_pe_nickname; 358 var devicejq = doAjaxRequest(deviceInfoReq); 359 devicejq.done(function(resultJson) { 360 361 total = resultJson.data.total; 362 if (total > 0) { 363 devices = resultJson.data.list; 364 365 for (var i=0; i<total; i++) { 366 367 deviceData = devices[i]; 368 deviceData['regionPeNickName'] = regionPeNickName; 369 deviceNodeData = createDeviceData(deviceData); 370 deviceNodeDataMapping[deviceData.instanceId] = deviceNodeData; 371 deviceNode = createDiv(deviceNodeData); 372 cacheConnections(vmNodeData, deviceNodeData, obtainConnectionDirections(vmNodeData, deviceNodeData)); 373 374 deviceClusterNodeData = createDeviceClusterData(deviceData); 375 deviceClusterNode = createDiv(deviceClusterNodeData); 376 cacheConnections(deviceNodeData, deviceClusterNodeData, obtainConnectionDirections(deviceNodeData,deviceClusterNodeData)); 377 linkSnapshot(devices[i].aliUid, devices[i].instanceId, deviceNodeData); 378 } 379 reconnectAll(connections); 380 } 381 else { 382 $('#error').append('该VM没有关联的磁盘实体!'); 383 } 384 }).fail(function() { 385 $('#error').append('获取关联磁盘实体失败!'); 386 }); 387 } 388 function linkVips(vmNodeData, vmInnerIp) { 389 390 var vipInfoReq = { 391 'url': httpPrefix + '/controllers/slbvip/listVip', 392 'params': { 393 'realserver_param': vmInnerIp, 394 'start': 0, 395 'page': 1, 396 'limit': 100 397 } 398 }; 399 var vipjq = doAjaxRequest(vipInfoReq); 400 vipjq.done(function(resultJson) { 401 402 total = resultJson.total; 403 vips = resultJson.data; 404 if (total > 0) { 405 for (j=0; j<total; j++) { 406 var vipInfo = vips[j]; 407 vipNodeData = createVipData(vipInfo); 408 vipNode = createDiv(vipNodeData); 409 cacheConnections(vmNodeData, vipNodeData, obtainConnectionDirections(vmNodeData,vipNodeData)); 410 } 411 reconnectAll(connections); 412 } 413 else { 414 $('#error').append('该VM没有关联的VIP实体!'); 415 } 416 }).fail(function() { 417 $('#error').append('获取关联VIP实体失败!'); 418 }); 419 420 } 421 function linkSnapshot(aliUid, diskId, deviceNodeData) { 422 423 var snapshotInfoReq = { 424 'url': httpPrefix + '/controllers/snapshot/search', 425 'params': { 426 'aliUid': aliUid, 427 'diskId': diskId 428 } 429 }; 430 var snapshotjq = doAjaxRequest(snapshotInfoReq); 431 snapshotjq.done(function(resultJson) { 432 433 total = resultJson.total; 434 if (total > 0) { 435 snapshotDataList = resultJson.list; 436 for (k=0; k<total; k++) { 437 snapshotData = snapshotDataList[k]; 438 snapshotNodeData = createSnapshotData(snapshotData); 439 snapshotNode = createDiv(snapshotNodeData); 440 cacheConnections(deviceNodeData, snapshotNodeData, obtainConnectionDirections(deviceNodeData, snapshotNodeData)); 441 } 442 reconnectAll(connections); 443 } 444 else { 445 $('#error').append('磁盘 ' + diskId + ' 没有关联的快照实体!'); 446 } 447 }).fail(function() { 448 $('#error').append('磁盘' + diskId + ' 获取关联快照实体失败!'); 449 }); 450 } 451 /** 452 * createXXXData 453 * 创建拓扑图所使用的节点数据 454 */ 455 function createVmData(vmData) { 456 return { 457 'type': 'VM', 458 'key': vmData.vm_name, 459 'data': vmData 460 } 461 } 462 function createNcData(ncData) { 463 return { 464 'type': 'NC', 465 'key': ncData.ip, 466 'data': ncData 467 } 468 } 469 function createNcClusterData(ncData) { 470 return { 471 'type': 'CLUSTER', 472 'key': ncData.regionPeNickName, 473 'data': { 474 'regionNo': ncData.regionNo, 475 'regionNickName': ncData.regionNickName, 476 'regionPeNickName': ncData.regionPeNickName 477 } 478 } 479 } 480 function createDeviceData(deviceData) { 481 return { 482 'type': 'DEVICE', 483 'key': deviceData.instanceId, 484 'data': deviceData 485 } 486 } 487 function createDeviceClusterData(deviceData) { 488 return { 489 'type': 'CLUSTER', 490 'key': deviceData.regionNo, 491 'data': { 492 'regionNo': deviceData.regionNo 493 } 494 } 495 } 496 function createSnapshotData(snapshotData) { 497 return { 498 'type': 'SNAPSHOT', 499 'key': snapshotData.snapshotId, 500 'data': snapshotData 501 } 502 } 503 function createSnapshotClusterData(snapshotData) { 504 return { 505 'type': 'CLUSTER', 506 'key': snapshotData.regionNo, 507 'data': { 508 'regionNo': snapshotData.regionNo 509 } 510 } 511 } 512 function createVipData(vipData) { 513 return { 514 'type': 'VIP', 515 'key': vipData.vipAddress, 516 'data': vipData 517 } 518 } 519 function createAccountData(accountData) { 520 return { 521 'type': 'ACCOUNT', 522 'key': accountData.aliyunID, 523 'data': accountData 524 } 525 } 526 /** 527 * 缓存起始节点 beginNode 和终止节点 endNode 的连接关系 528 */ 529 function cacheConnections(beginNode, endNode, directions) { 530 531 computeLayout(beginNode, endNode); 532 533 var startPoint = obtainNodeDivId(beginNode) + directions[0]; 534 var endPoint = obtainNodeDivId(endNode) + directions[1]; 535 connections.push([startPoint, endPoint]); 536 } 537 /** 538 * 计算节点位置及布局 539 */ 540 function computeLayout(beginNode, endNode) { 541 542 var beginDivId = obtainNodeDivId(beginNode); 543 var endDivId = obtainNodeDivId(endNode); 544 var beginNodeType = beginNode.type; 545 var endNodeType = endNode.type; 546 547 beginNodeTop = $('#'+beginDivId).offset().top; 548 beginNodeLeft = $('#'+beginDivId).offset().left; 549 550 var key = beginNodeType + '-' + endNodeType + '-' + beginNode.key; 551 if (cacheConnectionData[key] == null) { 552 cacheConnectionData[key] = -1; 553 } 554 else { 555 cacheConnectionData[key] = cacheConnectionData[key]+1; 556 } 557 connNum = cacheConnectionData[key]; 558 559 var typeKey = beginNodeType + '-' + endNodeType; 560 var relDistance = connectionDistanceMapping[typeKey]; 561 var relVertiDistance = relDistance[0]; 562 var relHoriDistance = relDistance[1]; 563 564 switch (beginNodeType) { 565 case VM_TYPE: 566 if (endNodeType == VIP_TYPE) { 567 endNodePosition = [beginNodeTop+connNum*relVertiDistance, beginNodeLeft+relHoriDistance]; 568 } 569 else if (endNodeType == DEVICE_TYPE) { 570 endNodePosition = [beginNodeTop+connNum*relVertiDistance, beginNodeLeft+relHoriDistance]; 571 } 572 else if (endNodeType == NC_TYPE) { 573 endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+relHoriDistance]; 574 } 575 else if (endNodeType == ACCOUNT_TYPE) { 576 endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+relHoriDistance]; 577 } 578 break; 579 case DEVICE_TYPE: 580 if (endNodeType == CLUSTER_TYPE) { 581 endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+relHoriDistance]; 582 } 583 else if (endNodeType == SNAPSHOT_TYPE) { 584 endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+(connNum+1)*relHoriDistance]; 585 } 586 break; 587 case VIP_TYPE: 588 break; 589 case NC_TYPE: 590 if (endNodeType == CLUSTER_TYPE) { 591 endNodePosition = [beginNodeTop+relVertiDistance, beginNodeLeft+relHoriDistance]; 592 } 593 break; 594 case SNAPSHOT_TYPE: 595 default: 596 break; 597 } 598 599 $('#'+endDivId).css('top', endNodePosition[0] + 'px'); 600 $('#'+endDivId).css('left', endNodePosition[1] + 'px'); 601 602 addEndPoints(beginDivId, beginNodeType); 603 addEndPoints(endDivId, endNodeType); 604 } 605 /** 606 * 为节点添加用于连线的附着点 607 * @param nodeDivId 节点的 DIV ID 608 * @param type 节点类型 609 */ 610 function addEndPoints(nodeDivId, type) { 611 var startAttachedPoints = entityEndPointsMapping[type][0]; 612 var endAttachedPoints = entityEndPointsMapping[type][1]; 613 topoDrawUtil.addEndpoints(nodeDivId, startAttachedPoints, endAttachedPoints); 614 } 615 /** 616 * 连接所有连线 617 */ 618 function reconnectAll(connections) { 619 620 var i=0; 621 for (i=0; i<connections.length; i++) { 622 jsPlumb.connect({uuids:connections[i], editable: false}); 623 } 624 // 使所有拓扑节点均为可拉拽的 625 jsPlumb.draggable(jsPlumb.getSelector(".node"), { grid: [5, 5] }); 626 } 627 /** 628 * div id cache , avoid duplicated div. 629 * {'divId': 'divStr'} 630 */ 631 divIdCache = {}; 632 /** 633 * 为节点数据创建节点\附着点并返回节点的DIV 634 */ 635 function createDiv(metaNode) { 636 var clickUrl = ''; 637 var display = ''; 638 var type = metaNode.type; 639 var regionPeNickname = ''; 640 if (metaNode.data != null) { 641 regionPeNickname = metaNode.data.regionPeNickName; 642 } 643 644 nodeDivId = obtainNodeDivId(metaNode); 645 646 if (divIdCache[nodeDivId] != null) { 647 // 该节点要移动到新的位置, 因此原来的附着点要去掉 648 topoDrawUtil.removeAllEndPoints(nodeDivId); 649 return divIdCache[nodeDivId]; 650 } 651 652 switch(type.toUpperCase()) { 653 case VM_TYPE: 654 clickUrl = httpPrefix + '/framehtml/vm_monitor.html?vm_name=' + metaNode.key + '&data='+JSON.stringify(metaNode.data).replace(/\"/g,"'"); 655 display = metaNode.key; 656 break; 657 case DEVICE_TYPE: 658 displayDevice1 = metaNode.data.instanceId; 659 clickDeviceUrl2 = httpPrefix + '/framehtml/device_monitor.html?device_id=' + metaNode.data.houyiDiskId + '®ion_pe_nickname='+regionPeNickname; 660 displayDevice2 = metaNode.data.houyiDiskId; 661 break; 662 case NC_TYPE: 663 var regionNo = metaNode.data.regionNo; 664 clickUrl = httpPrefix + '/framehtml/nc_monitor.html?nc_ip=' + metaNode.key + '®ion_pe_nickname='+regionPeNickname + '®ion_no='+regionNo; 665 display = metaNode.key; 666 break; 667 case VIP_TYPE: 668 display = metaNode.key + ':' + metaNode.data.port; 669 clickUrl = httpPrefix + '/framehtml/vip_monitor.html?vip=' + display + '®ion_pe_nickname='+regionPeNickname + '&slbdb_configId='+metaNode.data.slbdb_configId; 670 break; 671 case SNAPSHOT_TYPE: 672 display = metaNode.key + '<br/>' + metaNode.data.houyiSnapshotId + '<br/><span style="color:green">'+ metaNode.data.regionNo + '</span>'; 673 break; 674 case CLUSTER_TYPE: 675 case AVZ_TYPE: 676 case ACCOUNT_TYPE: 677 display = metaNode.key; 678 break; 679 default: 680 break; 681 } 682 683 if (type == VM_TYPE || type == NC_TYPE || type == VIP_TYPE || type == ACCOUNT_TYPE ) { 684 divStr = '<div class="node biggerNode" id="' + nodeDivId + '"><strong>' 685 + metaNode.type + '<br/><a href="' + clickUrl + '" target="_blank">' + display + '</a><br/></strong></div>'; 686 } 687 else if (type == DEVICE_TYPE){ 688 divStr = '<div class="node biggerNode" id="' + nodeDivId + '"><strong>' 689 + metaNode.type + '<br/>' + displayDevice1 + '<br/><a href="' + clickDeviceUrl2 + '" target="_blank">' + displayDevice2 + '</a><br/></strong></div>'; 690 } 691 else { 692 divStr = '<div class="node biggerNode" id="' + nodeDivId + '"><strong>' 693 + metaNode.type + '<br/>' + display + '<br/></strong></div>'; 694 } 695 parentDiv.append(divStr); 696 697 divIdCache[nodeDivId] = divStr; 698 return divStr; 699 } 700 function obtainConnectionDirections(srcNodeData, destNodeData) { 701 var key = srcNodeData.type + '-' + destNodeData.type; 702 var startDirection = connectionDirectionMapping[key][0]; 703 var endDirection = connectionDirectionMapping[key][1]; 704 return [startDirection, endDirection]; 705 } 706 /** 707 * 生成节点的 DIV id 708 * divId = nodeType.toUpperCase + "_" + key 709 * key 可能为 IP , 其中的 . 将被替换成 ZZZ , 因为 jquery id 选择器中 . 属于转义字符. 710 * eg. {type: 'VM', key: '1.1.1.1' }, divId = 'VM_1ZZZ1ZZZ1ZZZ1' 711 */ 712 function obtainNodeDivId(metaNode) { 713 if (metaNode.type == VIP_TYPE) { 714 return metaNode.type.toUpperCase() + '_' + transferKey(metaNode.key) + '_' + metaNode.data.port; 715 } 716 return metaNode.type.toUpperCase() + '_' + transferKey(metaNode.key); 717 } 718 function transferKey(key) { 719 return key.replace(/\./g, 'ZZZ').replace(/\@/g,'YYY'); 720 } 721 function revTransferKey(value) { 722 return value.replace(/ZZZ/g, '.').replace('/YYY/g','@'); 723 }