FE+BE: use of new events endpoint

Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
jokob-sk
2026-01-03 11:38:22 +11:00
parent 039189ff4b
commit 1eca02a0f4
3 changed files with 195 additions and 292 deletions

View File

@@ -1,337 +1,234 @@
<?php <?php
require 'php/templates/header.php'; require 'php/templates/header.php';
?> ?>
<script> <script>
showSpinner(); showSpinner(); // Show initial page loading spinner
</script> </script>
<!-- ----------------------------------------------------------------------- --> <div class="content-wrapper eventsPage">
<section class="content">
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper eventsPage">
<!-- Main content ---------------------------------------------------------- --> <!-- ---------------- Top small boxes (Event shortcuts) ---------------- -->
<section class="content"> <div class="row">
<?php
<!-- top small box --------------------------------------------------------- --> // Define the top shortcut boxes for different event types
<div class="row"> $eventBoxes = [
['id' => 'eventsAll', 'type' => 'all', 'label' => 'Events_Shortcut_AllEvents', 'color' => 'aqua', 'icon' => 'fa-bolt'],
['id' => 'eventsSessions', 'type' => 'sessions', 'label' => 'Events_Shortcut_Sessions', 'color' => 'green', 'icon' => 'fa-plug'],
['id' => 'eventsMissing', 'type' => 'missing', 'label' => 'Events_Shortcut_MissSessions', 'color' => 'yellow', 'icon' => 'fa-exchange'],
['id' => 'eventsVoided', 'type' => 'voided', 'label' => 'Events_Shortcut_VoidSessions', 'color' => 'yellow', 'icon' => 'fa-exclamation-circle'],
['id' => 'eventsNewDevices', 'type' => 'new', 'label' => 'Events_Shortcut_NewDevices', 'color' => 'yellow', 'icon' => 'fa-circle-plus'],
['id' => 'eventsDown', 'type' => 'down', 'label' => 'Events_Shortcut_DownAlerts', 'color' => 'red', 'icon' => 'fa-warning']
];
foreach ($eventBoxes as $box) :
?>
<div class="col-lg-2 col-sm-4 col-xs-6"> <div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getEvents('all');"> <a href="#" onclick="getEvents('<?= $box['type'] ?>')">
<div class="small-box bg-aqua"> <div class="small-box bg-<?= $box['color'] ?>">
<div class="inner"> <h3 id="eventsAll"> -- </h3> <div class="inner">
<p class="infobox_label"><?= lang('Events_Shortcut_AllEvents');?></p> <h3 id="<?= $box['id'] ?>">--</h3>
<p class="infobox_label"><?= lang($box['label']); ?></p>
</div>
<div class="icon">
<i class="fa <?= $box['icon'] ?> text-<?= $box['color'] ?>-40"></i>
</div> </div>
<div class="icon"> <i class="fa fa-bolt text-aqua-40"></i> </div>
</div> </div>
</a> </a>
</div> </div>
<?php endforeach; ?>
</div>
<!-- top small box --------------------------------------------------------- --> <!-- ---------------- Events DataTable ---------------- -->
<div class="col-lg-2 col-sm-4 col-xs-6"> <div class="row">
<a href="#" onclick="javascript: getEvents('sessions');"> <div class="col-xs-12">
<div class="small-box bg-green"> <div id="tableEventsBox" class="box">
<div class="inner"> <h3 id="eventsSessions"> -- </h3> <div class="box-header col-xs-12">
<p class="infobox_label"><?= lang('Events_Shortcut_Sessions');?></p> <h3 id="tableEventsTitle" class="box-title text-gray col-xs-10">Events</h3>
</div> <div class="eventsPeriodSelectWrap col-xs-2">
<div class="icon"> <i class="fa fa-plug text-green-40"></i> </div> <select class="form-control" id="period" onchange="periodChanged()">
<option value="1 day"><?= lang('Events_Periodselect_today'); ?></option>
<option value="7 days"><?= lang('Events_Periodselect_LastWeek'); ?></option>
<option value="1 month" selected><?= lang('Events_Periodselect_LastMonth'); ?></option>
<option value="1 year"><?= lang('Events_Periodselect_LastYear'); ?></option>
<option value="100 years"><?= lang('Events_Periodselect_All'); ?></option>
</select>
</div> </div>
</a>
</div>
<!-- top small box --------------------------------------------------------- -->
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getEvents('missing');">
<div class="small-box bg-yellow">
<div class="inner"> <h3 id="eventsMissing"> -- </h3>
<p class="infobox_label"><?= lang('Events_Shortcut_MissSessions');?></p>
</div>
<div class="icon"> <i class="fa fa-exchange text-yellow-40"></i> </div>
</div>
</a>
</div>
<!-- top small box --------------------------------------------------------- -->
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getEvents('voided');">
<div class="small-box bg-yellow">
<div class="inner"> <h3 id="eventsVoided"> -- </h3>
<p class="infobox_label"><?= lang('Events_Shortcut_VoidSessions');?></p>
</div>
<div class="icon"> <i class="fa fa-exclamation-circle text-yellow-40"></i> </div>
</div>
</a>
</div>
<!-- top small box --------------------------------------------------------- -->
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getEvents('new');">
<div class="small-box bg-yellow">
<div class="inner"> <h3 id="eventsNewDevices"> -- </h3>
<p class="infobox_label"><?= lang('Events_Shortcut_NewDevices');?></p>
</div>
<div class="icon"> <i class="fa-solid fa-circle-plus text-yellow-40"></i> </div>
</div>
</a>
</div>
<!-- top small box --------------------------------------------------------- -->
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getEvents('down');">
<div class="small-box bg-red">
<div class="inner"> <h3 id="eventsDown"> -- </h3>
<p class="infobox_label"><?= lang('Events_Shortcut_DownAlerts');?></p>
</div>
<div class="icon"> <i class="fa fa-warning text-red-40"></i> </div>
</div>
</a>
</div>
</div>
<!-- /.row -->
<!-- datatable ------------------------------------------------------------- -->
<div class="row">
<div class="col-xs-12">
<div id="tableEventsBox" class="box">
<!-- box-header -->
<div class="box-header col-xs-12">
<h3 id="tableEventsTitle" class="box-title text-gray col-xs-10">Events</h3>
<div class="eventsPeriodSelectWrap col-xs-2">
<select class="form-control" id="period" onchange="javascript: periodChanged();">
<option value="1 day"><?= lang('Events_Periodselect_today');?></option>
<option value="7 days"><?= lang('Events_Periodselect_LastWeek');?></option>
<option value="1 month" selected><?= lang('Events_Periodselect_LastMonth');?></option>
<option value="1 year"><?= lang('Events_Periodselect_LastYear');?></option>
<option value="100 years"><?= lang('Events_Periodselect_All');?></option>
</select>
</div>
</div>
<!-- table -->
<div class="box-body table-responsive">
<table id="tableEvents" class="table table-bordered table-hover table-striped ">
<thead>
<tr>
<th><?= lang('Events_TableHead_Order');?></th>
<th><?= lang('Events_TableHead_Device');?></th>
<th><?= lang('Events_TableHead_Owner');?></th>
<th><?= lang('Events_TableHead_Date');?></th>
<th><?= lang('Events_TableHead_EventType');?></th>
<th><?= lang('Events_TableHead_Connection');?></th>
<th><?= lang('Events_TableHead_Disconnection');?></th>
<th><?= lang('Events_TableHead_Duration');?></th>
<th><?= lang('Events_TableHead_DurationOrder');?></th>
<th><?= lang('Events_TableHead_IP');?></th>
<th><?= lang('Events_TableHead_IPOrder');?></th>
<th><?= lang('Events_TableHead_AdditionalInfo');?></th>
<th>N/A</th>
<th>MAC</th>
<th><?= lang('Events_TableHead_PendingAlert');?></th>
</tr>
</thead>
</table>
</div>
<!-- /.box-body -->
</div> </div>
<!-- /.box -->
<div class="box-body table-responsive">
<table id="tableEvents" class="spinnerTarget table table-bordered table-hover table-striped">
<thead>
<tr>
<th><?= lang('Events_TableHead_Order'); ?></th>
<th><?= lang('Events_TableHead_Device'); ?></th>
<th><?= lang('Events_TableHead_Owner'); ?></th>
<th><?= lang('Events_TableHead_Date'); ?></th>
<th><?= lang('Events_TableHead_EventType'); ?></th>
<th><?= lang('Events_TableHead_Connection'); ?></th>
<th><?= lang('Events_TableHead_Disconnection'); ?></th>
<th><?= lang('Events_TableHead_Duration'); ?></th>
<th><?= lang('Events_TableHead_DurationOrder'); ?></th>
<th><?= lang('Events_TableHead_IP'); ?></th>
<th><?= lang('Events_TableHead_IPOrder'); ?></th>
<th><?= lang('Events_TableHead_AdditionalInfo'); ?></th>
<th>N/A</th>
<th>MAC</th>
<th><?= lang('Events_TableHead_PendingAlert'); ?></th>
</tr>
</thead>
</table>
</div>
</div> </div>
<!-- /.col -->
</div> </div>
<!-- /.row --> </div>
<!-- ----------------------------------------------------------------------- --> </section>
</section> </div>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
<?php require 'php/templates/footer.php'; ?>
<!-- ----------------------------------------------------------------------- -->
<?php
require 'php/templates/footer.php';
?>
<!-- page script ----------------------------------------------------------- -->
<script> <script>
var parPeriod = 'nax_parPeriod'; /* ---------------- Global Variables ---------------- */
var parTableRows = 'nax_parTableRows'; const parPeriod = 'nax_parPeriod';
const parTableRows = 'nax_parTableRows';
var eventsType = 'all'; let eventsType = 'all'; // Default type
var period = '1 day'; let period = getCookie(parPeriod) || '1 day';
var tableRows = parseInt(getSetting("UI_DEFAULT_PAGE_SIZE")); let tableRows = parseInt(getCookie(parTableRows) || getSetting("UI_DEFAULT_PAGE_SIZE"), 10);
// Read parameters & Initialize components
main();
main(); // Initialize page
// ----------------------------------------------------------------------------- /* ---------------- Main initialization ---------------- */
function main() { function main() {
// Get parameter value from cookies instead of server
period = getCookie(parPeriod) === "" ? "1 day" : getCookie(parPeriod);
$('#period').val(period); $('#period').val(period);
tableRows = getCookie(parTableRows) === "" ? parseInt(getSetting("UI_DEFAULT_PAGE_SIZE")) : parseInt(getCookie(parTableRows), 10);
// Initialize components
initializeDatatable(); initializeDatatable();
// Query data
getEventsTotals(); getEventsTotals();
getEvents(eventsType); getEvents(eventsType);
} }
/* ---------------- Initialize DataTable ---------------- */
// ----------------------------------------------------------------------------- function initializeDatatable() {
function initializeDatatable () { const table = $('#tableEvents').DataTable({
$('#tableEvents').DataTable({ paging: true,
'paging' : true, lengthChange: true,
'lengthChange' : true, lengthMenu: getLengthMenu(getSetting("UI_DEFAULT_PAGE_SIZE")),
'lengthMenu' : getLengthMenu(parseInt(getSetting("UI_DEFAULT_PAGE_SIZE"))), searching: true,
'searching' : true, ordering: true,
'ordering' : true, info: true,
'info' : true, autoWidth: false,
'autoWidth' : false, order: [[0, "desc"], [3, "desc"], [5, "desc"]],
'order' : [[0,"desc"], [3,"desc"], [5,"desc"]], pageLength: tableRows,
columnDefs: [
// Parameters { targets: [0,5,6,7,8,10,11,12,13], visible: false },
'pageLength' : tableRows, { targets: [7], orderData: [8] },
{ targets: [9], orderData: [10] },
'columnDefs' : [ { targets: [1], createdCell: (td, cellData, rowData) => {
{visible: false, targets: [0,5,6,7,8,10,11,12,13] }, // Device column as link
{className: 'text-center', targets: [] }, $(td).html(`<b><a href="deviceDetails.php?mac=${rowData[13]}">${cellData}</a></b>`);
{orderData: [8], targets: 7 }, }},
{orderData: [10], targets: 9 }, { targets: [3], createdCell: (td, cellData) => $(td).html(localizeTimestamp(cellData)) },
{ targets: [4,5,6,7], createdCell: (td, cellData) => $(td).html(translateHTMLcodes(cellData)) }
// Device Name
{targets: [1],
"createdCell": function (td, cellData, rowData, row, col) {
$(td).html ('<b><a href="deviceDetails.php?mac='+ rowData[13] +'" class="">'+ cellData +'</a></b>');
} },
// Replace HTML codes
{targets: [4,5,6,7],
"createdCell": function (td, cellData, rowData, row, col) {
$(td).html (translateHTMLcodes (cellData));
} },
// PendingAlert
{targets: [14],
"createdCell": function (td, cellData, rowData, row, col) {
// console.log(cellData);
$(td).html (cellData);
} },
// Date
{targets: [3],
"createdCell": function (td, cellData, rowData, row, col) {
// console.log(cellData);
$(td).html (localizeTimestamp(cellData));
} }
], ],
processing: true, // Shows "processing" overlay
// Processing language: {
'processing' : true, processing: '<table><td width="130px" align="middle"><?= lang("Events_Loading"); ?></td><td><i class="fa-solid fa-spinner fa-spin-pulse"></i></td></table>',
'language' : {
processing: '<table><td width="130px" align="middle"><?= lang("Events_Loading");?></td><td><i class="fa-solid fa-spinner fa-spin-pulse"></i></td></table>',
emptyTable: 'No data', emptyTable: 'No data',
"lengthMenu": "<?= lang('Events_Tablelenght');?>", lengthMenu: "<?= lang('Events_Tablelenght'); ?>",
"search": "<?= lang('Events_Searchbox');?>: ", search: "<?= lang('Events_Searchbox'); ?>: ",
"paginate": { paginate: { next: "<?= lang('Events_Table_nav_next'); ?>", previous: "<?= lang('Events_Table_nav_prev'); ?>" },
"next": "<?= lang('Events_Table_nav_next');?>", info: "<?= lang('Events_Table_info'); ?>"
"previous": "<?= lang('Events_Table_nav_prev');?>"
},
"info": "<?= lang('Events_Table_info');?>",
},
initComplete: function(settings, json) {
hideSpinner(); // Called after the DataTable is fully initialized
} }
}); });
// Save Parameter rows when changed // Save page length when changed
$('#tableEvents').on( 'length.dt', function ( e, settings, len ) { $('#tableEvents').on('length.dt', function(e, settings, len) {
setCookie(parTableRows, len) setCookie(parTableRows, len);
} );
};
// -----------------------------------------------------------------------------
function periodChanged () {
// Save Parameter Period
period = $('#period').val();
setCookie(parTableRows, period)
// Requery totals and events
getEventsTotals();
getEvents (eventsType);
}
// -----------------------------------------------------------------------------
function getEventsTotals () {
// stop timer
stopTimerRefreshData();
// get totals and put in boxes
$.get('php/server/events.php?action=getEventsTotals&period='+ period, function(data) {
var totalsEvents = JSON.parse(data);
$('#eventsAll').html (totalsEvents[0].toLocaleString());
$('#eventsSessions').html (totalsEvents[1].toLocaleString());
$('#eventsMissing').html (totalsEvents[2].toLocaleString());
$('#eventsVoided').html (totalsEvents[3].toLocaleString());
$('#eventsNewDevices').html (totalsEvents[4].toLocaleString());
$('#eventsDown').html (totalsEvents[5].toLocaleString());
// Timer for refresh data
newTimerRefreshData (getEventsTotals);
}); });
} }
/* ---------------- Period filter changed ---------------- */
function periodChanged() {
period = $('#period').val();
setCookie(parPeriod, period);
getEventsTotals();
getEvents(eventsType);
}
// ----------------------------------------------------------------------------- /* ---------------- Fetch event totals ---------------- */
function getEvents (p_eventsType) { function getEventsTotals() {
// Save status selected stopTimerRefreshData();
eventsType = p_eventsType;
// Define color & title for the status selected const protocol = window.location.protocol.replace(":", "");
switch (eventsType) { const host = window.location.hostname;
case 'all': tableTitle = '<?= lang('Events_Shortcut_AllEvents');?>'; color = 'aqua'; sesionCols = false; break; const port = getSetting("GRAPHQL_PORT");
case 'sessions': tableTitle = '<?= lang('Events_Shortcut_Sessions');?>'; color = 'green'; sesionCols = true; break; const apiToken = getSetting("API_TOKEN");
case 'missing': tableTitle = '<?= lang('Events_Shortcut_MissSessions');?>'; color = 'yellow'; sesionCols = true; break; const url = `${protocol}://${host}:${port}/sessions/totals?period=${encodeURIComponent(period)}`;
case 'voided': tableTitle = '<?= lang('Events_Shortcut_VoidSessions');?>'; color = 'yellow'; sesionCols = false; break;
case 'new': tableTitle = '<?= lang('Events_Shortcut_NewDevices');?>'; color = 'yellow'; sesionCols = false; break;
case 'down': tableTitle = '<?= lang('Events_Shortcut_DownAlerts');?>'; color = 'red'; sesionCols = false; break;
default: tableTitle = '<?= lang('Events_Shortcut_Events');?>'; boxClass = ''; sesionCols = false; break;
}
// Set title and color $.ajax({
$('#tableEventsTitle')[0].className = 'box-title text-' + color; url,
$('#tableEventsBox')[0].className = 'box box-' + color; method: "GET",
$('#tableEventsTitle').html (tableTitle); dataType: "json",
headers: { "Authorization": `Bearer ${apiToken}` },
success: totalsEvents => {
const ids = ['eventsAll','eventsSessions','eventsMissing','eventsVoided','eventsNewDevices','eventsDown'];
ids.forEach((id, i) => $(`#${id}`).html(totalsEvents[i].toLocaleString()));
newTimerRefreshData(getEventsTotals);
},
error: (xhr, status, error) => console.error("Error fetching totals:", status, error)
});
}
// Columns Visibility /* ---------------- Fetch events and reload DataTable ---------------- */
$('#tableEvents').DataTable().column(3).visible (!sesionCols); function getEvents(type) {
$('#tableEvents').DataTable().column(4).visible (!sesionCols); eventsType = type;
$('#tableEvents').DataTable().column(5).visible (sesionCols); const table = $('#tableEvents').DataTable();
$('#tableEvents').DataTable().column(6).visible (sesionCols);
$('#tableEvents').DataTable().column(7).visible (sesionCols);
// Define new datasource URL and reload // Event type config: title, color, session columns visibility
$('#tableEvents').DataTable().clear(); const config = {
$('#tableEvents').DataTable().draw(); all: {title: 'Events_Shortcut_AllEvents', color: 'aqua', sesionCols: false},
$('#tableEvents').DataTable().order ([0,"desc"], [3,"desc"], [5,"desc"]); sessions: {title: 'Events_Shortcut_Sessions', color: 'green', sesionCols: true},
$('#tableEvents').DataTable().ajax.url('php/server/events.php?action=getEvents&type=' + eventsType +'&period='+ period ).load(); missing: {title: 'Events_Shortcut_MissSessions', color: 'yellow', sesionCols: true},
}; voided: {title: 'Events_Shortcut_VoidSessions', color: 'yellow', sesionCols: false},
new: {title: 'Events_Shortcut_NewDevices', color: 'yellow', sesionCols: false},
down: {title: 'Events_Shortcut_DownAlerts', color: 'red', sesionCols: false}
}[type] || {title: 'Events_Shortcut_Events', color: '', sesionCols: false};
// Update title and color
$('#tableEventsTitle').attr('class', 'box-title text-' + config.color).html(getString(config.title));
$('#tableEventsBox').attr('class', 'box box-' + config.color);
// Toggle columns visibility
table.column(3).visible(!config.sesionCols);
table.column(4).visible(!config.sesionCols);
table.column(5).visible(config.sesionCols);
table.column(6).visible(config.sesionCols);
table.column(7).visible(config.sesionCols);
// Build API URL
const protocol = window.location.protocol.replace(":", "");
const host = window.location.hostname;
const port = getSetting("GRAPHQL_PORT");
const apiToken = getSetting("API_TOKEN");
const url = `${protocol}://${host}:${port}/sessions/session-events?type=${encodeURIComponent(type)}&period=${encodeURIComponent(period)}`;
table.clear().draw(); // Clear old rows
showSpinner()
$.ajax({
url,
method: "GET",
dataType: "json",
headers: { "Authorization": `Bearer ${apiToken}` },
beforeSend: showSpinner, // Show spinner during fetch
complete: hideSpinner, // Hide spinner after fetch
success: response => {
const data = Array.isArray(response) ? response : response.data || [];
table.rows.add(data).draw();
},
error: (xhr, status, error) => console.error("Error fetching session events:", status, error, xhr.responseText)
});
}
</script> </script>

View File

@@ -1218,7 +1218,12 @@ let spinnerTimeout = null;
let animationTime = 300 let animationTime = 300
function showSpinner(stringKey = 'Loading') { function showSpinner(stringKey = 'Loading') {
const text = isEmpty(stringKey) ? "Loading" : getString(stringKey || "Loading"); let text = isEmpty(stringKey) ? "Loading..." : getString(stringKey || "Loading");
if (text == ""){
text = "Loading"
}
const spinner = $("#loadingSpinner"); const spinner = $("#loadingSpinner");
const target = $(".spinnerTarget").first(); // Only use the first one if multiple exist const target = $(".spinnerTarget").first(); // Only use the first one if multiple exist

View File

@@ -341,7 +341,8 @@ def get_session_events(event_type, period_date):
NULL, NULL,
ses_AdditionalInfo, ses_AdditionalInfo,
ses_StillConnected, ses_StillConnected,
devMac devMac,
0 AS ses_PendingAlertEmail
FROM Sessions_Devices FROM Sessions_Devices
""" """