nnetwork and link tweaks

This commit is contained in:
jokob-sk
2025-07-24 12:53:24 +10:00
parent dff6cba2d8
commit a111ed929b
7 changed files with 230 additions and 137 deletions

View File

@@ -12,6 +12,7 @@
----------------------------------------------------------------------------- */
:root {
--color-aqua: #00c0ef;
--color-lightblue: #3c8dbc;
--color-blue: #0060df;
--color-green: #00a65a;
--color-yellow: #f39c12;
@@ -29,6 +30,45 @@ h5
font-size: medium;
}
a[target="_blank"] {
position: relative;
display: inline-block; /* Needed for positioning */
padding-right: 0.6em; /* Space for the icon */
}
a[target="_blank"]::after {
content: '↗';
position: absolute;
top: 0;
right: 0;
font-size: 0.75em;
line-height: 1;
}
.select2 .hover-node-info::after {
padding-left: 1px ;
}
/* .node-standard-device .netNodeText::after
{
right: -7px;
top: 1px;
} */
/* .select2-container--default .select2-selection--multiple .select2-selection__choice
{
padding-right: 15px !important;
} */
.hoverHighlight
{
opacity: 0.7;
}
.hoverHighlight:hover
{
opacity: 1;
}
/* -----------------------------------------------------------------------------
Helper Classes
----------------------------------------------------------------------------- */
@@ -49,6 +89,7 @@ h5
float: inline-end;
}
/* -----------------------------------------------------------------------------
Text Classes
----------------------------------------------------------------------------- */
@@ -480,7 +521,7 @@ body
}
.bottom-border-primary {
border-bottom-color: #3c8dbc;
border-bottom-color: var(--color-lightblue);
border-bottom-style: solid;
border-bottom-width: 3px
}
@@ -1666,7 +1707,7 @@ input[readonly] {
#toggleFilters
{
display: block;
position: absolute;
position: fixed;
padding-left: 32px;
padding-top: 10px;
background-color: inherit;
@@ -1813,8 +1854,8 @@ input[readonly] {
#networkTree .highlightedNode
{
/* border: solid; */
border-color:#3c8dbc;
box-shadow: #3c8dbc 0px 0px 20px;
border-color:var(--color-lightblue);
box-shadow: var(--color-lightblue) 0px 0px 20px;
}
#networkTree .netStatus-Off-line i,
@@ -1843,6 +1884,8 @@ input[readonly] {
.networkTable
{
padding-bottom: 1px;
z-index: 3;
position: relative;
}
.networkNodeTabHeaders .icon i

View File

@@ -651,8 +651,14 @@
border-color: #888888;
}
.table-hover tbody tr:hover td, .table-hover tbody tr:hover th {
background-color: rgb(189,192,198);
color: #444;
background-color: var(--datatable-bgcolor);
color: var(--fbc-white);
}
table.dataTable tbody tr.selected, table.dataTable tbody tr .selected
{
background-color: var(--datatable-bgcolor);
color: var(--fbc-white);
}
.db_info_table_cell:nth-child(1) {background: #272c30}

View File

@@ -780,7 +780,9 @@ function initializeDatatable (status) {
// console.log(cellData)
$(td).html (
`<b class="anonymizeDev hover-node-info"
`<b class="anonymizeDev "
>
<a href="deviceDetails.php?mac=${rowData[mapIndx(11)]}" class="hover-node-info"
data-name="${cellData}"
data-ip="${rowData[mapIndx(8)]}"
data-mac="${rowData[mapIndx(11)]}"
@@ -792,9 +794,7 @@ function initializeDatatable (status) {
data-status="${rowData[mapIndx(10)]}"
data-present="${rowData[mapIndx(24)]}"
data-alert="${rowData[mapIndx(25)]}"
data-icon="${rowData[mapIndx(3)]}"
>
<a href="deviceDetails.php?mac=${rowData[mapIndx(11)]}" class="">
data-icon="${rowData[mapIndx(3)]}">
${cellData}
</a>
</b>`
@@ -845,7 +845,7 @@ function initializeDatatable (status) {
<a href="http://${cellData}" class="pointer" target="_blank">
${cellData}
</a>
<span class="alignRight">
<span class="alignRight lockIcon">
<a href="https://${cellData}" class="pointer" target="_blank">
<i class="fa fa-lock "></i>
</a>

View File

@@ -28,9 +28,9 @@
</div>
</div>
</div>
<div class="col-md-1">
<i class="fa-solid fa-circle-check" onclick="markAllSelected()" title="<?= lang('Gen_Add_All');?>"></i>
<i class="fa-solid fa-circle-xmark" onclick="markAllNotSelected()" title="<?= lang('Gen_Remove_All');?>"></i>
<div class="col-md-1 hoverHighlight">
<i class="fa-solid fa-circle-check hoverHighlight pointer" onclick="markAllSelected()" title="<?= lang('Gen_Add_All');?>"></i>
<i class="fa-solid fa-circle-xmark hoverHighlight pointer" onclick="markAllNotSelected()" title="<?= lang('Gen_Remove_All');?>"></i>
</div>
</div>

View File

@@ -170,9 +170,9 @@
<h5><i class="fa fa-server"></i> ${getString('Network_Node')}</h5>
<div class="mb-3 row">
<label class="col-sm-3 col-form-label fw-bold">${getString('Network_Node')}</label>
<label class="col-sm-3 col-form-label fw-bold">${getString('DevDetail_Tab_Details')}</label>
<div class="col-sm-9">
<a href="./deviceDetails.php?mac=${node.node_mac}" class="anonymize">${node.node_name}</a>
<a href="./deviceDetails.php?mac=${node.node_mac}" target="_blank" class="anonymize">${node.node_name}</a>
</div>
</div>
@@ -195,7 +195,7 @@
<label class="col-sm-3 col-form-label fw-bold">${getString('Network_Parent')}</label>
<div class="col-sm-9">
${isRootNode ? '' : `<a class="anonymize" href="#">`}
<span my-data-mac="${node.parent_mac}" data-mytreemacmain="${node.parent_mac}" onclick="handleNodeClick(this)">
<span my-data-mac="${node.parent_mac}" data-mac="${node.parent_mac}" onclick="handleNodeClick(this)">
${isRootNode ? getString('Network_Root') : getNameByMacAddress(node.parent_mac)}
</span>
${isRootNode ? '' : `</a>`}
@@ -208,7 +208,7 @@
${getString('Network_Connected')}
</h5>
<div id="leafs_${id}"></div>
<div id="leafs_${id}" class="table-responsive"></div>
</div>
</div>
`;
@@ -239,6 +239,30 @@
const $table = $(`#${tableId}`);
const columns = [
{
title: assignMode ? getString('Network_ManageAssign') : getString('Network_ManageUnassign'),
data: 'devMac',
orderable: false,
width: '5%',
render: function (mac) {
const label = assignMode ? 'assign' : 'unassign';
const btnClass = assignMode ? 'btn-primary' : 'btn-primary bg-red';
const btnText = assignMode ? getString('Network_ManageAssign') : getString('Network_ManageUnassign');
return `<button class="btn ${btnClass} btn-sm" data-myleafmac="${mac}" onclick="updateLeaf('${mac}','${label}')">
${btnText}
</button>`;
}
},
{
title: getString('Device_TableHead_Name'),
data: 'devName',
width: '15%',
render: function (name, type, device) {
return `<a href="./deviceDetails.php?mac=${device.devMac}" target="_blank">
<b class="anonymize">${name || '-'}</b>
</a>`;
}
},
{
title: getString('Network_Table_State'),
data: 'devStatus',
@@ -252,16 +276,6 @@
return `<a href="${badge.url}" class="badge ${badge.cssClass}">${badge.iconHtml} ${badge.status}</a>`;
}
},
{
title: getString('Device_TableHead_Name'),
data: 'devName',
width: '15%',
render: function (name, type, device) {
return `<a href="./deviceDetails.php?mac=${device.devMac}">
<b class="anonymize">${name || '-'}</b>
</a>`;
}
},
{
title: 'MAC',
data: 'devMac',
@@ -277,21 +291,7 @@
title: getString('Device_TableHead_Vendor'),
data: 'devVendor',
width: '20%'
},
{
title: assignMode ? getString('Network_ManageAssign') : getString('Network_ManageUnassign'),
data: 'devMac',
orderable: false,
width: '5%',
render: function (mac) {
const label = assignMode ? 'assign' : 'unassign';
const btnClass = assignMode ? 'btn-primary' : 'btn-primary bg-red';
const btnText = assignMode ? getString('Network_ManageAssign') : getString('Network_ManageUnassign');
return `<button class="btn ${btnClass} btn-sm" data-myleafmac="${mac}" onclick="updateLeaf('${mac}','${label}')">
${btnText}
</button>`;
}
}
}
].filter(Boolean);
@@ -302,7 +302,10 @@
order: assignMode ? [[2, 'asc']] : [],
responsive: true,
autoWidth: false,
searching: true
searching: true,
createdRow: function (row, data) {
$(row).attr('data-mac', data.devMac);
}
}
if ($.fn.DataTable.isDataTable($table)) {
@@ -325,7 +328,7 @@
const wrapperHtml = `
<div class="content">
<div id="unassignedDevices" class="box box-aqua box-body">
<div id="unassignedDevices" class="box box-aqua box-body table-responsive">
<section>
<h5><i class="fa-solid fa-plug-circle-xmark"></i> ${getString('Network_UnassignedDevices')}</h5>
<table id="unassignedDevicesTable" class="table table-striped" width="100%"></table>
@@ -357,7 +360,7 @@
const id = node_mac.replace(/:/g, '_');
const wrapperHtml = `
<table class="table table-bordered table-striped node-leafs-table" id="table_leafs_${id}" data-node-mac="${node_mac}">
<table class="table table-bordered table-striped node-leafs-table " id="table_leafs_${id}" data-node-mac="${node_mac}">
</table>`;
@@ -375,98 +378,98 @@
// -----------------------------------------------------------
const networkDeviceTypes = getSetting("NETWORK_DEVICE_TYPES").replace("[", "").replace("]", "");
const showArchived = getCache('showArchived') === "true";
const showOffline = getCache('showOffline') === "true";
const showArchived = getCache('showArchived') === "true";
const showOffline = getCache('showOffline') === "true";
console.log('showArchived:', showArchived);
console.log('showOffline:', showOffline);
console.log('showArchived:', showArchived);
console.log('showOffline:', showOffline);
// Always get all devices
const rawSql = `
SELECT *,
CASE
WHEN devAlertDown != 0 AND devPresentLastScan = 0 THEN "Down"
WHEN devPresentLastScan = 1 THEN "On-line"
ELSE "Off-line"
END AS devStatus,
CASE
WHEN devType IN (${networkDeviceTypes}) THEN 1
ELSE 0
END AS devIsNetworkNodeDynamic
FROM Devices a
`;
// Always get all devices
const rawSql = `
SELECT *,
CASE
WHEN devAlertDown != 0 AND devPresentLastScan = 0 THEN "Down"
WHEN devPresentLastScan = 1 THEN "On-line"
ELSE "Off-line"
END AS devStatus,
CASE
WHEN devType IN (${networkDeviceTypes}) THEN 1
ELSE 0
END AS devIsNetworkNodeDynamic
FROM Devices a
`;
const apiUrl = `php/server/dbHelper.php?action=read&rawSql=${btoa(encodeURIComponent(rawSql))}`;
const apiUrl = `php/server/dbHelper.php?action=read&rawSql=${btoa(encodeURIComponent(rawSql))}`;
$.get(apiUrl, function (data) {
$.get(apiUrl, function (data) {
console.log(data);
const parsed = JSON.parse(data);
const allDevices = parsed;
console.log(data);
const parsed = JSON.parse(data);
const allDevices = parsed;
console.log(allDevices);
console.log(allDevices);
if (!allDevices || allDevices.length === 0) {
showModalOK(getString('Gen_Warning'), getString('Network_NoDevices'));
return;
}
// Count totals for UI
let archivedCount = 0;
let offlineCount = 0;
allDevices.forEach(device => {
if (parseInt(device.devIsArchived) === 1) archivedCount++;
if (parseInt(device.devPresentLastScan) === 0 && parseInt(device.devIsArchived) === 0) offlineCount++;
});
if(archivedCount > 0)
{
$('#showArchivedNumber').text(`(${archivedCount})`);
}
if(offlineCount > 0)
{
$('#showOfflineNumber').text(`(${offlineCount})`);
}
// Now apply UI filter based on toggles
const filteredDevices = allDevices.filter(device => {
if (!showArchived && parseInt(device.devIsArchived) === 1) return false;
if (!showOffline && parseInt(device.devPresentLastScan) === 0) return false;
return true;
});
// Sort filtered devices
const orderTopologyBy = createArray(getSetting("UI_TOPOLOGY_ORDER"));
const devicesSorted = filteredDevices.sort((a, b) => {
const parsePort = (port) => {
const parsed = parseInt(port, 10);
return isNaN(parsed) ? Infinity : parsed;
};
switch (orderTopologyBy[0]) {
case "Name":
const nameCompare = a.devName.localeCompare(b.devName);
return nameCompare !== 0 ? nameCompare : parsePort(a.devParentPort) - parsePort(b.devParentPort);
case "Port":
return parsePort(a.devParentPort) - parsePort(b.devParentPort);
default:
return a.rowid - b.rowid;
if (!allDevices || allDevices.length === 0) {
showModalOK(getString('Gen_Warning'), getString('Network_NoDevices'));
return;
}
// Count totals for UI
let archivedCount = 0;
let offlineCount = 0;
allDevices.forEach(device => {
if (parseInt(device.devIsArchived) === 1) archivedCount++;
if (parseInt(device.devPresentLastScan) === 0 && parseInt(device.devIsArchived) === 0) offlineCount++;
});
if(archivedCount > 0)
{
$('#showArchivedNumber').text(`(${archivedCount})`);
}
if(offlineCount > 0)
{
$('#showOfflineNumber').text(`(${offlineCount})`);
}
// Now apply UI filter based on toggles
const filteredDevices = allDevices.filter(device => {
if (!showArchived && parseInt(device.devIsArchived) === 1) return false;
if (!showOffline && parseInt(device.devPresentLastScan) === 0) return false;
return true;
});
// Sort filtered devices
const orderTopologyBy = createArray(getSetting("UI_TOPOLOGY_ORDER"));
const devicesSorted = filteredDevices.sort((a, b) => {
const parsePort = (port) => {
const parsed = parseInt(port, 10);
return isNaN(parsed) ? Infinity : parsed;
};
switch (orderTopologyBy[0]) {
case "Name":
const nameCompare = a.devName.localeCompare(b.devName);
return nameCompare !== 0 ? nameCompare : parsePort(a.devParentPort) - parsePort(b.devParentPort);
case "Port":
return parsePort(a.devParentPort) - parsePort(b.devParentPort);
default:
return a.rowid - b.rowid;
}
});
setCache('devicesListNew', JSON.stringify(devicesSorted));
deviceListGlobal = devicesSorted;
// Render filtered result
initTree(getHierarchy());
loadNetworkNodes();
attachTreeEvents();
});
setCache('devicesListNew', JSON.stringify(devicesSorted));
deviceListGlobal = devicesSorted;
// Render filtered result
initTree(getHierarchy());
loadNetworkNodes();
attachTreeEvents();
});
</script>
@@ -589,23 +592,63 @@ function attachTreeEvents()
// Handle network node click - select correct tab in the bottom table
function handleNodeClick(el)
{
const targetTabMAC = $(el).attr("data-mytreemacmain");
// handle network node
isNetworkDevice = $(el).data("devisnetworknodedynamic") == 1;
targetTabMAC = ""
thisDevMac= $(el).data("mac");
if (isNetworkDevice == false)
{
targetTabMAC = $(el).data("parentmac");
} else
{
targetTabMAC = thisDevMac;
}
var targetTab = $(`a[data-mytabmac="${targetTabMAC}"]`);
if (targetTab.length) {
// Simulate a click event on the target tab
targetTab.click();
}
if (isNetworkDevice) {
// 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)
} else {
$("tr.selected").removeClass("selected");
$(`tr[data-mac="${thisDevMac}"]`).addClass("selected");
const tableId = "table_leafs_" + targetTabMAC.replace(/:/g, '_');
const $table = $(`#${tableId}`).DataTable();
// Find the row index (in the full data set) that matches
const rowIndex = $table
.rows()
.eq(0)
.filter(function(idx) {
return $table.row(idx).node().getAttribute("data-mac") === thisDevMac;
});
if (rowIndex.length > 0) {
// Change to the page where this row is
$table.page(Math.floor(rowIndex[0] / $table.page.len())).draw(false);
// Delay needed so the row is in the DOM after page draw
setTimeout(() => {
const rowNode = $table.row(rowIndex[0]).node();
$(rowNode).addClass("selected");
// Smooth scroll to the row
$('html, body').animate({
scrollTop: $(rowNode).offset().top - 50
}, 500);
}, 0);
}
}
}
@@ -712,7 +755,8 @@ function initTree(myHierarchy)
class="node-inner hover-node-info box pointer ${highlightedCss} ${cssNodeType}"
style="height:${nodeHeightPx}px;font-size:${nodeHeightPx-5}px;"
onclick="handleNodeClick(this)"
data-mytreemacmain="${nodeData.data.mac}"
data-mac="${nodeData.data.mac}"
data-parentMac="${nodeData.data.parentMac}"
data-name="${nodeData.data.name}"
data-ip="${nodeData.data.ip}"
data-mac="${nodeData.data.mac}"
@@ -818,7 +862,7 @@ function initSelectedNodeHighlighting()
$(selNode).attr('class', $(selNode).attr('class').replace('highlightedNode'))
}
newSelNode = $("#networkTree div[data-mytreemacmain='"+currentNodeMac+"']")[0]
newSelNode = $("#networkTree div[data-mac='"+currentNodeMac+"']")[0]
console.log(newSelNode)

View File

@@ -107,7 +107,7 @@
"DevDetail_Network_Node_hover": "Select the parent network device the current device is connected to, to populate the Network tree.",
"DevDetail_Network_Port_hover": "The port this device is connected to on the parent network device. If left empty a wifi icon is displayed in the Network tree.",
"DevDetail_Nmap_Scans": "Manual Nmap Scans",
"DevDetail_Nmap_Scans_desc": "Here you can execute manual NMAP scans. You can also schedule regular automatic NMAP scans via the Services & Ports (NMAP) plugin. Head to <a href='/settings.php' target='_blank'>Settings</a> to find out more",
"DevDetail_Nmap_Scans_desc": "Here you can execute manual NMAP scans. You can also schedule regular automatic NMAP scans via the Services & Ports (NMAP) plugin. Head to <a href=\"/settings.php\" target=\"_blank\">Settings</a> to find out more",
"DevDetail_Nmap_buttonDefault": "Default Scan",
"DevDetail_Nmap_buttonDefault_text": "Default Scan: Nmap scans the top 1,000 ports for each scan protocol requested. This catches roughly 93% of the TCP ports and 49% of the UDP ports. (about 5 seconds)",
"DevDetail_Nmap_buttonDetail": "Detailed Scan",

0
front/php/templates/language/it_it.json Normal file → Executable file
View File