FE: more defensive network topology hierarchy check #1308

Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
jokob-sk
2025-11-30 10:27:23 +11:00
parent 9634e4e0f7
commit 74677f940e

View File

@@ -565,14 +565,27 @@ function getChildren(node, list, path, visited = [])
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function getHierarchy() function getHierarchy()
{ {
let internetNode = null;
for(i in deviceListGlobal) for(i in deviceListGlobal)
{ {
if(deviceListGlobal[i].devMac == 'Internet') if(deviceListGlobal[i].devMac == 'Internet')
{ {
return (getChildren(deviceListGlobal[i], deviceListGlobal, '')) internetNode = deviceListGlobal[i];
return (getChildren(internetNode, deviceListGlobal, ''))
break; break;
} }
} }
if (!internetNode) {
showModalOk(
getString('Network_Configuration_Error'),
getString('Network_Root_Not_Configured')
);
console.error("getHierarchy(): Internet node not found");
return null;
}
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@@ -671,8 +684,6 @@ function handleNodeClick(el)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
var myTree; var myTree;
var emSize; var emSize;
var nodeHeight; var nodeHeight;
// var sizeCoefficient = 1.4 // var sizeCoefficient = 1.4
@@ -689,140 +700,139 @@ function emToPx(em, element) {
function initTree(myHierarchy) function initTree(myHierarchy)
{ {
// calculate the drawing area based on teh tree width and available screen size if(myHierarchy && myHierarchy.type !== "")
let baseFontSize = parseFloat($('html').css('font-size'));
let treeAreaHeight = ($(window).height() - 155); ;
// calculate the font size of the leaf nodes to fit everything into the tree area
leafNodesCount == 0 ? 1 : leafNodesCount;
emSize = pxToEm((treeAreaHeight/(leafNodesCount)).toFixed(2));
let screenWidthEm = pxToEm($('.networkTable').width()-15);
// init the drawing area size
$("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${emToPx(screenWidthEm)}px`)
if(myHierarchy.type == "")
{ {
showModalOk(getString('Network_Configuration_Error'), getString('Network_Root_Not_Configured')) // calculate the drawing area based on the tree width and available screen size
let baseFontSize = parseFloat($('html').css('font-size'));
let treeAreaHeight = ($(window).height() - 155); ;
return; // calculate the font size of the leaf nodes to fit everything into the tree area
} leafNodesCount == 0 ? 1 : leafNodesCount;
// handle canvas and node size if only a few nodes emSize = pxToEm((treeAreaHeight/(leafNodesCount)).toFixed(2));
emSize > 1 ? emSize = 1 : emSize = emSize;
let nodeHeightPx = emToPx(emSize*1); let screenWidthEm = pxToEm($('.networkTable').width()-15);
let nodeWidthPx = emToPx(screenWidthEm / (parentNodesCount));
// handle if only a few nodes // init the drawing area size
nodeWidthPx > 160 ? nodeWidthPx = 160 : nodeWidthPx = nodeWidthPx; $("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${emToPx(screenWidthEm)}px`)
console.log(Treeviz); // handle canvas and node size if only a few nodes
emSize > 1 ? emSize = 1 : emSize = emSize;
myTree = Treeviz.create({ let nodeHeightPx = emToPx(emSize*1);
htmlId: "networkTree", let nodeWidthPx = emToPx(screenWidthEm / (parentNodesCount));
renderNode: nodeData => {
(!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = ""; // handle if only a few nodes
nodeWidthPx > 160 ? nodeWidthPx = 160 : nodeWidthPx = nodeWidthPx;
(port == "" || port == 0 || port == 'None' ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`; console.log(Treeviz);
portHtml = (port == "" || port == 0 || port == 'None' ) ? " &nbsp " : port; myTree = Treeviz.create({
htmlId: "networkTree",
renderNode: nodeData => {
// Build HTML for individual nodes in the network diagram (!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = "";
deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ?
`<div class="netIcon">
${atob(nodeData.data.icon)}
</div>` : "";
devicePort = `<div class="netPort"
style="width:${emSize}em;height:${emSize}em">
${portHtml}</div>
<div class="portBckgIcon"
style="margin-left:-${emSize*0.7}em;">
${portBckgIcon}
</div>`;
collapseExpandIcon = nodeData.data.hiddenChildren ?
"square-plus" : "square-minus";
// generate +/- icon if node has children nodes (port == "" || port == 0 || port == 'None' ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`;
collapseExpandHtml = nodeData.data.hasChildren ?
`<div class="netCollapse"
style="font-size:${nodeHeightPx/2}px;top:${Math.floor(nodeHeightPx / 4)}px"
data-mytreepath="${nodeData.data.path}"
data-mytreemac="${nodeData.data.mac}">
<i class="fa fa-${collapseExpandIcon} pointer"></i>
</div>` : "";
selectedNodeMac = $(".nav-tabs-custom .active a").attr('data-mytabmac') portHtml = (port == "" || port == 0 || port == 'None' ) ? " &nbsp " : port;
highlightedCss = nodeData.data.mac == selectedNodeMac ? // Build HTML for individual nodes in the network diagram
" highlightedNode " : ""; deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ?
cssNodeType = nodeData.data.devIsNetworkNodeDynamic ? `<div class="netIcon">
" node-network-device " : " node-standard-device "; ${atob(nodeData.data.icon)}
</div>` : "";
devicePort = `<div class="netPort"
style="width:${emSize}em;height:${emSize}em">
${portHtml}</div>
<div class="portBckgIcon"
style="margin-left:-${emSize*0.7}em;">
${portBckgIcon}
</div>`;
collapseExpandIcon = nodeData.data.hiddenChildren ?
"square-plus" : "square-minus";
networkHardwareIcon = nodeData.data.devIsNetworkNodeDynamic ? `<span class="network-hw-icon"> // generate +/- icon if node has children nodes
<i class="fa-solid fa-hard-drive"></i> collapseExpandHtml = nodeData.data.hasChildren ?
</span>` : ""; `<div class="netCollapse"
style="font-size:${nodeHeightPx/2}px;top:${Math.floor(nodeHeightPx / 4)}px"
data-mytreepath="${nodeData.data.path}"
data-mytreemac="${nodeData.data.mac}">
<i class="fa fa-${collapseExpandIcon} pointer"></i>
</div>` : "";
const badgeConf = getStatusBadgeParts(nodeData.data.presentLastScan, nodeData.data.alertDown, nodeData.data.mac, statusText = '') selectedNodeMac = $(".nav-tabs-custom .active a").attr('data-mytabmac')
return result = `<div highlightedCss = nodeData.data.mac == selectedNodeMac ?
class="node-inner hover-node-info box pointer ${highlightedCss} ${cssNodeType}" " highlightedNode " : "";
style="height:${nodeHeightPx}px;font-size:${nodeHeightPx-5}px;" cssNodeType = nodeData.data.devIsNetworkNodeDynamic ?
onclick="handleNodeClick(this)" " node-network-device " : " node-standard-device ";
data-mac="${nodeData.data.mac}"
data-parentMac="${nodeData.data.parentMac}" networkHardwareIcon = nodeData.data.devIsNetworkNodeDynamic ? `<span class="network-hw-icon">
data-name="${nodeData.data.name}" <i class="fa-solid fa-hard-drive"></i>
data-ip="${nodeData.data.ip}" </span>` : "";
data-mac="${nodeData.data.mac}"
data-vendor="${nodeData.data.vendor}" const badgeConf = getStatusBadgeParts(nodeData.data.presentLastScan, nodeData.data.alertDown, nodeData.data.mac, statusText = '')
data-type="${nodeData.data.type}"
data-devIsNetworkNodeDynamic="${nodeData.data.devIsNetworkNodeDynamic}" return result = `<div
data-lastseen="${nodeData.data.lastseen}" class="node-inner hover-node-info box pointer ${highlightedCss} ${cssNodeType}"
data-firstseen="${nodeData.data.firstseen}" style="height:${nodeHeightPx}px;font-size:${nodeHeightPx-5}px;"
data-relationship="${nodeData.data.relType}" onclick="handleNodeClick(this)"
data-status="${nodeData.data.status}" data-mac="${nodeData.data.mac}"
data-present="${nodeData.data.presentLastScan}" data-parentMac="${nodeData.data.parentMac}"
data-alert="${nodeData.data.alertDown}" data-name="${nodeData.data.name}"
data-icon="${nodeData.data.icon}" data-ip="${nodeData.data.ip}"
> data-mac="${nodeData.data.mac}"
<div class="netNodeText"> data-vendor="${nodeData.data.vendor}"
<strong><span>${devicePort} <span class="${badgeConf.cssText}">${deviceIcon}</span></span> data-type="${nodeData.data.type}"
<span class="spanNetworkTree anonymizeDev" style="width:${nodeWidthPx-50}px">${nodeData.data.name}</span> data-devIsNetworkNodeDynamic="${nodeData.data.devIsNetworkNodeDynamic}"
${networkHardwareIcon} data-lastseen="${nodeData.data.lastseen}"
</strong> data-firstseen="${nodeData.data.firstseen}"
data-relationship="${nodeData.data.relType}"
data-status="${nodeData.data.status}"
data-present="${nodeData.data.presentLastScan}"
data-alert="${nodeData.data.alertDown}"
data-icon="${nodeData.data.icon}"
>
<div class="netNodeText">
<strong><span>${devicePort} <span class="${badgeConf.cssText}">${deviceIcon}</span></span>
<span class="spanNetworkTree anonymizeDev" style="width:${nodeWidthPx-50}px">${nodeData.data.name}</span>
${networkHardwareIcon}
</strong>
</div>
</div> </div>
</div> ${collapseExpandHtml}`;
${collapseExpandHtml}`; },
}, mainAxisNodeSpacing: 'auto',
mainAxisNodeSpacing: 'auto', // secondaryAxisNodeSpacing: 0.3,
// secondaryAxisNodeSpacing: 0.3, nodeHeight: nodeHeightPx,
nodeHeight: nodeHeightPx, nodeWidth: nodeWidthPx,
nodeWidth: nodeWidthPx, marginTop: '5',
marginTop: '5', isHorizontal : true,
isHorizontal : true, hasZoom: true,
hasZoom: true, hasPan: true,
hasPan: true, marginLeft: '10',
marginLeft: '10', marginRight: '10',
marginRight: '10', idKey: "mac",
idKey: "mac", hasFlatData: false,
hasFlatData: false, relationnalField: "children",
relationnalField: "children", linkWidth: (nodeData) => 2,
linkWidth: (nodeData) => 2, linkColor: (nodeData) => {
linkColor: (nodeData) => { relConf = getRelationshipConf(nodeData.data.relType)
relConf = getRelationshipConf(nodeData.data.relType) return relConf.color;
return relConf.color; }
} // onNodeClick: (nodeData) => handleNodeClick(nodeData),
// onNodeClick: (nodeData) => handleNodeClick(nodeData), });
});
console.log(deviceListGlobal); console.log(deviceListGlobal);
myTree.refresh(myHierarchy); myTree.refresh(myHierarchy);
// hide spinning icon // hide spinning icon
hideSpinner() hideSpinner()
} else
{
console.error("getHierarchy() not returning expected result");
}
} }