refactor, tab async loading on focus

This commit is contained in:
jokob-sk
2025-07-26 12:58:45 +10:00
parent cd7cbcc4c8
commit 54fa2743f9
14 changed files with 535 additions and 364 deletions

View File

@@ -1,3 +1,8 @@
<span class="helpIcon">
<a target="_blank" href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/WORKFLOWS_DEBUGGING.md">
<i class="fa fa-circle-question"></i>
</a>
</span>
<section class="content">
<div class="nav-tabs-custom app-event-content" style="margin-bottom: 0px;">
<ul id="tabs-location" class="nav nav-tabs col-sm-2 hidden">

View File

@@ -2090,36 +2090,45 @@ input[readonly] {
/* -----------------------------------------------------------------------------
Spin
----------------------------------------------------------------------------- */
#loadingSpinner {
position: fixed;
z-index: 1000;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.3s ease-in-out;
pointer-events: none;
display: block;
z-index: 999;
}
#loadingSpinner.visible {
opacity: 1;
pointer-events: auto;
}
.pa_semitransparent-panel {
position: absolute;
width: 100%; /*calc (100% -40px);*/
width: 100%;
height: 100%;
left: 0;
top: 0;
display: block;
opacity: 0.8;
background-color: #fff;
z-index: 800;
opacity: 0.8;
z-index: 99;
}
.pa_spinner {
position: fixed;
left: 0;
right: 0;
position: absolute;
top: 100px;
margin-left: auto;
margin-right: auto;
left: 50%;
transform: translateX(-50%);
padding: 15px;
width: 200px;
background-color: #fff;
z-index: 801;
z-index: 1000;
}
#loadingSpinner
{
z-index: 100;
}
/* Multi-edit adjustements */
.box-header

View File

