mirror of https://github.com/EdgeVPNio/portal.git
843 lines
28 KiB
JavaScript
843 lines
28 KiB
JavaScript
import React from "react";
|
|
import ReactDOM from "react-dom";
|
|
import Cytoscape from "react-cytoscapejs";
|
|
import CollapseButton from "./CustomCollapsibleButton";
|
|
import cytoscapeStyle from "./cytoscapeStyle.js";
|
|
import { Typeahead } from "react-bootstrap-typeahead";
|
|
import SideBar from "./Sidebar";
|
|
import { connect } from "react-redux";
|
|
import { setCyElements } from "../features/evio/evioSlice";
|
|
import {
|
|
setRedrawGraph,
|
|
} from "../features/evio/evioSlice";
|
|
import { setCurrentView } from "../features/view/viewSlice";
|
|
import { setZoomValue } from "../features/tools/toolsSlice";
|
|
|
|
const nodeStates = {
|
|
connected: "Connected",
|
|
noTunnels: "No Tunnels",
|
|
notReporting: "Not Reporting",
|
|
};
|
|
|
|
class TopologyView extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.isSwapToggle = false;
|
|
this.intervalId = null;
|
|
this.timeoutId = null;
|
|
this.autoRefresh = this.props.autoUpdate;
|
|
this.cy = null;
|
|
this._typeahead = null;
|
|
}
|
|
|
|
/**
|
|
* Polling function on GET Topology data - runs untill autoUpdate is disabled
|
|
* @param {String} overlayId
|
|
* @param {String} intervalId
|
|
*/
|
|
async apiQueryTopology(overlayId, intervalId) {
|
|
var url = "/topology?overlayid=" + overlayId + "&interval=" + intervalId;
|
|
var resp = await fetch(url).then((res) => {
|
|
return res.json();
|
|
});
|
|
console.log("apiQueryTopology: ", resp);
|
|
return resp;
|
|
}
|
|
|
|
queryTopology() {
|
|
if (this.autoRefresh)
|
|
this.apiQueryTopology(this.props.currentOverlayId, this.intervalId)
|
|
.then((res) => {
|
|
if (this.autoRefresh) {
|
|
this.props.setCyElements(this.buildCyElements(res[0].Topology));
|
|
//console.log("cyElements:", this.props.cyElements);
|
|
this.intervalId = res[0]._id;
|
|
this.queryTopology();
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
console.warn("query topology failed ", err);
|
|
if (this.autoRefresh) {
|
|
this.timeoutId = setTimeout(this.queryTopology.bind(this), 30000);
|
|
}
|
|
});
|
|
}
|
|
|
|
buildCyElements = (topologies) => {
|
|
var elements = [];
|
|
var nodeDetails = {};
|
|
|
|
if (topologies.length < 1) return elements;
|
|
var topology = topologies[0];
|
|
|
|
for (var nid in topology.Nodes) {
|
|
var node = topology.Nodes[nid];
|
|
var nodeData = {
|
|
group: "nodes",
|
|
data: {
|
|
id: node.NodeId,
|
|
},
|
|
};
|
|
if (node.hasOwnProperty("NodeName"))
|
|
nodeData["data"]["label"] = node.NodeName;
|
|
else nodeData["data"]["label"] = node.NodeId.slice(0, 12);
|
|
if (node.hasOwnProperty("Version"))
|
|
nodeData["data"]["version"] = node.Version;
|
|
else nodeData["data"]["version"] = "0.0.0";
|
|
if (node.hasOwnProperty("GeoCoordinates"))
|
|
nodeData["data"]["coords"] = node.GeoCoordinates;
|
|
else nodeData["data"]["coords"] = "0,0";
|
|
if (node.hasOwnProperty("Edges")) {
|
|
nodeData["data"]["edges"] = node.Edges;
|
|
if (node.Edges.length === 0) {
|
|
nodeData["data"]["state"] = nodeStates.noTunnels;
|
|
nodeData["data"]["color"] = "#F2BE22";
|
|
} else {
|
|
nodeData["data"]["state"] = nodeStates.connected;
|
|
nodeData["data"]["color"] = "#8AA626";
|
|
}
|
|
} else {
|
|
nodeData["data"]["state"] = nodeStates.notReporting;
|
|
nodeData["data"]["color"] = "#ADD8E6";
|
|
}
|
|
nodeDetails[node.NodeId] = nodeData;
|
|
}
|
|
for (var edgeId in topology.Edges) {
|
|
var edge = topology.Edges[edgeId];
|
|
if (edge["Descriptor"].length > 2) {
|
|
console.error(
|
|
"Too many edge descriptors reported ",
|
|
JSON.stringify(edge["Descriptor"])
|
|
);
|
|
}
|
|
var edgeData = {
|
|
group: "edges",
|
|
data: {},
|
|
};
|
|
edgeData["data"]["id"] = edge.EdgeId;
|
|
edgeData["data"]["descriptor"] = edge["Descriptor"];
|
|
edgeData["data"]["label"] = edge.EdgeId.slice(0, 12);
|
|
edgeData["data"]["source"] = edge["Descriptor"][0].Source;
|
|
edgeData["data"]["target"] = edge["Descriptor"][0].Target;
|
|
edgeData["data"]["color"] = this.getLinkColor(edge["Descriptor"][0].Type);
|
|
edgeData["data"]["style"] = this.getLinkStyle(
|
|
edge["Descriptor"][0].State
|
|
);
|
|
if (
|
|
edge["Descriptor"].length === 2 &&
|
|
edge["Descriptor"][0].Source > edge["Descriptor"][1].Source
|
|
) {
|
|
edgeData["data"]["source"] = edge["Descriptor"][1].Source;
|
|
edgeData["data"]["target"] = edge["Descriptor"][1].Target;
|
|
edgeData["data"]["color"] = this.getLinkColor(
|
|
edge["Descriptor"][1].Type
|
|
);
|
|
edgeData["data"]["style"] = this.getLinkStyle(
|
|
edge["Descriptor"][1].State
|
|
);
|
|
}
|
|
elements.push(edgeData);
|
|
}
|
|
var nodes = Object.keys(nodeDetails).sort();
|
|
nodes.forEach((nodeId) => elements.push(nodeDetails[nodeId]));
|
|
|
|
return elements;
|
|
};
|
|
|
|
getLinkColor(type) {
|
|
var linkColor;
|
|
switch (type) {
|
|
case "CETypeILongDistance":
|
|
linkColor = "#5E4FA2";
|
|
break;
|
|
case "CETypeLongDistance":
|
|
linkColor = "#5E4FA2";
|
|
break;
|
|
case "CETypePredecessor":
|
|
linkColor = "#01665E";
|
|
break;
|
|
case "CETypeSuccessor":
|
|
linkColor = "#01665E";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return linkColor;
|
|
}
|
|
|
|
getLinkStyle(state) {
|
|
var linkStyle;
|
|
switch (state) {
|
|
case "CEStateInitialized":
|
|
case "CEStatePreAuth":
|
|
case "CEStateAuthorized":
|
|
case "CEStateCreated":
|
|
linkStyle = "dotted";
|
|
break;
|
|
case "CEStateConnected":
|
|
linkStyle = "solid";
|
|
break;
|
|
case "CEStateDisconnected":
|
|
case "CEStateDeleting":
|
|
linkStyle = "dashed";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return linkStyle;
|
|
}
|
|
|
|
partitionElements(selectedElement) {
|
|
var neighborhood;
|
|
var excluded;
|
|
if (selectedElement.isNode()) {
|
|
neighborhood = selectedElement
|
|
.outgoers()
|
|
.union(selectedElement.incomers())
|
|
.union(selectedElement);
|
|
excluded = this.cy
|
|
.elements()
|
|
.difference(
|
|
selectedElement.outgoers().union(selectedElement.incomers())
|
|
)
|
|
.not(selectedElement);
|
|
let adj = selectedElement.neighborhood();
|
|
let abscomp = adj.absoluteComplement();
|
|
} else if (selectedElement.isEdge()) {
|
|
neighborhood = selectedElement.connectedNodes().union(selectedElement);
|
|
excluded = this.cy
|
|
.elements()
|
|
.difference(selectedElement.connectedNodes())
|
|
.not(selectedElement);
|
|
}
|
|
return { neighborhood, excluded };
|
|
}
|
|
|
|
async queryGeoCoordinates(coordinates) {
|
|
coordinates = coordinates.split(",");
|
|
if (coordinates.length < 2) return "Unknown";
|
|
try {
|
|
const res = await fetch(
|
|
`https://maps.googleapis.com/maps/api/geocode/json?latlng=${coordinates[0]},${coordinates[1]}&key=AIzaSyBjkkk4UyMh4-ihU1B1RR7uGocXpKECJhs&language=en`
|
|
);
|
|
var data = await res.json();
|
|
var nodeLocation =
|
|
data.results[data.results.length - 1].formatted_address;
|
|
return nodeLocation.slice(7, nodeLocation.length);
|
|
} catch (err) {
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
renderTypeahead() {
|
|
return (
|
|
<Typeahead
|
|
id="searchTopology"
|
|
onChange={(selected) => {
|
|
if (selected.length > 0) {
|
|
let selectedEle = this.cy
|
|
//.elements()
|
|
.getElementById(selected[0].data.id);
|
|
this.cy.elements().unselect();
|
|
selectedEle.select();
|
|
let part = this.partitionElements(selectedEle);
|
|
part.neighborhood.removeClass("transparent");
|
|
part.excluded.addClass("transparent");
|
|
}
|
|
}}
|
|
ref={(ref) => (this._typeahead = ref)}
|
|
options={this.props.cyElements}
|
|
placeholder={"search by node or tunnel ID"}
|
|
labelKey={(option) => {
|
|
return `${option.data.label}`;
|
|
}}
|
|
renderMenuItemChildren={(option) => {
|
|
return (
|
|
<div className="searchResult">
|
|
<div className="resultLabel">
|
|
<b>{option.data.label}</b>
|
|
</div>
|
|
<small className="resultLabel">{`ID : ${option.data.id}`}</small>
|
|
<br />
|
|
</div>
|
|
);
|
|
}}
|
|
></Typeahead>
|
|
);
|
|
}
|
|
|
|
getNotReportingNodeDetails(cyNode) {
|
|
var nodeContent = (
|
|
<CollapseButton title={cyNode.data().label}>
|
|
<div>
|
|
<h5>{cyNode.data().label}</h5>
|
|
<div className="DetailsLabel">Node ID</div>
|
|
<label id="valueLabel">{cyNode.data().id}</label>
|
|
<div className="DetailsLabel">State</div>
|
|
<label id="valueLabel">{cyNode.data().state}</label>
|
|
<div className="DetailsLabel">Location</div>
|
|
<label id="valueLabel">{cyNode.data().location}</label>
|
|
<hr style={{ backgroundColor: "#486186" }} />
|
|
</div>
|
|
</CollapseButton>
|
|
);
|
|
return nodeContent;
|
|
}
|
|
|
|
getConnectedNodeDetails(cyNode, connectedNodes, connectedEdges) {
|
|
var sidebarNodeslist = [];
|
|
for (var el of connectedNodes) {
|
|
if (cyNode.data() !== el.data()) {
|
|
sidebarNodeslist.push(el.data());
|
|
}
|
|
}
|
|
var nodeContent = (
|
|
<CollapseButton
|
|
title={cyNode.data().label}
|
|
expanded={true}
|
|
description={
|
|
<div>
|
|
<div id="DetailsLabel">Node ID</div>
|
|
<label id="valueLabel">{cyNode.data().id}</label>
|
|
<div className="DetailsLabel">State</div>
|
|
<label id="valueLabel">{cyNode.data().state}</label>
|
|
<div className="DetailsLabel">Location</div>
|
|
<label id="valueLabel">{cyNode.data().location}</label>
|
|
<hr style={{ backgroundColor: "#486186" }} />
|
|
<div id="connectedNode" style={{ overflow: "auto" }}></div>
|
|
</div>
|
|
}
|
|
>
|
|
{sidebarNodeslist.map((connectedNode) => {
|
|
try {
|
|
var [connectedlinkDetail, tunnelId] = this.getConnectedLinkDetails(
|
|
cyNode,
|
|
connectedNode,
|
|
connectedEdges
|
|
);
|
|
var connectedNodeBtn = (
|
|
<CollapseButton title={connectedNode.label}>
|
|
<div className="DetailsLabel">Node ID</div>
|
|
<label id="valueLabel">{connectedNode.id}</label>
|
|
<div className="DetailsLabel">Tunnel ID</div>
|
|
<label id="valueLabel">{tunnelId}</label>
|
|
<div className="DetailsLabel">Interface Name</div>
|
|
<label id="valueLabel">{connectedlinkDetail.TapName}</label>
|
|
<div className="DetailsLabel">MAC</div>
|
|
<label id="valueLabel">{connectedlinkDetail.MAC}</label>
|
|
<div className="DetailsLabel">State</div>
|
|
<label id="valueLabel">
|
|
{connectedlinkDetail.State.slice(
|
|
7,
|
|
connectedlinkDetail.State.length
|
|
)}
|
|
</label>
|
|
<div className="DetailsLabel">Tunnel Type</div>
|
|
<label id="valueLabel">
|
|
{connectedlinkDetail.Type.slice(
|
|
6,
|
|
connectedlinkDetail.Type.length
|
|
)}
|
|
</label>
|
|
</CollapseButton>
|
|
);
|
|
return connectedNodeBtn;
|
|
} catch (e) {
|
|
console.log(e);
|
|
return false;
|
|
}
|
|
})}
|
|
<br />
|
|
</CollapseButton>
|
|
);
|
|
return nodeContent;
|
|
}
|
|
|
|
getNotConnectedNodeDetails(cyNode) {
|
|
var nodeContent = (
|
|
//No tunnels node
|
|
<CollapseButton title={cyNode.data().label} expanded={true}>
|
|
<div>
|
|
<h5>{cyNode.data().label}</h5>
|
|
<div className="DetailsLabel">Node ID</div>
|
|
<label id="valueLabel">{cyNode.data().id}</label>
|
|
<div className="DetailsLabel">State</div>
|
|
<label id="valueLabel">{cyNode.data().state}</label>
|
|
<div className="DetailsLabel">Location</div>
|
|
<label id="valueLabel">{cyNode.data().location}</label>
|
|
<hr style={{ backgroundColor: "#486186" }} />
|
|
</div>
|
|
</CollapseButton>
|
|
);
|
|
return nodeContent;
|
|
}
|
|
|
|
renderNodeDetails = (cyNode, adj) => {
|
|
var connectedNodes = adj.nodes();
|
|
var connectedEdges = adj.edges();
|
|
var nodeDetails = null;
|
|
if(cyNode.data().hasOwnProperty("location")){
|
|
if (cyNode.data("state") === nodeStates.notReporting) {
|
|
nodeDetails = this.getNotReportingNodeDetails(cyNode);
|
|
} else if (cyNode.data("state") === nodeStates.connected) {
|
|
nodeDetails = this.getConnectedNodeDetails(
|
|
cyNode,
|
|
connectedNodes,
|
|
connectedEdges
|
|
);
|
|
} else if (cyNode.data("state") === nodeStates.noTunnels) {
|
|
nodeDetails = this.getNotConnectedNodeDetails(cyNode);
|
|
}
|
|
ReactDOM.render(
|
|
<div>
|
|
<div> Node Details </div>
|
|
<div> {nodeDetails} </div>
|
|
</div>,
|
|
document.getElementById("sideBarContent")
|
|
);
|
|
}
|
|
else{
|
|
this.queryGeoCoordinates(cyNode.data("coords"))
|
|
.then((loc) => {
|
|
cyNode.data("location", loc);
|
|
if (cyNode.data("state") === nodeStates.notReporting) {
|
|
nodeDetails = this.getNotReportingNodeDetails(cyNode);
|
|
} else if (cyNode.data("state") === nodeStates.connected) {
|
|
nodeDetails = this.getConnectedNodeDetails(
|
|
cyNode,
|
|
connectedNodes,
|
|
connectedEdges
|
|
);
|
|
} else if (cyNode.data("state") === nodeStates.noTunnels) {
|
|
nodeDetails = this.getNotConnectedNodeDetails(cyNode);
|
|
}
|
|
ReactDOM.render(
|
|
<div>
|
|
<div> Node Details </div>
|
|
<div> {nodeDetails} </div>
|
|
</div>,
|
|
document.getElementById("sideBarContent")
|
|
);
|
|
})
|
|
.catch((err) => {
|
|
console.warn(err);
|
|
});
|
|
}
|
|
};
|
|
|
|
getConnectedLinkDetails(source, tgt, connectedEdges) {
|
|
for (var edge of connectedEdges) {
|
|
if (
|
|
(source.data().id === edge._private.data.source &&
|
|
tgt.id === edge._private.data.target) ||
|
|
(source.data().id === edge._private.data.target &&
|
|
tgt.id === edge._private.data.source)
|
|
) {
|
|
for (var descriptorItem of edge._private.data.descriptor) {
|
|
if (
|
|
source.data().id === descriptorItem.Source &&
|
|
tgt.id === descriptorItem.Target
|
|
) {
|
|
return [descriptorItem, edge._private.data.id];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
getSourceAndTargetDetails(cyEdge) {
|
|
var sourceNodeLinkDetails;
|
|
var targetNodeLinkDetails;
|
|
var srcNode;
|
|
var tgtNode;
|
|
var selectedTunnel = cyEdge.data();
|
|
for (var descriptor of selectedTunnel.descriptor) {
|
|
if (
|
|
descriptor.Source === selectedTunnel.source &&
|
|
descriptor.Target === selectedTunnel.target
|
|
) {
|
|
sourceNodeLinkDetails = descriptor;
|
|
srcNode = this.cy.getElementById(sourceNodeLinkDetails.Source).data();
|
|
if (selectedTunnel.descriptor.length === 1) {
|
|
tgtNode = this.cy.getElementById(sourceNodeLinkDetails.Target).data();
|
|
}
|
|
} else if (
|
|
descriptor.Target === selectedTunnel.source &&
|
|
descriptor.Source === selectedTunnel.target
|
|
) {
|
|
targetNodeLinkDetails = descriptor;
|
|
tgtNode = this.cy.getElementById(targetNodeLinkDetails.Source)._private
|
|
.data;
|
|
}
|
|
}
|
|
if (this.isSwapToggle === false) {
|
|
return [sourceNodeLinkDetails, srcNode, tgtNode];
|
|
} else {
|
|
//if swapbutton toggled then swap source and node details
|
|
if (selectedTunnel.descriptor.length === 1)
|
|
return [sourceNodeLinkDetails, tgtNode, srcNode];
|
|
return [targetNodeLinkDetails, tgtNode, srcNode];
|
|
}
|
|
}
|
|
|
|
getTunnelWithBothReportingNodes(selectedTunnel, adj) {
|
|
var LocalEndpointInternal;
|
|
var [sourceNodeLinkDetails, srcNode, tgtNode] =
|
|
this.getSourceAndTargetDetails(selectedTunnel);
|
|
if (sourceNodeLinkDetails.LocalEndpoint.Internal === ":0") {
|
|
LocalEndpointInternal = "NA";
|
|
} else {
|
|
LocalEndpointInternal = sourceNodeLinkDetails.LocalEndpoint.Internal;
|
|
}
|
|
|
|
var linkContent = (
|
|
<CollapseButton title={sourceNodeLinkDetails.TapName} expanded={true}>
|
|
<div>
|
|
<div className="row">
|
|
<div className="col-10" style={{ paddingRight: "0" }}>
|
|
<CollapseButton
|
|
title={srcNode.label}
|
|
>
|
|
<div className="DetailsLabel">Node ID</div>
|
|
<label id="valueLabel">{srcNode.id.slice(0, 7)}</label>
|
|
<div className="DetailsLabel">State</div>
|
|
<label id="valueLabel">{srcNode.state}</label>
|
|
<div className="DetailsLabel">Location</div>
|
|
<label id="valueLabel">{srcNode.location}</label>
|
|
</CollapseButton>
|
|
|
|
<CollapseButton
|
|
title={tgtNode.label}
|
|
>
|
|
<div className="DetailsLabel">Node ID</div>
|
|
<label id="valueLabel">{tgtNode.id.slice(0, 7)}</label>
|
|
<div className="DetailsLabel">State</div>
|
|
<label id="valueLabel">{tgtNode.state}</label>
|
|
<div className="DetailsLabel">Location</div>
|
|
<label id="valueLabel">{tgtNode.location}</label>
|
|
</CollapseButton>
|
|
</div>
|
|
<div
|
|
className="col"
|
|
style={{ margin: "auto", padding: "0", textAlign: "center" }}
|
|
>
|
|
<button onClick={this.handleSwitch.bind(this, selectedTunnel, adj)} id="switchBtn" />
|
|
</div>
|
|
</div>
|
|
<hr style={{ backgroundColor: "#486186" }} />
|
|
<div className="DetailsLabel">Tunnel ID</div>
|
|
<label id="valueLabel">{selectedTunnel.data().id}</label>
|
|
<div className="DetailsLabel">Interface Name</div>
|
|
<label id="valueLabel">{sourceNodeLinkDetails.TapName}</label>
|
|
<div className="DetailsLabel">MAC</div>
|
|
<label id="valueLabel">{sourceNodeLinkDetails.MAC}</label>
|
|
<div className="DetailsLabel">State</div>
|
|
<label id="valueLabel">
|
|
{sourceNodeLinkDetails.State.slice(
|
|
7,
|
|
sourceNodeLinkDetails.State.length
|
|
)}
|
|
</label>
|
|
<div className="DetailsLabel">Tunnel Type</div>
|
|
<label id="valueLabel">
|
|
{sourceNodeLinkDetails.Type.slice(
|
|
6,
|
|
sourceNodeLinkDetails.Type.length
|
|
)}
|
|
</label>
|
|
<div className="DetailsLabel">LocalEndpoint</div>
|
|
<label id="valueLabel" style={{ fontSize: "16px" }}>
|
|
{sourceNodeLinkDetails.LocalEndpoint.Proto}
|
|
{`://`}
|
|
{sourceNodeLinkDetails.LocalEndpoint.External}
|
|
{"<>"}
|
|
{LocalEndpointInternal}
|
|
</label>
|
|
<div className="DetailsLabel">RemoteEndpoint</div>
|
|
<label id="valueLabel" style={{ fontSize: "16px" }}>
|
|
{sourceNodeLinkDetails.RemoteEndpoint.Proto}
|
|
{`://`}
|
|
{sourceNodeLinkDetails.RemoteEndpoint.External}
|
|
</label>
|
|
</div>
|
|
</CollapseButton>
|
|
);
|
|
return linkContent;
|
|
}
|
|
|
|
getTunnelWithEitherOneReportingNodes(selectedTunnel, adj) {
|
|
var LocalEndpointInternal;
|
|
var [sourceNodeLinkDetails, srcNode, tgtNode] =
|
|
this.getSourceAndTargetDetails(selectedTunnel);
|
|
if (sourceNodeLinkDetails.LocalEndpoint.Internal === ":0") {
|
|
LocalEndpointInternal = "NA";
|
|
} else {
|
|
LocalEndpointInternal = sourceNodeLinkDetails.LocalEndpoint.Internal;
|
|
}
|
|
var linkContent = (
|
|
<CollapseButton
|
|
title={sourceNodeLinkDetails.TapName}
|
|
expanded={true}
|
|
>
|
|
<div>
|
|
<div className="row">
|
|
<div className="col-10" style={{ paddingRight: "0" }}>
|
|
<CollapseButton
|
|
title={srcNode.label}
|
|
>
|
|
<div className="DetailsLabel">Node ID</div>
|
|
<label id="valueLabel">{srcNode.id.slice(0, 7)}</label>
|
|
<div className="DetailsLabel">State</div>
|
|
<label id="valueLabel">{srcNode.state}</label>
|
|
<div className="DetailsLabel">Location</div>
|
|
<label id="valueLabel">{srcNode.location}</label>
|
|
</CollapseButton>
|
|
|
|
<CollapseButton
|
|
title={tgtNode.label} >
|
|
<div className="DetailsLabel">Node ID</div>
|
|
<label id="valueLabel">{tgtNode.id.slice(0, 7)}</label>
|
|
<div className="DetailsLabel">State</div>
|
|
<label id="valueLabel">{tgtNode.state}</label>
|
|
<div className="DetailsLabel">Location</div>
|
|
<label id="valueLabel">{tgtNode.location}</label>
|
|
</CollapseButton>
|
|
</div>
|
|
<div
|
|
className="col"
|
|
style={{ margin: "auto", padding: "0", textAlign: "center" }}
|
|
>
|
|
<button onClick={this.handleSwitch.bind(this, selectedTunnel, adj)} id="switchBtn" />
|
|
</div>
|
|
</div>
|
|
<hr style={{ backgroundColor: "#486186" }} />
|
|
<div className="DetailsLabel">Tunnel ID</div>
|
|
<label id="valueLabel">{selectedTunnel.data().id}</label>
|
|
<div className="DetailsLabel">Interface Name</div>
|
|
<label id="valueLabel">{sourceNodeLinkDetails.TapName}</label>
|
|
<div className="DetailsLabel">MAC</div>
|
|
<label id="valueLabel">{sourceNodeLinkDetails.MAC}</label>
|
|
<div className="DetailsLabel">State</div>
|
|
<label id="valueLabel">
|
|
{sourceNodeLinkDetails.State.slice(
|
|
7,
|
|
sourceNodeLinkDetails.State.length
|
|
)}
|
|
</label>
|
|
<div className="DetailsLabel">Tunnel Type</div>
|
|
<label id="valueLabel">
|
|
{sourceNodeLinkDetails.Type.slice(
|
|
6,
|
|
sourceNodeLinkDetails.Type.length
|
|
)}
|
|
</label>
|
|
<div className="DetailsLabel">LocalEndpoint</div>
|
|
<label id="valueLabel" style={{ fontSize: "16px" }}>
|
|
{sourceNodeLinkDetails.LocalEndpoint.Proto}
|
|
{`://`}
|
|
{sourceNodeLinkDetails.LocalEndpoint.External}
|
|
{"<>"}
|
|
{LocalEndpointInternal}
|
|
</label>
|
|
<div className="DetailsLabel">RemoteEndpoint</div>
|
|
<label id="valueLabel" style={{ fontSize: "16px" }}>
|
|
{sourceNodeLinkDetails.RemoteEndpoint.Proto}
|
|
{`://`}
|
|
{sourceNodeLinkDetails.RemoteEndpoint.External}
|
|
</label>
|
|
</div>
|
|
</CollapseButton>
|
|
);
|
|
return linkContent;
|
|
}
|
|
|
|
getTunnelWithNoReportingNodes() {
|
|
var linkContentNR = (
|
|
<CollapseButton
|
|
title={"Details"}
|
|
expanded={true}
|
|
>
|
|
<div>
|
|
<label id="valueLabel">{"Data not available"}</label>
|
|
</div>
|
|
</CollapseButton>
|
|
);
|
|
return linkContentNR;
|
|
}
|
|
|
|
renderTunnelDetails = (cyEdge, adj) => {
|
|
var tunnelDetails;
|
|
var selectedTunnelNodesDetails = [];
|
|
try {
|
|
for (var node of adj) {
|
|
if (node._private.group === "nodes") {
|
|
selectedTunnelNodesDetails.push(node.data());
|
|
}
|
|
}
|
|
if (
|
|
selectedTunnelNodesDetails[0].state === nodeStates.connected &&
|
|
selectedTunnelNodesDetails[1].state === nodeStates.connected
|
|
) {
|
|
tunnelDetails = this.getTunnelWithBothReportingNodes(cyEdge, adj);
|
|
} else if (
|
|
(selectedTunnelNodesDetails[0].state === nodeStates.connected &&
|
|
selectedTunnelNodesDetails[1].state === nodeStates.notReporting) ||
|
|
(selectedTunnelNodesDetails[0].state === nodeStates.notReporting &&
|
|
selectedTunnelNodesDetails[1].state === nodeStates.connected)
|
|
) {
|
|
tunnelDetails = this.getTunnelWithEitherOneReportingNodes(cyEdge, adj);
|
|
} else if (
|
|
selectedTunnelNodesDetails[0].state === nodeStates.notReporting &&
|
|
selectedTunnelNodesDetails[1].state === nodeStates.notReporting
|
|
) {
|
|
tunnelDetails = this.getTunnelWithNoReportingNodes();
|
|
}
|
|
ReactDOM.render(
|
|
<div>
|
|
<div> Tunnel Details </div>
|
|
<div> {tunnelDetails} </div>
|
|
</div>,
|
|
document.getElementById("sideBarContent")
|
|
);
|
|
} catch (err) {
|
|
console.warn(err);
|
|
}
|
|
};
|
|
|
|
handleSwitch = (selectedTunnel, adj) => {
|
|
this.isSwapToggle= !this.isSwapToggle;
|
|
this.renderTunnelDetails(selectedTunnel, adj);
|
|
};
|
|
|
|
handleWheel(e) {
|
|
this.props.setZoomValue(this.cy.zoom());
|
|
}
|
|
|
|
handleCytoClick(event) {
|
|
var cyEle = event.target[0];
|
|
try {
|
|
if (event.target === this.cy) {
|
|
//this.props.clearSelectedElement();
|
|
this.cy.elements().removeClass("transparent");
|
|
this._typeahead.clear();
|
|
return;
|
|
}
|
|
var part = this.partitionElements(cyEle);
|
|
part.neighborhood.removeClass("transparent");
|
|
part.excluded.addClass("transparent");
|
|
if (cyEle.isNode()) {
|
|
this.renderNodeDetails(cyEle, cyEle.neighborhood());
|
|
} else if (cyEle.isEdge()) {
|
|
this.renderTunnelDetails(cyEle, part.neighborhood);
|
|
}
|
|
} catch (error) {
|
|
//this.props.clearSelectedElement();
|
|
this.cy.elements().removeClass("transparent");
|
|
console.warn(error);
|
|
}
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.props.setCurrentView("TopologyView");
|
|
this.autoRefresh = this.props.autoUpdate;
|
|
if (this.autoRefresh) {
|
|
this.queryTopology();
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps, prevState) {
|
|
if (this.props.zoomValue !== prevProps.zoomValue) {
|
|
this.cy.zoom(this.props.zoomValue);
|
|
}
|
|
if (this.props.zoomMin !== prevProps.zoomMin) {
|
|
this.cy.minZoom(this.props.zoomMin);
|
|
}
|
|
if (this.props.zoomMax !== prevProps.zoomMax) {
|
|
this.cy.maxZoom(this.props.zoomMax);
|
|
}
|
|
if (this.props.redrawGraph !== prevProps.redrawGraph) {
|
|
this.cy.center();
|
|
}
|
|
if (this.props.autoUpdate !== prevProps.autoUpdate) {
|
|
this.autoRefresh = this.props.autoUpdate;
|
|
if (this.autoRefresh) {
|
|
this.queryTopology();
|
|
}
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.autoRefresh = false;
|
|
clearTimeout(this.timeoutId);
|
|
//this.props.clearSelectedElement();
|
|
this.props.setCyElements([]);
|
|
}
|
|
|
|
renderTopologyContent() {
|
|
const topologyContent = (
|
|
<Cytoscape
|
|
id="cy"
|
|
cy={(cy) => {
|
|
this.cy = cy;
|
|
this.cy
|
|
.layout({
|
|
name: "circle",
|
|
clockwise: true,
|
|
animate: true,
|
|
animationDuration: 400,
|
|
})
|
|
.run();
|
|
this.cy.on("click", this.handleCytoClick.bind(this));
|
|
this.cy.maxZoom(this.props.zoomMax);
|
|
this.cy.minZoom(this.props.zoomMin);
|
|
this.cy.zoom(this.props.zoomValue); // has to be set after the other operations or it gets reset
|
|
//this.cy.center();
|
|
}}
|
|
wheelSensitivity={0.1}
|
|
elements={JSON.parse(JSON.stringify(this.props.cyElements))} //props.cyElements are frozen
|
|
stylesheet={cytoscapeStyle}
|
|
style={{ width: window.innerWidth, height: window.innerHeight }}
|
|
/>
|
|
);
|
|
|
|
return topologyContent;
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<>
|
|
<section
|
|
onWheel={this.handleWheel.bind(this)}
|
|
style={{ width: "100vw", height: "100vh" }}
|
|
>
|
|
<div id="cyArea">{this.renderTopologyContent()}</div>
|
|
</section>
|
|
<div id="SidePanel">
|
|
<SideBar typeahead={this.renderTypeahead()} />
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
|
|
const mapStateToProps = (state) => ({
|
|
currentOverlayId: state.evio.selectedOverlayId,
|
|
cyElements: state.evio.cyElements,
|
|
currentView: state.view.current,
|
|
selectedView: state.view.selected,
|
|
zoomValue: state.tools.zoomValue,
|
|
zoomMin: state.tools.zoomMinimum,
|
|
zoomMax: state.tools.zoomMaximum,
|
|
autoUpdate: state.tools.autoUpdate,
|
|
redrawGraph: state.evio.redrawGraph,
|
|
});
|
|
|
|
const mapDispatchToProps = {
|
|
setCurrentView,
|
|
setZoomValue,
|
|
setCyElements,
|
|
setRedrawGraph,
|
|
};
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(TopologyView);
|