本文实现的方法可以边异步加载数据边绘制拓扑图。主流程很简单: 发送 AJAX 请求获取数据 —> 创建节点(实际上就是DIV) —> 计算节点位置、布局 —> 添加节点附着点 —> 缓存节点连接 —> 连接所有现有的缓存节点连接。 多个 AJAX 请求的处理是异步的, 顺序没有控制。

        本文实现的方法可以边异步加载数据边绘制拓扑图。 有若干点需要说明一下:

        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":"it-cloudpc@alibaba-inc.com","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 }  

 

版权声明:本文为lovesqcc原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/lovesqcc/p/4037689.html