Files
NetAlertX/front/devices.php
2025-01-20 14:55:26 +11:00

941 lines
32 KiB
PHP
Executable File

<!--
#---------------------------------------------------------------------------------#
# NetAlertX #
# Open Source Network Guard / WIFI & LAN intrusion detector #
# #
# devices.php - Front module. Devices list page #
#---------------------------------------------------------------------------------#
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3 #
# jokob-sk 2022 jokob.sk@gmail.com GNU GPLv3 #
# leiweibau 2022 https://github.com/leiweibau GNU GPLv3 #
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
#---------------------------------------------------------------------------------#
-->
<?php
require 'php/templates/header.php';
// check permissions
$dbPath = "../db/app.db";
$confPath = "../config/app.conf";
checkPermissions([$dbPath, $confPath]);
?>
<!-- ----------------------------------------------------------------------- -->
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper">
<!-- Main content ---------------------------------------------------------- -->
<section class="content">
<!-- Tile toggle cards ------------------------------------------------------- -->
<div class="row" id="TileCards">
<!-- Placeholder ------------------------------------------------------- -->
</div>
<!-- Device presence / Activity Chart ------------------------------------------------------- -->
<div class="row" id="DevicePresence">
<div class="col-md-12">
<div class="box" id="clients">
<div class="box-header with-border">
<h3 class="box-title"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
</div>
<div class="box-body">
<div class="chart">
<script src="lib/chart.js/Chart.js?v=<?php include 'php/templates/version.php'; ?>"></script>
<!-- presence chart -->
<?php
require 'php/components/graph_online_history.php';
?>
</div>
</div>
<!-- /.box-body -->
</div>
</div>
</div>
<!-- datatable ------------------------------------------------------------- -->
<div class="row">
<div class="col-xs-12">
<div id="tableDevicesBox" class="box">
<!-- box-header -->
<div class="box-header">
<div class=" col-md-9 ">
<h3 id="tableDevicesTitle" class="box-title text-gray "></h3>
</div>
<div class="dummyDevice col-md-3 ">
<span id="multiEditPlc">
<!-- multi edit button placeholder -->
</span>
<span>
<a href="deviceDetails.php?mac=new"><i title="<?= lang('Gen_create_new_device');?>" class="fa fa-square-plus"></i> <?= lang('Gen_create_new_device');?></a>
</span>
</div>
</div>
<!-- table -->
<div class="box-body table-responsive">
<table id="tableDevices" class="table table-bordered table-hover table-striped">
<thead>
<tr>
</tr>
</thead>
</table>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
<!-- /.col -->
</div>
<!-- /.row -->
<!-- ----------------------------------------------------------------------- -->
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
<!-- ----------------------------------------------------------------------- -->
<?php
require 'php/templates/footer.php';
?>
<!-- page script ----------------------------------------------------------- -->
<script>
var deviceStatus = 'all';
var tableRows = getCache ("nax_parTableRows") == "" ? 10 : getCache ("nax_parTableRows") ;
var tableOrder = getCache ("nax_parTableOrder") == "" ? [[3,'desc'], [0,'asc']] : JSON.parse(getCache ("nax_parTableOrder")) ;
var tableColumnHide = [];
var tableColumnOrder = [];
var tableColumnVisible = [];
headersDefaultOrder = [];
missingNumbers = [];
// Read parameters & Initialize components
callAfterAppInitialized(main)
showSpinner();
// -----------------------------------------------------------------------------
function main () {
showSpinner();
// render tiles
getDevicesTotals();
//initialize the table headers in the correct order
var availableColumns = getSettingOptions("UI_device_columns").split(",");
headersDefaultOrder = availableColumns.map(val => getString(val));
console.log(headersDefaultOrder);
var selectedColumns = JSON.parse(getSetting("UI_device_columns").replace(/'/g, '"'));
// generate default order lists of given length
var columnsStr = JSON.stringify(Array.from({ length: headersDefaultOrder.length }, (_, i) => i));
tableColumnOrder = Array.from({ length: headersDefaultOrder.length }, (_, i) => i);
tableColumnVisible = [];
// Initialize tableColumnVisible by including all columns from selectedColumns, preserving their order.
tableColumnVisible = selectedColumns.map(column => availableColumns.indexOf(column)).filter(index => index !== -1);
// Add any columns from availableColumns that are not in selectedColumns to the end.
const remainingColumns = availableColumns.map((column, index) => index).filter(index => !tableColumnVisible.includes(index));
// Combine both arrays.
tableColumnOrder = tableColumnVisible.concat(remainingColumns);
// Generate the full array of numbers from 0 to totalLength - 1 of tableColumnOrder
const fullArray = Array.from({ length: tableColumnOrder.length }, (_, i) => i);
// Filter out the elements already present in inputArray
missingNumbers = fullArray.filter(num => !tableColumnVisible.includes(num));
// Concatenate the inputArray with the missingNumbers
tableColumnOrder = [...tableColumnVisible, ...missingNumbers];
// Initialize components with parameters
initializeDatatable(getUrlAnchor('my_devices'));
// check if data outdated and show spinner if so
handleLoadingDialog()
}
// -----------------------------------------------------------------------------
// mapping the default order to the user specified one
function mapIndx(oldIndex)
{
// console.log(oldIndex);
// console.log(tableColumnOrder);
for(i=0;i<tableColumnOrder.length;i++)
{
if(tableColumnOrder[i] == oldIndex)
{
return i;
}
}
}
//------------------------------------------------------------------------------
// Query total numbers of Devices by status
//------------------------------------------------------------------------------
function getDevicesTotals() {
maxDelay = 180; //cap at 180 seconds
let maxRetries = Math.ceil(Math.log2(maxDelay)); // Calculate maximum retries to cap at maxDelay seconds
let attempt = 0;
let calledUpdateAPI = false;
function fetchDataWithBackoff() {
// Calculate the delay (2^attempt seconds, capped at maxDelay seconds)
const delay = Math.min(2 ** attempt, maxDelay) * 1000;
// Attempt to fetch data
$.ajax({
url: '/php/server/query_json.php',
type: "GET",
dataType: "json",
data: {
file: 'table_devices_tiles.json', // Pass the file parameter
nocache: Date.now() // Prevent caching with a timestamp
},
success: function(response) {
if (response && response.data) {
const resultJSON = response.data[0]; // Assuming the structure {"data": [ ... ]}
// Save the result to cache
setCache("getDevicesTotals", JSON.stringify(resultJSON));
// Process the fetched data
processDeviceTotals(resultJSON);
} else {
console.error("Invalid response format from API");
}
},
error: function(xhr, status, error) {
console.error("Failed to fetch devices data (Attempt " + (attempt + 1) + "):", error);
// try updating the API once
if(calledUpdateAPI == false)
{
calledUpdateAPI = true;
updateApi("devices_tiles");
}
// Retry logic
if (attempt < maxRetries) {
attempt++;
setTimeout(fetchDataWithBackoff, delay);
} else {
console.error("Maximum retries reached. Unable to fetch devices data.");
}
}
});
}
// Start the first fetch attempt
fetchDataWithBackoff();
}
function processDeviceTotals(devicesData) {
// Define filter conditions and corresponding objects
const filters = [
{ status: 'my_devices', color: 'bg-aqua', label: getString('Device_Shortcut_AllDevices'), icon: 'fa-laptop' },
{ status: 'all', color: 'bg-aqua', label: getString('Gen_All_Devices'), icon: 'fa-laptop' },
{ status: 'connected', color: 'bg-green', label: getString('Device_Shortcut_Connected'), icon: 'fa-plug' },
{ status: 'favorites', color: 'bg-yellow', label: getString('Device_Shortcut_Favorites'), icon: 'fa-star' },
{ status: 'new', color: 'bg-yellow', label: getString('Device_Shortcut_NewDevices'), icon: 'fa-plus' },
{ status: 'down', color: 'bg-red', label: getString('Device_Shortcut_DownOnly'), icon: 'fa-warning' },
{ status: 'archived', color: 'bg-gray', label: getString('Device_Shortcut_Archived'), icon: 'fa-eye-slash' },
{ status: 'offline', color: 'bg-gray', label: getString('Gen_Offline'), icon: 'fa-xmark' }
];
// Initialize an empty array to store the final objects
let dataArray = [];
// Loop through each filter condition
filters.forEach(filter => {
// Get count directly from API response data
let count = devicesData[filter.status] || 0;
// Check any condition to skip adding the object to dataArray
if (
(['', 'False'].includes(getSetting('UI_hide_empty')) || (getSetting('UI_hide_empty') == "True" && count > 0)) &&
(getSetting('UI_shown_cards') == "" || getSetting('UI_shown_cards').includes(filter.status))
) {
dataArray.push({
onclickEvent: `forceLoadUrl('devices.php#${filter.status}')`,
color: filter.color,
title: count,
label: filter.label,
icon: filter.icon
});
}
});
// Render info boxes/tile cards
console.log(getSetting('UI_hide_empty'));
console.log(dataArray);
console.log(devicesData);
renderInfoboxes(dataArray);
}
//------------------------------------------------------------------------------
// Render the info boxes/tiles on top
function renderInfoboxes(customData) {
if(customData.length > 0)
{
$.ajax({
url: 'php/components/tile_cards.php', // PHP script URL
type: 'POST', // Use POST method to send data
dataType: 'html', // Expect HTML response
data: { items: JSON.stringify(customData) }, // Send customData as JSON
success: function(response) {
$('#TileCards').html(response); // Replace container content with fetched HTML
},
error: function(xhr, status, error) {
console.error('Error fetching infoboxes:', error);
}
});
}
}
// -----------------------------------------------------------------------------
// Map column index to column name for GraphQL query
function mapColumnIndexToFieldName(index, tableColumnVisible) {
// the order is important, don't change it!
const columnNames = [
"devName",
"devOwner",
"devType",
"devIcon",
"devFavorite",
"devGroup",
"devFirstConnection",
"devLastConnection",
"devLastIP",
"devIsRandomMac", // resolved on the fly
"devStatus", // resolved on the fly
"devMac",
"devIpLong", //formatIPlong(device.devLastIP) || "", // IP orderable
"rowid",
"devParentMAC",
"devParentChildrenCount", // resolved on the fly
"devLocation",
"devVendor",
"devParentPort",
"devGUID",
"devSyncHubNode",
"devSite",
"devSSID",
"devSourcePlugin",
"devPresentLastScan",
"devAlertDown",
"devCustomProps"
];
// console.log("OrderBy: " + columnNames[tableColumnOrder[index]]);
return columnNames[tableColumnOrder[index]] || null;
}
// ---------------------------------------------------------
// Initializes the main devices list datatable
function initializeDatatable (status) {
if(!status)
{
status = 'my_devices'
}
// Save status selected
deviceStatus = status;
// Define color & title for the status selected
switch (deviceStatus) {
case 'my_devices': tableTitle = getString('Device_Shortcut_AllDevices'); color = 'aqua'; break;
case 'connected': tableTitle = getString('Device_Shortcut_Connected'); color = 'green'; break;
case 'all': tableTitle = getString('Gen_All_Devices'); color = 'aqua'; break;
case 'favorites': tableTitle = getString('Device_Shortcut_Favorites'); color = 'yellow'; break;
case 'new': tableTitle = getString('Device_Shortcut_NewDevices'); color = 'yellow'; break;
case 'down': tableTitle = getString('Device_Shortcut_DownOnly'); color = 'red'; break;
case 'archived': tableTitle = getString('Device_Shortcut_Archived'); color = 'gray'; break;
case 'offline': tableTitle = getString('Gen_Offline'); color = 'gray'; break;
default: tableTitle = getString('Device_Shortcut_Devices'); color = 'gray'; break;
}
// Set title and color
$('#tableDevicesTitle')[0].className = 'box-title text-'+ color;
$('#tableDevicesBox')[0].className = 'box box-'+ color;
$('#tableDevicesTitle').html (tableTitle);
// render table headers
html = '';
for(index = 0; index < tableColumnOrder.length; index++)
{
html += '<th>' + headersDefaultOrder[tableColumnOrder[index]] + '</th>';
}
$('#tableDevices tr').html(html);
hideUIelements("UI_DEV_SECTIONS")
for(i = 0; i < tableColumnOrder.length; i++)
{
// hide this column if not in the tableColumnVisible variable (we need to keep the MAC address (index 11) for functionality reasons)
if(tableColumnVisible.includes(tableColumnOrder[i]) == false)
{
tableColumnHide.push(mapIndx(tableColumnOrder[i]));
}
}
// todo: dynamically filter based on status
var table = $('#tableDevices').DataTable({
"serverSide": true,
"processing": true,
"ajax": {
"url": 'php/server/query_graphql.php', // PHP endpoint that proxies to the GraphQL server
"type": "POST",
"contentType": "application/json",
"data": function (d) {
// Construct GraphQL query with pagination and sorting options
let graphqlQuery = `
query devices($options: PageQueryOptionsInput) {
devices(options: $options) {
devices {
rowid
devMac
devName
devOwner
devType
devVendor
devFavorite
devGroup
devComments
devFirstConnection
devLastConnection
devLastIP
devStaticIP
devScan
devLogEvents
devAlertEvents
devAlertDown
devSkipRepeated
devLastNotification
devPresentLastScan
devIsNew
devIsRandomMac
devLocation
devIsArchived
devParentMAC
devParentPort
devIcon
devGUID
devSite
devSSID
devSyncHubNode
devSourcePlugin
devStatus
devParentChildrenCount
devIpLong
devCustomProps
}
count
}
}
`;
console.log(d);
// Prepare query variables for pagination, sorting, and search
let query = {
"operationName": null,
"query": graphqlQuery,
"variables": {
"options": {
"page": Math.floor(d.start / d.length) + 1, // Page number (1-based)
"limit": parseInt(d.length, 10), // Page size (ensure it's an integer)
"sort": d.order && d.order[0] ? [{
"field": mapColumnIndexToFieldName(d.order[0].column, tableColumnVisible), // Sort field from DataTable column
"order": d.order[0].dir.toUpperCase() // Sort direction (ASC/DESC)
}] : [], // Default to an empty array if no sorting is defined
"search": d.search.value, // Search query
"status": deviceStatus
}
}
};
return JSON.stringify(query); // Send the JSON request
},
"dataSrc": function (json) {
console.log(json);
// Set the total number of records for pagination
json.recordsTotal = json.devices.count || 0;
json.recordsFiltered = json.devices.count || 0;
return json.devices.devices.map(device => {
// Convert each device record into the required DataTable row format
// Order has to be the same as in the UI_device_columns setting options
const originalRow = [
device.devName || "",
device.devOwner || "",
device.devType || "",
device.devIcon || "",
device.devFavorite || "",
device.devGroup || "",
device.devFirstConnection || "",
device.devLastConnection || "",
device.devLastIP || "",
device.devIsRandomMac || "", // Custom logic for randomized MAC
device.devStatus || "",
device.devMac || "", // hidden
device.devIpLong || "", // IP orderable
device.rowid || "",
device.devParentMAC || "",
device.devParentChildrenCount || 0,
device.devLocation || "",
device.devVendor || "",
device.devParentPort || "",
device.devGUID || "",
device.devSyncHubNode || "",
device.devSite || "",
device.devSSID || "",
device.devSourcePlugin || "",
device.devPresentLastScan || "",
device.devAlertDown || "",
device.devCustomProps || ""
];
const newRow = [];
// Reorder data based on user-defined columns order
for (let index = 0; index < tableColumnOrder.length; index++) {
newRow.push(originalRow[tableColumnOrder[index]]);
}
return newRow;
});
}
},
'paging' : true,
'lengthChange' : true,
'lengthMenu' : [[10, 25, 50, 100, 500, 100000], [10, 25, 50, 100, 500, getString('Device_Tablelenght_all')]],
'searching' : true,
'ordering' : true,
'info' : true,
'autoWidth' : false,
// Parameters
'pageLength' : tableRows,
'order' : tableOrder,
'select' : true, // Enable selection
'fixedHeader': true,
'fixedHeader': {
'header': true,
'footer': true
},
'columnDefs' : [
{visible: false, targets: tableColumnHide },
{className: 'text-center', targets: [mapIndx(4), mapIndx(9), mapIndx(10), mapIndx(15), mapIndx(18)] },
{className: 'iconColumn text-center', targets: [mapIndx(3)]},
{width: '80px', targets: [mapIndx(6), mapIndx(7), mapIndx(15)] },
{width: '85px', targets: [mapIndx(9)] },
{width: '30px', targets: [mapIndx(3), mapIndx(10), mapIndx(13), mapIndx(18)] },
{orderData: [mapIndx(12)], targets: mapIndx(8) },
// Device Name
{targets: [mapIndx(0)],
'createdCell': function (td, cellData, rowData, row, col) {
// console.log(cellData)
$(td).html ('<b class="anonymizeDev"><a href="deviceDetails.php?mac='+ rowData[mapIndx(11)] +'" class="">'+ cellData +'</a></b>');
} },
// Connected Devices
{targets: [mapIndx(15)],
'createdCell': function (td, cellData, rowData, row, col) {
// check if this is a network device
if(getSetting("NETWORK_DEVICE_TYPES").includes(`'${rowData[mapIndx(2)]}'`) )
{
$(td).html ('<b><a href="./network.php?mac='+ rowData[mapIndx(11)] +'" class="">'+ cellData +'</a></b>');
}
else
{
$(td).html (`<i class="fa-solid fa-xmark" title="${getString("Device_Table_Not_Network_Device")}"></i>`)
}
} },
// Icon
{targets: [mapIndx(3)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html (atob(cellData));
} else {
$(td).html ('');
}
} },
// Full MAC
{targets: [mapIndx(11)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html ('<span class="anonymizeMac">'+cellData+'</span>');
} else {
$(td).html ('');
}
} },
// IP address
{targets: [mapIndx(8)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html (`<span class="anonymizeIp">
<a href="http://${cellData}" class="pointer" target="_blank">
${cellData}
</a>
<span class="alignRight">
<a href="https://${cellData}" class="pointer" target="_blank">
<i class="fa fa-lock "></i>
</a>
<span>
<span>`);
} else {
$(td).html ('');
}
}
},
// IP address (ordeable)
{targets: [mapIndx(12)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html (`<span class="anonymizeIp">${cellData}<span>`);
} else {
$(td).html ('');
}
}
},
// Custom Properties
{targets: [mapIndx(26)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html (`<span>${renderCustomProps(cellData, rowData[mapIndx(11)])}<span>`);
} else {
$(td).html ('');
}
}
},
// Favorite
{targets: [mapIndx(4)],
'createdCell': function (td, cellData, rowData, row, col) {
if (cellData == 1){
$(td).html ('<i class="fa fa-star text-yellow" style="font-size:16px"></i>');
} else {
$(td).html ('');
}
} },
// Dates
{targets: [mapIndx(6), mapIndx(7)],
'createdCell': function (td, cellData, rowData, row, col) {
var result = cellData.toString(); // Convert to string
if (result.includes("+")) { // Check if timezone offset is present
result = result.split('+')[0]; // Remove timezone offset
}
$(td).html (translateHTMLcodes (result));
} },
// Random MAC
{targets: [mapIndx(9)],
'createdCell': function (td, cellData, rowData, row, col) {
// console.log(cellData)
if (cellData == 1){
$(td).html ('<i data-toggle="tooltip" data-placement="right" title="Random MAC" class="fa-solid fa-shuffle"></i>');
} else {
$(td).html ('');
}
} },
// Status color
{targets: [mapIndx(10)],
'createdCell': function (td, cellData, rowData, row, col) {
tmp_devPresentLastScan = rowData[mapIndx(24)]
tmp_devAlertDown = rowData[mapIndx(25)]
if (tmp_devPresentLastScan == 1)
{
css = "green text-white statusOnline"
icon = '<i class="fa-solid fa-plug"></i>'
} else if (tmp_devPresentLastScan != 1 && tmp_devAlertDown == 1)
{
css = "red text-white statusDown"
icon = '<i class="fa-solid fa-triangle-exclamation"></i>'
} else if(tmp_devPresentLastScan != 1)
{
css = "gray text-white statusOffline"
icon = '<i class="fa-solid fa-xmark"></i>'
} else
{
css = "gray text-white statusUnknown"
icon = '<i class="fa-solid fa-question"></i>'
}
$(td).html (`<a href="deviceDetails.php?mac=${rowData[mapIndx(11)]}" class="badge bg-${css}">${icon} ${cellData.replace('-', '')}</a>`);
} },
],
// Processing
'processing' : true,
'language' : {
emptyTable: 'No data',
"lengthMenu": "<?= lang('Device_Tablelenght');?>",
"search": "<?= lang('Device_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Device_Table_nav_next');?>",
"previous": "<?= lang('Device_Table_nav_prev');?>"
},
"info": "<?= lang('Device_Table_info');?>",
},
initComplete: function (settings, devices) {
// Handle any additional interactions or event listeners as required
// Save cookie Rows displayed, and Parameters rows & order
$('#tableDevices').on( 'length.dt', function ( e, settings, len ) {
setCache ("nax_parTableRows", len, 129600); // save for 90 days
} );
$('#tableDevices').on( 'order.dt', function () {
setCache ("nax_parTableOrder", JSON.stringify (table.order()), 129600); // save for 90 days
} );
// add multi-edit button
$('#multiEditPlc').append(
`<span type="submit" id="multiEdit" class="pointer " style="display:none" onclick="multiEditDevices();">
<a href="#"><i class="fa fa-pencil " ></i> ${getString("Device_MultiEdit")} </a>
</span>`)
// Event listener for row selection in DataTable
$('#tableDevices').on('click', 'tr', function (e) {
setTimeout(function(){
// Check if any row is selected
var anyRowSelected = $('#tableDevices tr.selected').length > 0;
// Toggle visibility of element with ID 'multiEdit'
$('#multiEdit').toggle(anyRowSelected);
}, 100);
});
// search only after idle
var typingTimer; // Timer identifier
var debounceTime = 500; // Delay in milliseconds
$('input[aria-controls="tableDevices"]').off().on('keyup', function () {
clearTimeout(typingTimer); // Clear the previous timer
var searchValue = this.value;
typingTimer = setTimeout(function () {
$('#tableDevices').DataTable().search(searchValue).draw(); // Trigger the search after delay
}, debounceTime);
});
hideSpinner();
},
createdRow: function(row, data, dataIndex) {
// add devMac to the table row
$(row).attr('my-devMac', data[mapIndx(11)]);
}
});
}
// -----------------------------------------------------------------------------
function handleLoadingDialog(needsReload = false)
{
// console.log(`needsReload: ${needsReload}`);
$.get('/php/server/query_logs.php?file=execution_queue.log&nocache=' + Date.now(), function(data) {
if(data.includes("update_api|devices"))
{
showSpinner("devices_old")
setTimeout(handleLoadingDialog(true), 1000);
} else if (needsReload)
{
location.reload();
}else
{
// hideSpinner();
}
})
}
// -----------------------------------------------------------------------------
// Function collects selected devices in the DataTable and redirects the user to
// the Miantenance section with a 'macs' query string identifying selected devices
function multiEditDevices()
{
// get selected devices
var selectedDevicesDataTableData = $('#tableDevices').DataTable().rows({ selected: true, page: 'current' }).data().toArray();
console.log(selectedDevicesDataTableData);
macs = ""
for (var j = 0; j < selectedDevicesDataTableData.length; j++) {
macs += selectedDevicesDataTableData[j][mapIndx(11)] + ","; // [11] == MAC
}
// redirect to the Maintenance section
window.location.href = './maintenance.php#tab_multiEdit?macs=' + macs.slice(0, -1);
}
// -----------------------------------------------------------------------------
// Function collects shown devices from the DataTable
function getMacsOfShownDevices() {
rows = $('#tableDevices')[0].rows;
macs = [];
// var devicesDataTableData = $('#tableDevices').dataTable().fnGetData();
var devicesDataTableData = $('#tableDevices').DataTable().rows({ selected: false, page: 'current' }).data().toArray();
console.log(devicesDataTableData);
var selectedDevices = [];
// first row is the heading, skip
for (var i = 1; i < rows.length; i++) {
var rowIndex = rows[i]._DT_RowIndex;
// Ensure the rowIndex is valid and within bounds of devicesDataTableData
if (rowIndex >= 0 && rowIndex < devicesDataTableData.length) {
selectedDevices.push(devicesDataTableData[rowIndex]);
} else {
console.log(`Invalid rowIndex: ${rowIndex} at row ${i}`);
}
}
for (var j = 0; j < selectedDevices.length; j++) {
// Ensure that selectedDevices[j] is not undefined
if (selectedDevices[j]) {
macs.push(selectedDevices[j][mapIndx(11)]); // mapIndx(11) == MAC
} else {
console.log(`selectedDevices[${j}] is undefined`);
}
}
return macs;
}
// -----------------------------------------------------------------------------
// Handle custom actions/properties on a device
function renderCustomProps(custProps, mac) {
// Decode and parse the custom properties
if (!isBase64(custProps)) {
console.error(`Unable to decode CustomProps for ${mac}`);
console.error(custProps);
} else{
const props = JSON.parse(atob(custProps));
let html = "";
props.forEach((propGroup, index) => {
const propMap = Object.fromEntries(
propGroup.map(prop => Object.entries(prop)[0]) // Convert array of objects to key-value pairs
);
if (propMap["CUSTPROP_show"] === true) { // Render if visible
let onClickEvent = "";
switch (propMap["CUSTPROP_type"]) {
case "show_notes":
onClickEvent = `showModalOK('${propMap["CUSTPROP_name"]}','${propMap["CUSTPROP_notes"]}')`;
break;
case "link":
onClickEvent = `window.location.href='${propMap["CUSTPROP_args"]}';`;
break;
case "link_new_tab":
onClickEvent = `openInNewTab('${propMap["CUSTPROP_args"]}')`;
break;
case "run_plugin":
onClickEvent = `alert('Not implemented')`;
break;
case "delete_dev":
onClickEvent = `askDelDevDTInline('${mac}')`;
break;
default:
break;
}
html += `<div class="pointer devicePropAction" onclick="${onClickEvent}" title="${propMap["CUSTPROP_name"]} ${propMap["CUSTPROP_args"]}"> ${atob(propMap["CUSTPROP_icon"])} </div>`;
}
});
return html;
}
return "Error, check browser Console log"
}
// -----------------------------------------------------------------------------
// Update cache with shown devices before navigating away
window.addEventListener('beforeunload', function(event) {
// Call your function here
macs = getMacsOfShownDevices();
setCache("ntx_visible_macs", macs)
});
</script>