@@ -10,6 +10,16 @@
*
* Additional fixes For Pi.Alert UI by leiweibau */
:root {
--color-aqua: #00c0ef;
--color-lightblue: #3c8dbc;
--color-blue: #0060df;
--color-green: #00a65a;
--color-yellow: #f39c12;
--color-red: #dd4b39;
--color-gray: #8c8c8c;
}
:root {
--datatable-bgcolor: rgba(64, 76, 88, 0.8);
}
@@ -770,4 +780,9 @@ input[type="password"]::-webkit-caps-lock-indicator {
.thresholdFormControl
{
color:#000;
}
.btn:hover
{
color: var(--color-gray);
}

View File

@@ -11,6 +11,16 @@
* Additional fixes For Pi.Alert UI by leiweibau */
@media (prefers-color-scheme: dark) {
:root {
--color-aqua: #00c0ef;
--color-lightblue: #3c8dbc;
--color-blue: #0060df;
--color-green: #00a65a;
--color-yellow: #f39c12;
--color-red: #dd4b39;
--color-gray: #8c8c8c;
}
:root {
--datatable-bgcolor: rgba(64, 76, 88, 0.8);
@@ -781,3 +791,7 @@
color:#000;
}
.btn:hover
{
color: var(--color-gray);
}

View File

@@ -16,8 +16,9 @@
require 'php/templates/header.php';
?>
<script>
showSpinner();
</script>
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper">
@@ -125,15 +126,11 @@
<div class="tab-content" style="min-height: 430px;">
<!-- tab page 1 ------------------------------------------------------------ -->
<!--
<div class="tab-pane fade in active" id="panDetails">
-->
<div class="tab-pane fade" id="panDetails">
<div class="tab-pane fade" id="panDetails">
<?php
require 'deviceDetailsEdit.php';
?>
</div>
<!-- tab page 2 ------------------------------------------------------------ -->
@@ -141,51 +138,38 @@
<?php
require 'deviceDetailsSessions.php';
?>
</div>
<!-- tab page "Tools" ------------------------------------------------------------ -->
<div class="tab-pane fade" id="panTools">
<?php
require 'deviceDetailsTools.php';
?>
?>
</div>
<!-- tab page 3 ------------------------------------------------------------ -->
<div class="tab-pane fade table-responsive" id="panPresence">
<div class="tab-pane fade table-responsive" id="panPresence">
<?php
// Include the other page
include 'deviceDetailsPresence.php';
?>
</div>
<!-- tab page 4 ------------------------------------------------------------ -->
<div class="tab-pane fade table-responsive" id="panEvents">
<?php
// Include the other page
include 'deviceDetailsEvents.php';
?>
?>
</div>
<!-- tab page 7 ------------------------------------------------------------ -->
<div class="tab-pane fade table-responsive" id="panPlugins">
<?php
// Include the other page
include 'pluginsCore.php';
?>
</div>
</div>
@@ -273,7 +257,7 @@ function main () {
period = '1 day';
sessionsRows = 50;
eventsRows = 50;
$('#chkHideConnectionEvents')[0].checked = eval(eventsHide == 'true');
// $('#chkHideConnectionEvents')[0].checked = eval(eventsHide == 'true');
// Initialize components with parameters
@@ -282,26 +266,7 @@ function main () {
$( document ).ready(function() {
initializeTabs();
});
// // Events tab toggle conenction events
// $('input').on('ifToggled', function(event){
// // Hide / Show Events
// if (event.currentTarget.id == 'chkHideConnectionEvents') {
// getDeviceEvents();
// } else {
// // Activate save & restore
// // activateSaveRestoreData();
// // Ask skip notifications
// // if (event.currentTarget.id == 'chkArchived' ) {
// // askSkipNotifications();
// // }
// }
// });
}
@@ -328,6 +293,48 @@ function recordSwitch(direction) {
}
}
// ----------------------------------------
// Handle previous/next arrows/chevrons
function updateChevrons(currentMac) {
const devicesList = getDevicesList();
// Find the index of the device by MAC
pos = devicesList.findIndex(item => item.devMac == currentMac);
// If device not found, optionally add it or handle error
if (pos === -1) {
// If you want to add a placeholder or handle missing device:
// devicesList.push({ mac: currentMac, name: 'Unknown', type: 'Unknown' });
// pos = devicesList.length - 1;
// Or just return early if device not found
console.warn('Device with MAC not found:', currentMac);
return;
}
// Update the record number display
$('#txtRecord').html((pos + 1) + ' / ' + devicesList.length);
// Enable/disable previous button
if (pos <= 0) {
$('#btnPrevious').attr('disabled', '');
$('#btnPrevious').addClass('text-gray50');
} else {
$('#btnPrevious').removeAttr('disabled');
$('#btnPrevious').removeClass('text-gray50');
}
// Enable/disable next button
if (pos >= devicesList.length - 1) {
$('#btnNext').attr('disabled', '');
$('#btnNext').addClass('text-gray50');
} else {
$('#btnNext').removeAttr('disabled');
$('#btnNext').removeClass('text-gray50');
}
}
// -----------------------------------------------------------------------------
function performSwitch(direction)
@@ -338,7 +345,9 @@ function performSwitch(direction)
// Update the global position in the devices list variable 'pos'
if (direction === "next") {
if (pos < devicesList.length - 1) {
console.log("direction" + direction);
if (pos < devicesList.length) {
pos++;
}
} else if (direction === "prev") {
@@ -358,15 +367,12 @@ function performSwitch(direction)
}
// -----------------------------------------------------------------------------
// Activate save & restore on any value change
$(document).on('input', 'input:text', function() {
settingsChanged();
});
// -----------------------------------------------------------------------------
function initializeTabs () {
@@ -380,8 +386,6 @@ function initializeTabs () {
}
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
// $('.nav-tabs a[id='+ selectedTab +']').parent().click();
// $('.nav-tabs a[id="tabPlugins"]').tab('show');
// When changed save new current tab
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
@@ -391,11 +395,6 @@ function initializeTabs () {
// events on tab change
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr("href") // activated tab
// if(target == "#panTools")
// {
// // loadTools();
// }
});
}
@@ -482,22 +481,41 @@ async function renderSmallBoxes() {
console.error('Error in renderSmallBoxes:', error);
} finally {
// Hide loading dialog
hideSpinner();
// hideSpinner();
}
}
function updateDevicePageName(mac) {
name = getDevDataByMac(mac, "devName")
owner = getDevDataByMac(mac, "devOwner")
// Page title - Name
if (mac == "new") {
$('#pageTitle').html(`<i title="${getString("Gen_create_new_device")}" class="fa fa-square-plus"></i> ` + getString("Gen_create_new_device"));
$('#devicePageInfoPlc .inner').html(`<i class="fa fa-circle-info"></i> ` + getString("Gen_create_new_device_info"));
$('#devicePageInfoPlc').show();
} else if (owner == null || owner == '' ||
(name.toString()).indexOf(owner) != -1) {
$('#pageTitle').html(name);
$('#devicePageInfoPlc').hide();
} else {
$('#pageTitle').html(name + ' (' + owner + ')');
$('#devicePageInfoPlc').hide();
}
}
//-----------------------------------------------------------------------------------
window.onload = function async()
{
initializeTabs();
updateChevrons(mac);
updateDevicePageName(mac);
}
</script>

View File

@@ -39,7 +39,7 @@
// -------------------------------------------------------------------
// Get plugin and settings data from API endpoints
function getDeviceData(readAllData){
function getDeviceData(){
mac = getMac()
@@ -53,13 +53,13 @@
var deviceData = JSON.parse(data);
// Deactivate next previous buttons
if (readAllData) {
$('#btnPrevious').attr ('disabled','');
$('#btnPrevious').addClass ('text-gray50');
$('#btnNext').attr ('disabled','');
$('#btnNext').addClass ('text-gray50');
}
// // Deactivate next previous buttons
// if (readAllData) {
// $('#btnPrevious').attr ('disabled','');
// $('#btnPrevious').addClass ('text-gray50');
// $('#btnNext').attr ('disabled','');
// $('#btnNext').addClass ('text-gray50');
// }
// some race condition, need to implement delay
setTimeout(() => {
@@ -255,30 +255,13 @@
updateAllIconPreviews();
// update readonly fields
handleReadOnly(settingsData, disabledFields);
// Page title - Name
if (mac == "new") {
$('#pageTitle').html(`<i title="${getString("Gen_create_new_device")}" class="fa fa-square-plus"></i> ` + getString("Gen_create_new_device"));
$('#devicePageInfoPlc .inner').html(`<i class="fa fa-circle-info"></i> ` + getString("Gen_create_new_device_info"));
$('#devicePageInfoPlc').show();
} else if (deviceData['devOwner'] == null || deviceData['devOwner'] == '' ||
(deviceData['devName'].toString()).indexOf(deviceData['devOwner']) != -1) {
$('#pageTitle').html(deviceData['devName']);
$('#devicePageInfoPlc').hide();
} else {
$('#pageTitle').html(deviceData['devName'] + ' (' + deviceData['devOwner'] + ')');
$('#devicePageInfoPlc').hide();
}
handleReadOnly(settingsData, disabledFields);
};
// console.log(relevantSettings)
generateSimpleForm(relevantSettings);
// <> chevrons
updateChevrons(deviceData)
toggleNetworkConfiguration(mac == 'Internet')
initSelect2();
@@ -294,46 +277,6 @@
}
// ----------------------------------------
// Handle previous/next arrows/chevrons
function updateChevrons(deviceData) {
devicesList = getDevicesList();
// console.log(devicesList);
// Check if device is part of the devicesList
pos = devicesList.findIndex(item => item.rowid == deviceData['rowid']);
// console.log(pos);
if (pos == -1) {
devicesList.push({"rowid" : deviceData['rowid'], "mac" : deviceData['devMac'], "name": deviceData['devName'], "type": deviceData['devType']});
pos=0;
}
// Record number
$('#txtRecord').html (pos+1 +' / '+ devicesList.length);
// Deactivate previous button
if (pos <= 0) {
$('#btnPrevious').attr ('disabled','');
$('#btnPrevious').addClass ('text-gray50');
} else {
$('#btnPrevious').removeAttr ('disabled');
$('#btnPrevious').removeClass ('text-gray50');
}
// Deactivate next button
if (pos >= (devicesList.length-1)) {
$('#btnNext').attr ('disabled','');
$('#btnNext').addClass ('text-gray50');
} else {
$('#btnNext').removeAttr ('disabled');
$('#btnNext').removeClass ('text-gray50');
}
}
// ----------------------------------------
// Handle the read-only fields
function handleReadOnly(settingsData, disabledFields) {
@@ -451,9 +394,47 @@
}
}
// -----------------------------------------------
// INIT with polling for panel element visibility
// -----------------------------------------------
var deviceDetailsPageInitialized = false;
function initdeviceDetailsPage()
{
// Only proceed if .plugin-content is visible
if (!$('#panDetails:visible').length) {
return; // exit early if nothing is visible
}
// init page once
if (deviceDetailsPageInitialized) return; // ENSURE ONCE
deviceDetailsPageInitialized = true;
showSpinner();
getDeviceData();
}
// -----------------------------------------------------------------------------
// Recurring function to monitor the URL and reinitialize if needed
function deviceDetailsPageUpdater() {
initdeviceDetailsPage();
// Run updater again after delay
setTimeout(deviceDetailsPageUpdater, 200);
}
// if visible, load immediately, if not start updater
if (!$('#panDetails:visible').length) {
deviceDetailsPageUpdater();
}
else
{
getDeviceData();
}
// -------------------- INIT ------------------------
getDeviceData(true);
</script>

View File

@@ -29,10 +29,7 @@
<script>
var eventsRows = 10;
var eventsHide = true;
var parEventsRows = 'Front_Details_Events_Rows';
var parEventsHide = 'Front_Details_Events_Hide';
@@ -41,6 +38,8 @@ function loadEventsData() {
const hideConnections = $('#chkHideConnectionEvents')[0].checked;
const hideConnectionsStr = hideConnections ? 'true' : 'false';
mac = getMac()
const rawSql = `
SELECT eve_DateTime, eve_EventType, eve_IP, eve_AdditionalInfo
FROM Events
@@ -56,8 +55,6 @@ function loadEventsData() {
// Manually load the data first
$.get(apiUrl, function (data) {
const parsed = JSON.parse(data);
console.log(parsed);
const rows = parsed.map(row => {
const rawDate = row.eve_DateTime;
@@ -70,62 +67,94 @@ function loadEventsData() {
];
});
console.log(rows);
// Fill the table manually
const table = $('#tableEvents').DataTable();
table.clear();
table.rows.add(rows); // assuming each row is an array
table.draw();
hideSpinner();
});
}
function initializeSessionsDatatable () {
function initializeEventsDatatable (eventsRows) {
if ($.fn.dataTable.isDataTable('#tableEvents')) {
$('#tableEvents').DataTable().clear().destroy();
}
// Events datatable
$('#tableEvents').DataTable({
'paging' : true,
'lengthChange': true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
'searching' : true,
'ordering' : true,
'info' : true,
'autoWidth' : false,
'order' : [[0,'desc']],
'paging' : true,
'lengthChange': true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
'searching' : true,
'ordering' : true,
'info' : true,
'autoWidth' : false,
'order' : [[0,'desc']],
'pageLength' : eventsRows,
// Parameters
'pageLength' : eventsRows,
'columnDefs' : [
// Replace HTML codes
{targets: [0],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html (translateHTMLcodes (localizeTimestamp(cellData)));
} }
],
// Processing
'processing' : true,
'language' : {
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
'</td></table>',
emptyTable: 'No data',
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
"search": "<?= lang('Events_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Events_Table_nav_next');?>",
"previous": "<?= lang('Events_Table_nav_prev');?>"
},
"info": "<?= lang('Events_Table_info');?>",
}
});
'columnDefs' : [
{
targets: [0],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html(translateHTMLcodes(localizeTimestamp(cellData)));
}
}
],
'processing' : true,
'language' : {
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw"></i></td></table>',
emptyTable: 'No data',
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
"search": "<?= lang('Events_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Events_Table_nav_next');?>",
"previous": "<?= lang('Events_Table_nav_prev');?>"
},
"info": "<?= lang('Events_Table_info');?>",
}
});
}
initializeSessionsDatatable();
loadEventsData();
// -----------------------------------------------
// INIT with polling for panel element visibility
// -----------------------------------------------
var eventsPageInitialized = false;
function initDeviceEventsPage()
{
// Only proceed if .plugin-content is visible
if (!$('#panEvents:visible').length) {
return; // exit early if nothing is visible
}
// init page once
if (eventsPageInitialized) return; // ENSURE ONCE
eventsPageInitialized = true;
showSpinner();
var eventsRows = 10;
var eventsHide = true;
initializeEventsDatatable(eventsRows);
loadEventsData();
}
// -----------------------------------------------------------------------------
// Recurring function to monitor the URL and reinitialize if needed
function deviceEventsPageUpdater() {
initDeviceEventsPage();
// Run updater again after delay
setTimeout(deviceEventsPageUpdater, 200);
}
deviceEventsPageUpdater();
</script>

View File

@@ -23,8 +23,6 @@
<script>
initializeCalendar();
loadPresenceData();
// Force re-render calendar on tab change
// (bugfix for render error at left panel)
@@ -234,6 +232,37 @@ function initializeCalendar() {
})
}
// -----------------------------------------------
// INIT with polling for panel element visibility
// -----------------------------------------------
var presencePageInitialized = false;
function initDevicePresencePage() {
// Only proceed if the Presence tab is visible
if (!$('#panPresence:visible').length) {
return; // Exit early if nothing is visible
}
// Ensure initialization only happens once
if (presencePageInitialized) return;
presencePageInitialized = true;
showSpinner();
initializeCalendar();
loadPresenceData();
}
// Recurring check to initialize when visible
function devicePresencePageUpdater() {
initDevicePresencePage();
setTimeout(devicePresencePageUpdater, 200);
}
devicePresencePageUpdater();
</script>

View File

@@ -24,77 +24,126 @@
<script>
var parSessionsRows = 'Front_Details_Sessions_Rows';
function initializeSessionsDatatable (sessionsRows) {
// Sessions datatable
$('#tableSessions').DataTable({
'paging' : true,
'lengthChange': true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
'searching' : true,
'ordering' : true,
'info' : true,
'autoWidth' : false,
'order' : [[0,'desc'], [1,'desc']],
// Parameters
'pageLength' : sessionsRows,
'columnDefs' : [
{visible: false, targets: [0]},
// Replace HTML codes
{targets: [3,5],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html (translateHTMLcodes (cellData));
} },
// Date
{targets: [1,2],
"createdCell": function (td, cellData, rowData, row, col) {
// console.log(cellData);
if (!cellData.includes("missing event") && !cellData.includes("..."))
{
if (cellData.includes("+")) { // Check if timezone offset is present
cellData = cellData.split('+')[0]; // Remove timezone offset
}
// console.log(cellData);
result = localizeTimestamp(cellData);
} else
{
result = translateHTMLcodes(cellData)
}
$(td).html (result);
} }
],
// Processing
'processing' : true,
'language' : {
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
'</td></table>',
emptyTable: 'No data',
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
"search": "<?= lang('Events_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Events_Table_nav_next');?>",
"previous": "<?= lang('Events_Table_nav_prev');?>"
},
"info": "<?= lang('Events_Table_info');?>",
}
});
}
// -----------------------------------------------
// INIT with polling for panel element visibility
// -----------------------------------------------
// -----------------------------------------------------------
// Init datatable
function loadSessionsData(period){
const table = $('#tableSessions').DataTable();
showSpinner();
// table.clear().draw(); // Clear existing data before reloading
table.ajax
.url('php/server/events.php?action=getDeviceSessions&mac=' + getMac() + '&period=' + period)
.load(function () {
hideSpinner();
});
}
var sessionsPageInitialized = false;
// -----------------------------------------------------------
// Main init function
function initDeviceSessionsPage()
{
// Only proceed if .plugin-content is visible
if (!$('#panSessions:visible').length) {
return; // exit early if nothing is visible
}
// init page once
if (sessionsPageInitialized) return;
sessionsPageInitialized = true;
showSpinner();
var sessionsRows = 10;
var period = '1 month';
function initializeSessionsDatatable () {
// Sessions datatable
$('#tableSessions').DataTable({
'paging' : true,
'lengthChange': true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
'searching' : true,
'ordering' : true,
'info' : true,
'autoWidth' : false,
'order' : [[0,'desc'], [1,'desc']],
initializeSessionsDatatable(sessionsRows);
loadSessionsData(period);
}
// Parameters
'pageLength' : sessionsRows,
// -----------------------------------------------------------------------------
// Recurring function to monitor the URL and reinitialize if needed
function deviceSessionsPageUpdater() {
initDeviceSessionsPage();
'columnDefs' : [
{visible: false, targets: [0]},
// Run updater again after delay
setTimeout(deviceSessionsPageUpdater, 200);
}
// Replace HTML codes
{targets: [3,5],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html (translateHTMLcodes (cellData));
} },
// Date
{targets: [1,2],
"createdCell": function (td, cellData, rowData, row, col) {
// console.log(cellData);
if (!cellData.includes("missing event") && !cellData.includes("..."))
{
if (cellData.includes("+")) { // Check if timezone offset is present
cellData = cellData.split('+')[0]; // Remove timezone offset
}
// console.log(cellData);
result = localizeTimestamp(cellData);
} else
{
result = translateHTMLcodes(cellData)
}
$(td).html (result);
} }
],
// Processing
'processing' : true,
'language' : {
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
'</td></table>',
emptyTable: 'No data',
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
"search": "<?= lang('Events_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Events_Table_nav_next');?>",
"previous": "<?= lang('Events_Table_nav_prev');?>"
},
"info": "<?= lang('Events_Table_info');?>",
}
});
}
function loadSessionsData(){
$('#tableSessions').DataTable().ajax.url('php/server/events.php?action=getDeviceSessions&mac=' + getMac() +'&period='+ period).load();
}
initializeSessionsDatatable();
loadSessionsData();
// start updater
deviceSessionsPageUpdater();
</script>

View File

@@ -1047,40 +1047,24 @@ function multiEditDevices()
// -----------------------------------------------------------------------------
// Function collects shown devices from the DataTable
function getMacsOfShownDevices() {
rows = $('#tableDevices')[0].rows;
macs = [];
var table = $('#tableDevices').DataTable();
// var devicesDataTableData = $('#tableDevices').dataTable().fnGetData();
var devicesDataTableData = $('#tableDevices').DataTable().rows({ selected: false, page: 'current' }).data().toArray();
var macs = [];
console.log(devicesDataTableData);
// Get all row indexes on current page, in display order
var allIndexes = table.rows({ page: 'current' }).indexes();
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}`);
allIndexes.each(function(idx) {
var rowData = table.row(idx).data();
if (rowData) {
macs.push(rowData[mapIndx(11)]); // mapIndx(11) == MAC column
}
}
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) {

View File

@@ -1109,46 +1109,52 @@ function getGuid() {
// -----------------------------------------------------------------------------
// Loading Spinner overlay
// -----------------------------------------------------------------------------
spinnerHtml = `
<!-- spinner -->
<div id="loadingSpinner" style="display: block">
<div class="pa_semitransparent-panel"></div>
<div class="panel panel-default pa_spinner">
<table>
<td width="130px" align="middle">_text_</td>
<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw"></td>
</table>
</div>
</div>
`
function showSpinner(stringKey='Loading')
{
let spinnerTimeout = null;
let animationTime = 300
if(stringKey == "")
{
text = ''
} else
{
text = getString(stringKey)
}
function showSpinner(stringKey = 'Loading') {
const text = isEmpty(stringKey) ? "Loading" : getString(stringKey || "Loading");
const spinner = $("#loadingSpinner");
if (spinner.length && spinner.is(':visible')) {
clearTimeout(spinnerTimeout);
console.log(spinner);
text = isEmpty(text) ? "Loading" : text;
$("#loadingSpinnerText").text(text);
spinner.addClass("visible");
if($("#loadingSpinner").length)
{
$("#loadingSpinner").show();
}
else{
$(".wrapper").append(spinnerHtml.replace('_text_',text))
spinner.fadeIn(animationTime);
} else {
$("#loadingSpinnerText").text(text);
requestAnimationFrame(() => {
spinner.addClass("visible");
spinner.fadeIn(animationTime);
});
}
}
// -----------------------------------------------------------------------------
function hideSpinner()
{
$("#loadingSpinner").hide()
function hideSpinner() {
clearTimeout(spinnerTimeout);
const spinner = $("#loadingSpinner");
if (spinner.length) {
spinner.removeClass("visible");
spinner.fadeOut(animationTime);
spinnerTimeout = setTimeout(() => {
spinner.removeClass("visible");
spinner.fadeOut(animationTime); // optional remove or hide again
}, 300);
}
}
// --------------------------------------------------------
// Calls a backend function to add a front-end event to an execution queue

View File

@@ -136,7 +136,21 @@
<!-- ----------------------------------------------------------------------- -->
<!-- Layout Boxed Yellow -->
<!-- spinner -->
<body class="hold-transition fixed <?php echo $pia_skin_selected;?> theme-<?php echo $UI_THEME;?> sidebar-mini" onLoad="update_servertime();" >
<div id="loadingSpinner">
<div class="pa_semitransparent-panel"></div>
<div class="panel panel-default pa_spinner">
<table>
<td id="loadingSpinnerText" width="130px" ></td>
<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw"></td>
</table>
</div>
</div>
<!-- Site wrapper -->
<div class="wrapper">

View File

@@ -9,7 +9,7 @@
"About_Exit": "Sign out",
"About_Title": "Network security scanner & notification framework",
"AppEvents_AppEventProcessed": "Processed",
"AppEvents_DateTimeCreated": "Discovered On",
"AppEvents_DateTimeCreated": "Logged",
"AppEvents_Extra": "Extra",
"AppEvents_GUID": "Application Event GUID",
"AppEvents_Helper1": "Helper 1",

View File

@@ -49,10 +49,19 @@ function initMacFilter() {
return mac;
}
// -----------------------------------------------
// INIT with polling for panel element visibility
// -----------------------------------------------
// -----------------------------------------------------------------------------
// Initializes the fields if the MAC in the URL is different or not yet set
function initFields() {
// Only proceed if .plugin-content is visible
if (!$('.plugin-content:visible').length) {
return; // exit early if nothing is visible
}
// Get current value from the readonly text field
const currentVal = initMacFilter();
@@ -74,15 +83,6 @@ function initFields() {
}
}
// -----------------------------------------------------------------------------
// Recurring function to monitor the URL and reinitialize if needed
function updater() {
initFields();
// Run updater again after 500 milliseconds
setTimeout(updater, 500);
}
// -----------------------------------------------------------------------------
// Get form control according to the column definition from config.json > database_column_definitions
function getFormControl(dbColumnDef, value, index) {
@@ -285,11 +285,9 @@ function getData(){
pluginHistory = res["data"];
generateTabs()
});
});
});
});
}
@@ -301,15 +299,24 @@ function generateTabs() {
// Sort pluginDefinitions by unique_prefix alphabetically
pluginDefinitions.sort((a, b) => a.unique_prefix.localeCompare(b.unique_prefix));
assignActive = true;
// Iterate over the sorted pluginDefinitions to create tab headers and content
pluginDefinitions.forEach(pluginObj => {
if (pluginObj.show_ui) {
stats = createTabContent(pluginObj); // Create the content for each tab
createTabHeader(pluginObj, stats); // Create the header for each tab
}
});
stats = createTabContent(pluginObj, assignActive); // Create the content for each tab
hideSpinner()
if(stats.objectDataCount > 0)
{
createTabHeader(pluginObj, stats, assignActive); // Create the header for each tab
assignActive = false; // only mark first with content active
}
}
});
hideSpinner()
}
function resetTabs() {
@@ -318,27 +325,30 @@ function resetTabs() {
$('#tabs-content-location').empty();
}
function createTabHeader(pluginObj, stats) {
// ---------------------------------------------------------------
// left headers
function createTabHeader(pluginObj, stats, assignActive) {
const prefix = pluginObj.unique_prefix; // Get the unique prefix for the plugin
// Determine the active class for the first tab
const activeClass = pluginDefinitions.indexOf(pluginObj) === 0 ? 'active' : '';
assignActive ? activeClass = "active" : activeClass = "";
if(stats.objectDataCount > 0)
{
// Append the tab header to the tabs location
$('#tabs-location').append(`
<li class="left-nav ${activeClass} ">
<a class="col-sm-12 textOverflow" href="#${prefix}" data-plugin-prefix="${prefix}" id="${prefix}_id" data-toggle="tab">
${getString(`${prefix}_icon`)} ${getString(`${prefix}_display_name`)}
</a>
${stats.objectDataCount > 0 ? `<div class="pluginBadgeWrap"><span title="" class="badge pluginBadge" >${stats.objectDataCount}</span></div>` : ""}
</li>
`);
}
// Append the tab header to the tabs location
$('#tabs-location').append(`
<li class="left-nav ${activeClass} ">
<a class="col-sm-12 textOverflow" href="#${prefix}" data-plugin-prefix="${prefix}" id="${prefix}_id" data-toggle="tab">
${getString(`${prefix}_icon`)} ${getString(`${prefix}_display_name`)}
</a>
${stats.objectDataCount > 0 ? `<div class="pluginBadgeWrap"><span title="" class="badge pluginBadge" >${stats.objectDataCount}</span></div>` : ""}
</li>
`);
}
function createTabContent(pluginObj) {
// ---------------------------------------------------------------
// Content of selected plugin (header)
function createTabContent(pluginObj, assignActive) {
const prefix = pluginObj.unique_prefix; // Get the unique prefix for the plugin
const colDefinitions = getColumnDefinitions(pluginObj); // Get column definitions for DataTables
@@ -349,7 +359,7 @@ function createTabContent(pluginObj) {
// Append the content structure for the plugin's tab to the content location
$('#tabs-content-location').append(`
<div id="${prefix}" class="tab-pane ${pluginDefinitions.indexOf(pluginObj) === 0 ? 'active' : ''}">
<div id="${prefix}" class="tab-pane ${objectData.length > 0 && assignActive? 'active' : ''}">
${generateTabNavigation(prefix, objectData.length, eventData.length, historyData.length)} <!-- Create tab navigation -->
<div class="tab-content">
${generateDataTable(prefix, 'Objects', objectData, colDefinitions)}
@@ -435,7 +445,7 @@ function generateDataTable(prefix, tableType, data, colDefinitions) {
</table>
<div class="plugin-obj-purge">
<button class="btn btn-primary" onclick="purgeAll('${prefix}', 'Plugins_${tableType}' )"><?= lang('Plugins_DeleteAll');?></button>
${tableType !== 'Events' ? `<button class="btn btn-danger" onclick="deleteListed('${prefix}', 'Plugins_${tableType}' )"><?= lang('Plugins_Obj_DeleteListed');?></button>` : ''}
${tableType !== 'Events' ? `<button class="btn btn-primary" onclick="deleteListed('${prefix}', 'Plugins_${tableType}' )"><?= lang('Plugins_Obj_DeleteListed');?></button>` : ''}
</div>
</div>
`;
@@ -583,14 +593,22 @@ function deleteListedExecute() {
// -----------------------------------------------------------------------------
// Main sequence
// show spinning icon
showSpinner()
// Start updater on page load
$(document).ready(function () {
setTimeout(() => {
updater();
}, 100);
});
// -----------------------------------------------------------------------------
// Recurring function to monitor the URL and reinitialize if needed
function updater() {
initFields();
// Run updater again after delay
setTimeout(updater, 200);
}
// if visible, load immediately, if not start updater
if (!$('.plugin-content:visible').length) {
updater();
}
else
{
initFields();
}
</script>