Files
NetAlertX/front/network.php
jokob-sk 5b6424d405
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
hover-box in devices lists
2025-07-16 22:28:40 +10:00

953 lines
34 KiB
PHP
Executable File

<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
// online / offline badges HTML snippets
define('badge_online', '<div class="badge bg-green text-white" style="width: 60px;">Online</div>');
define('badge_offline', '<div class="badge bg-red text-white" style="width: 60px;">Offline</div>');
define('sortable_column', ' <span class="sort-btn" onclick="sortColumn(this)"><i class="fa-solid fa-arrow-up-short-wide"></i></span>');
?>
<script>
// show spinning icon
showSpinner()
</script>
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper">
<span class="helpIcon"> <a target="_blank" href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md"><i class="fa fa-circle-question"></i></a></span>
<div id="networkTree" class="drag"></div>
<!-- Main content ---------------------------------------------------------- -->
<section class="content networkTable">
<?php
// Create top-level node (network devices) tabs
function createDeviceTabs($node_mac, $node_name, $node_status, $node_type, $node_ports_count, $icon, $node_alert, $activetab) {
// prepare string with port number in brackets if available
$str_port = "";
if ($node_ports_count != "") {
$str_port = ' ('.$node_ports_count.')';
}
// online/offline status circle (red/green)
$icon_class = "";
if($node_status == 0 && $node_alert == 1) // 1 means online, 0 offline
{
$icon_class = " text-red";
} elseif ($node_status == 1) {
$icon_class = " text-green";
} elseif ($node_status == 0) {
$icon_class = " text-gray50";
}
$decoded_icon = base64_decode($icon);
$idFromMac = str_replace(":", "_", $node_mac);
$str_tab_header = '<li class="networkNodeTabHeaders '.$activetab.' " >
<a href="#'.$idFromMac.'" data-mytabmac="'.$node_mac.'" id="'.$idFromMac.'_id" data-toggle="tab" title="'.$node_name.' ">' // _id is added so it doesn't conflict with AdminLTE tab behavior
.'<div class="icon '.$icon_class.'" >'.$decoded_icon.' </div> <span class="node-name">'.$node_name.'</span>' .$str_port.
'</a>
</li>';
echo $str_tab_header;
}
// Create pane content (displayed inside of the tabs)
function createPane($node_mac, $node_name, $node_status, $node_type, $node_ports_count, $node_parent_mac, $activetab){
// online/offline status circle (red/green)
$node_badge = "";
if($node_status == 1) // 1 means online, 0 offline
{
$node_badge = badge_online;
} else
{
$node_badge = badge_offline;
}
$idFromMac = str_replace(":", "_", $node_mac);
$idParentMac = str_replace(":", "_", $node_parent_mac);
$str_tab_pane = '<div class="tab-pane '.$activetab.'" id="'.$idFromMac.'">
<div>
<h2 class="page-header"><i class="fa fa-server"></i> '.lang('Network_Node'). '</h2>
</div>
<table class="table table-striped" >
<tbody>
<tr>
<td class="col-sm-3">
<b>'.lang('Network_Node').'</b>
</td>
<td class="anonymize">
<a href="./deviceDetails.php?mac='.$node_mac.'">
'.$node_name.'
</a>
</td>
</tr>
<tr>
<td >
<b>MAC</b>
</td>
<td data-mynodemac="'.$node_mac.'" class="anonymize">'
.$node_mac.
'</td>
</tr>
<tr>
<td>
<b>'.lang('Device_TableHead_Type').'</b>
</td>
<td>
' .$node_type. '
</td>
</tr>
<tr>
<td>
<b>'.lang('Network_Table_State').'</b>
</td>
<td> '
.$node_badge.
'</td>
</tr>
<tr>
<td>
<b>'.lang('Network_Parent').'</b>
</td>
<td>
<a href="./network.php?mac='.$idParentMac.'">
<b class="anonymize">
<span class="mac-to-name" my-data-mac="'.$node_parent_mac.'">'.$node_parent_mac.' </span>
<i class="fa fa-square-up-right"></i>
</b>
</a>
</td>
</tr>
</tbody>
</table>
<div id="assignedDevices" class="box-body no-padding">
<div class="page-header">
<h3>
<i class="fa fa-sitemap"></i> '.lang('Network_Connected').'
</h3>
</div>
';
$str_table = ' <table class="table table-striped">
<thead>
<tr>
<th class="col-sm-1" >Port</th>
<th class="col-sm-1" >'.lang('Network_Table_State').'</th>
<th class="col-sm-2" >'.lang('Network_Table_Hostname').sortable_column.'</th>
<th class="col-sm-1" >'.lang('Network_Table_IP').sortable_column.'</th>
<th class="col-sm-3" >'.lang('Network_ManageLeaf').'</th>
</tr>
</thead>
<tbody>
<tr>
</tr>';
// Prepare Array for Devices with Port value
// If no Port is set, the Port number is set to 0
if ($node_ports_count == "") {
$node_ports_count = 0;
}
// Get all leafs connected to a node based on the node_mac
$func_sql = 'SELECT devParentPort as port,
devMac as mac,
devPresentLastScan as online,
devName as name,
devType as type,
devLastIP as last_ip,
(select devType from Devices a where devMac = "'.$node_mac.'") as node_type
FROM Devices WHERE devParentMAC = "'.$node_mac.'" and devIsArchived = 0 order by port, name asc';
global $db;
$func_result = $db->query($func_sql);
// array
$tableData = array();
while ($row = $func_result -> fetchArray (SQLITE3_ASSOC)) {
// Push row data
$tableData[] = array( 'port' => $row['port'],
'mac' => $row['mac'],
'online' => $row['online'],
'name' => $row['name'],
'type' => $row['type'],
'last_ip' => $row['last_ip'],
'node_type' => $row['node_type']);
}
// Control no rows
if (empty($tableData)) {
$tableData = [];
}
$str_table_rows = "";
foreach ($tableData as $row) {
if ($row['online'] == 1) {
$port_state = badge_online;
} else {
$port_state = badge_offline;
}
// prepare HTML for the port table column cell
$port_content = "N/A";
if (($row['node_type'] == "WLAN" || $row['node_type'] == "AP" ) && ($row['port'] == NULL || $row['port'] == "") ){
$port_content = '<i class="fa fa-wifi"></i>';
} elseif ($row['node_type'] == "Powerline")
{
$port_content = '<i class="fa fa-flash"></i>';
} elseif ($row['port'] != NULL && $row['port'] != "")
{
$port_content = $row['port'];
}
$str_table_rows = $str_table_rows.
'<tr>
<td style="text-align: center;">
'.$port_content.'
</td>
<td>'
.$port_state.
'</td>
<td style="padding-left: 10px;">
<a href="./deviceDetails.php?mac='.$row['mac'].'">
<b class="anonymize">'.$row['name'].'</b>
</a>
</td>
<td class="anonymize">'
.$row['last_ip'].
'</td>
<td class="">
<button class="btn btn-primary btn-danger btn-sm" data-myleafmac="'.$row['mac'].'" >'.lang('Network_ManageUnassign').'</button>
</td>
</tr>';
}
$str_table_close = '</tbody>
</table>';
// no connected device - don't render table, just display some info
if($str_table_rows == "")
{
$str_table = "<div>
<div>
".lang("Network_NoAssignedDevices")."
</div>
</div>";
$str_table_close = "";
}
$str_close_pane = '</div>
</div>';
// write the HTML
echo ''.$str_tab_pane.
$str_table.
$str_table_rows.
$str_table_close.
$str_close_pane;
}
// Create Top level tabs (List of network devices), explanation of the terminology below:
//
// Switch 1 (node)
// /(p1) \ (p2) <----- port numbers
// / \
// Smart TV (leaf) Switch 2 (node (for the PC) and leaf (for Switch 1))
// \
// PC (leaf) <------- leafs are not included in this SQL query
$networkDeviceTypes = str_replace("]", "",(str_replace("[", "", getSettingValue("NETWORK_DEVICE_TYPES"))));
$sql = "SELECT node_name, node_mac, online, node_type, node_ports_count, parent_mac, node_icon, node_alert
FROM
(
SELECT a.devName as node_name,
a.devMac as node_mac,
a.devPresentLastScan as online,
a.devType as node_type,
a.devParentMAC as parent_mac,
a.devIcon as node_icon,
a.devAlertDown as node_alert
FROM Devices a
WHERE a.devType in (".$networkDeviceTypes.")
AND devIsArchived = 0
) t1
LEFT JOIN
(
SELECT b.devParentMAC as node_mac_2,
count() as node_ports_count
FROM Devices b
WHERE b.devParentMAC NOT NULL group by b.devParentMAC
) t2
ON (t1.node_mac = t2.node_mac_2);
";
$result = $db->query($sql);
// array
$tableData = array();
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
// Push row data
$tableData[] = array( 'node_mac' => $row['node_mac'],
'node_name' => $row['node_name'],
'online' => $row['online'],
'node_type' => $row['node_type'],
'parent_mac' => $row['parent_mac'],
'node_icon' => $row['node_icon'],
'node_ports_count' => $row['node_ports_count'],
'node_alert' => $row['node_alert']
);
}
// Control no rows
if (empty($tableData)) {
$tableData = [];
}
echo '<div class="nav-tabs-custom" style="margin-bottom: 0px;">
<ul class="nav nav-tabs">';
$activetab='active';
foreach ($tableData as $row) {
createDeviceTabs( $row['node_mac'],
$row['node_name'],
$row['online'],
$row['node_type'],
$row['node_ports_count'],
$row['node_icon'],
$row['node_alert'],
$activetab);
$activetab = ""; // reset active tab indicator, only the first tab is active
}
echo ' </ul> <div class="tab-content">';
$activetab='active';
foreach ($tableData as $row) {
createPane($row['node_mac'],
$row['node_name'],
$row['online'],
$row['node_type'],
$row['node_ports_count'],
$row['parent_mac'],
$activetab);
$activetab = ""; // reset active tab indicator, only the first tab is active
}
?>
<!-- /.tab-pane -->
</div>
</section>
<!-- Unassigned devices -->
<?php
// Get all Unassigned / unconnected nodes
$func_sql = 'SELECT
devMac AS mac,
devPresentLastScan AS online,
devName AS name,
devLastIP AS last_ip,
devParentMAC
FROM Devices
WHERE devParentMAC IS NULL
OR devParentMAC IN ("", " ", "undefined", "null")
AND devMac NOT LIKE "%internet%"
AND devIsArchived = 0
ORDER BY name ASC;';
global $db;
$func_result = $db->query($func_sql);
// array
$tableData = array();
while ($row = $func_result -> fetchArray (SQLITE3_ASSOC)) {
// Push row data
$tableData[] = array( 'mac' => $row['mac'],
'online' => $row['online'],
'name' => $row['name'],
'last_ip' => $row['last_ip']);
}
// Don't do anything if empty
if (!(empty($tableData))) {
$str_table_header = '
<div class="content">
<div id="unassignedDevices" class="box box-aqua box-body">
<section>
<h3>
<i class="fa fa-laptop"></i> '.lang('Network_UnassignedDevices').'
</h3>
<table class="table table-striped">
<thead>
<tr>
<th class="col-sm-1" ></th>
<th class="col-sm-1" >'.lang('Network_Table_State').'</th>
<th class="col-sm-2" >'.lang('Network_Table_Hostname').sortable_column.'</th>
<th class="col-sm-1" >'.lang('Network_Table_IP').sortable_column.'</th>
<th class="col-sm-3" >'.lang('Network_Assign').'</th>
</tr>
</thead>
<tbody>
<tr>
</tr>';
$str_table_rows = "";
foreach ($tableData as $row) {
if ($row['online'] == 1) {
$state = badge_online;
} else {
$state = badge_offline;
}
$str_table_rows = $str_table_rows.
'
<tr>
<td> </td>
<td>'
.$state.
'</td>
<td style="padding-left: 10px;">
<a href="./deviceDetails.php?mac='.$row['mac'].'">
<b class="anonymize">'.$row['name'].'</b>
</a>
</td>
<td>'
.$row['last_ip'].
'</td>
<td>
<button class="btn btn-primary btn-sm" data-myleafmac="'.$row['mac'].'" >'.lang('Network_ManageAssign').'</button>
</td>
</tr>';
}
$str_table_close = '</tbody>
</table>
</section>
</div>
</div>';
// write the html
echo $str_table_header.$str_table_rows.$str_table_close;
}
?>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
<!-- ----------------------------------------------------------------------- -->
<?php
require 'php/templates/footer.php';
?>
<script src="lib/treeviz/bundle.js"></script>
<script defer>
const apiUrl = `php/server/dbHelper.php?action=read&rawSql=${btoa(encodeURIComponent(
`select *, CASE WHEN devAlertDown !=0 AND devPresentLastScan=0 THEN "Down"
WHEN devPresentLastScan=1 THEN "On-line"
ELSE "Off-line" END as devStatus
from Devices`))}`;
$.get(apiUrl, function (data) {
rawData = JSON.parse (data)
console.log(rawData);
if(rawData["data"] == "")
{
showModalOK (getString('Gen_Warning'), getString('Network_NoDevices'))
return;
}
orderTopologyBy = createArray(getSetting("UI_TOPOLOGY_ORDER"))
devicesListnew = rawData.sort((a, b) => {
// Helper to safely parse port into an integer; invalid ports become Infinity for sorting
const parsePort = (devParentPort) => {
const parsed = parseInt(devParentPort, 10);
return isNaN(parsed) ? Infinity : parsed;
};
switch (orderTopologyBy[0]) {
case "Name":
// First sort by name alphabetically
const nameCompare = a.devName.localeCompare(b.devName);
if (nameCompare !== 0) {
return nameCompare;
}
// If names are the same, sort by port numerically
return parsePort(a.devParentPort) - parsePort(b.devParentPort);
case "Port":
// Sort by port numerically
return parsePort(a.devParentPort) - parsePort(b.devParentPort);
default:
// Default: Sort by rowid (as a fallback)
return a.rowid - b.rowid;
}
});
setCache('devicesListNew', JSON.stringify(devicesListnew));
// Init global variable
deviceListGlobal = devicesListnew;
// create tree
initTree(getHierarchy());
// attach on-click events
attachTreeEvents();
});
</script>
<script defer>
// ---------------------------------------------------------------------------
// Tree functionality
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
var leafNodesCount = 0;
var visibleNodesCount = 0;
var parentNodesCount = 0;
var hiddenMacs = []; // hidden children
var hiddenChildren = [];
var deviceListGlobal = null;
// ---------------------------------------------------------------------------
// Recursively get children nodes and build a tree
function getChildren(node, list, path, visited = [])
{
var children = [];
// Check for infinite recursion by seeing if the node has been visited before
if (visited.includes(node.devMac.toLowerCase())) {
console.error("Infinite recursion detected at node:", node.devMac);
write_notification("[ERROR] ⚠ Infinite recursion detected. You probably have assigned the Internet node to another children node or to itself. Please open a new issue on GitHub and describe how you did it.", 'interrupt')
return { error: "Infinite recursion detected", node: node.devMac };
}
// Add current node to visited list
visited.push(node.devMac.toLowerCase());
// Loop through all items to find children of the current node
for (var i in list) {
if (list[i].devParentMAC.toLowerCase() == node.devMac.toLowerCase() && !hiddenMacs.includes(list[i].devParentMAC)) {
visibleNodesCount++;
// Process children recursively, passing a copy of the visited list
children.push(getChildren(list[i], list, path + ((path == "") ? "" : '|') + list[i].devParentMAC, visited));
}
}
// Track leaf and parent node counts
if (children.length == 0) {
leafNodesCount++;
} else {
parentNodesCount++;
}
return {
name: node.devName,
path: path,
mac: node.devMac,
port: node.devParentPort,
id: node.devMac,
parentMac: node.devParentMAC,
icon: node.devIcon,
type: node.devType,
vendor: node.devVendor,
lastseen: node.devLastConnection,
firstseen: node.devFirstConnection,
ip: node.devLastIP,
status: node.devStatus,
presentLastScan: node.devPresentLastScan,
alertDown: node.devAlertDown,
hasChildren: children.length > 0 || hiddenMacs.includes(node.devMac),
relType: node.devParentRelType,
hiddenChildren: hiddenMacs.includes(node.devMac),
qty: children.length,
children: children
};
}
// ---------------------------------------------------------------------------
function getHierarchy()
{
for(i in deviceListGlobal)
{
if(deviceListGlobal[i].devMac == 'Internet')
{
return (getChildren(deviceListGlobal[i], deviceListGlobal, ''))
break;
}
}
}
//---------------------------------------------------------------------------
function toggleSubTree(parentMac, treePath)
{
treePath = treePath.split('|')
if(!hiddenMacs.includes(parentMac))
{
hiddenMacs.push(parentMac)
}
else
{
removeItemFromArray(hiddenMacs, parentMac)
}
updatedTree = getHierarchy()
myTree.refresh(updatedTree);
// re-attach any onclick events
attachTreeEvents();
}
// ---------------------------------------------------------------------------
function attachTreeEvents()
{
// toggle subtree functionality
$("div[data-mytreemac]").each(function(){
$(this).attr('onclick', 'toggleSubTree("'+$(this).attr('data-mytreemac')+'","'+ $(this).attr('data-mytreepath')+'")')
});
}
// ---------------------------------------------------------------------------
// Handle network node click - select correct tab in the bottom table
function handleNodeClick(el)
{
const targetTabMAC = $(el).attr("data-mytreemacmain");
// handle network node
var targetTab = $(`a[data-mytabmac="${targetTabMAC}"]`);
if (targetTab.length) {
// Simulate a click event on the target tab
targetTab.click();
// Smooth scroll to the tab content
$('html, body').animate({
scrollTop: targetTab.offset().top - 50
}, 500); // Adjust the duration as needed
} else
{
// handle regular device - open in new tab
goToDevice($(el).data("mac"), true)
}
}
// ---------------------------------------------------------------------------
var myTree;
var emSize;
var nodeHeight;
// var sizeCoefficient = 1.4
function pxToEm(px, element) {
var baseFontSize = parseFloat($(element || "body").css("font-size"));
return px / baseFontSize;
}
function emToPx(em, element) {
var baseFontSize = parseFloat($(element || "body").css("font-size"));
return Math.round(em * baseFontSize);
}
function initTree(myHierarchy)
{
// calculate the drawing area based on teh tree width and available screen size
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'))
return;
}
// handle if only a few nodes
emSize > 1 ? emSize = 1 : emSize = emSize;
let nodeHeightPx = emToPx(emSize*1);
let nodeWidthPx = emToPx(screenWidthEm / (parentNodesCount));
// handle if only a few nodes
nodeWidthPx > 160 ? nodeWidthPx = 160 : nodeWidthPx = nodeWidthPx;
console.log(Treeviz);
myTree = Treeviz.create({
htmlId: "networkTree",
renderNode: nodeData => {
(!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = "";
(port == "" || port == 0 || port == 'None' ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`;
portHtml = (port == "" || port == 0 || port == 'None' ) ? "" : port;
// Build HTML for individual nodes in the network diagram
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}em;">
${portBckgIcon}
</div>`;
collapseExpandIcon = nodeData.data.hiddenChildren ?
"square-plus" : "square-minus";
// generate +/- icon if node has children nodes
collapseExpandHtml = nodeData.data.hasChildren ?
`<div class="netCollapse"
style="font-size:${nodeHeightPx/2}px;top:${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')
highlightedCss = nodeData.data.mac == selectedNodeMac ?
" highlightedNode" : "";
const badgeConf = getStatusBadgeParts(nodeData.data.presentLastScan, nodeData.data.alertDown, nodeData.data.mac, statusText = '')
return result = `<div
class="node-inner hover-node-info box pointer ${highlightedCss}"
data-mytreemacmain="${nodeData.data.mac}"
style="height:${nodeHeightPx}px;font-size:${nodeHeightPx-5}px;"
onclick="handleNodeClick(this)"
data-name="${nodeData.data.name}"
data-ip="${nodeData.data.ip}"
data-mac="${nodeData.data.mac}"
data-vendor="${nodeData.data.vendor}"
data-lastseen="${nodeData.data.lastseen}"
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>
</strong>
</div>
</div>
${collapseExpandHtml}`;
},
mainAxisNodeSpacing: 'auto',
// secondaryAxisNodeSpacing: 0.3,
nodeHeight: nodeHeightPx,
nodeWidth: nodeWidthPx,
marginTop: '5',
isHorizontal : true,
hasZoom: true,
hasPan: true,
marginLeft: '10',
marginRight: '10',
idKey: "mac",
hasFlatData: false,
relationnalField: "children",
linkWidth: (nodeData) => 3,
linkColor: (nodeData) => {
relConf = getRelationshipConf(nodeData.data.relType)
return relConf.color;
}
// onNodeClick: (nodeData) => handleNodeClick(nodeData),
});
console.log(deviceListGlobal);
myTree.refresh(myHierarchy);
// hide spinning icon
hideSpinner()
}
// ---------------------------------------------------------------------------
// Tabs functionality
// ---------------------------------------------------------------------------
// Register events on tab change
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
initButtons()
});
// ---------------------------------------------------------------------------
function initTab()
{
key = "activeNetworkTab"
// default selection
selectedTab = "Internet_id"
// the #target from the url
target = getQueryString('mac')
// update cookie if target specified
if(target != "")
{
setCache(key, target.replaceAll(":","_")+'_id') // _id is added so it doesn't conflict with AdminLTE tab behavior
}
// get the tab id from the cookie (already overridden by the target)
if(!emptyArr.includes(getCache(key)))
{
selectedTab = getCache(key);
}
// Activate panel
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
// When changed save new current tab
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
setCache(key, $(e.target).attr('id'))
});
}
// ---------------------------------------------------------------------------
function initDeviceNamesFromMACs()
{
$('.mac-to-name').each(function() {
var dataMacValue = $(this).attr('my-data-mac');
if(dataMacValue =="" )
{
$(this).html(getString("Network_Root"))
}
else{
$(this).html(getNameByMacAddress(dataMacValue));
}
});
}
// ---------------------------------------------------------------------------
function initButtons()
{
var currentNodeMac = $(".tab-content .active td[data-mynodemac]").attr('data-mynodemac');
// change highlighted node in the tree
selNode = $("#networkTree .highlightedNode")[0]
// console.log(selNode)
if(selNode)
{
$(selNode).attr('class', $(selNode).attr('class').replace('highlightedNode'))
}
newSelNode = $("#networkTree div[data-mytreemacmain='"+currentNodeMac+"']")[0]
$(newSelNode).attr('class', $(newSelNode).attr('class') + ' highlightedNode')
// init the Assign buttons
$('#unassignedDevices button[data-myleafmac]').each(function(){
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","${currentNodeMac}")`)
});
// init Unassign buttons
$('#assignedDevices button[data-myleafmac]').each(function(){
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","")`)
});
}
// ---------------------------------------------------------------------------
function updateLeaf(leafMac,nodeMac)
{
console.log(leafMac) // child
console.log(nodeMac) // parent
console.log(nodeMac != "") // parent
// prevent the assignment of the Internet root node avoiding recursion when generating the network tree topology
if(leafMac.toLowerCase().includes('internet') && nodeMac != "")
{
showMessage(getString('Network_Cant_Assign'))
}
else{
saveData('updateNetworkLeaf', leafMac, nodeMac);
setTimeout("location.reload();", 500); // refresh page
}
}
// init device names where macs are used
initDeviceNamesFromMACs();
// init selected (first) tab
initTab();
// init Assign/Unassign buttons
initButtons()
// init pop up hover boxes for device details
initHoverNodeInfo();
</script>