FE+BE: use of new events endpoint, devMAC -> devMac

Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
jokob-sk
2026-01-03 12:05:56 +11:00
parent a01ccaec94
commit 6e194185ed
4 changed files with 72 additions and 442 deletions

View File

@@ -1,418 +0,0 @@
<?php
//------------------------------------------------------------------------------
// NetAlertX
// Open Source Network Guard / WIFI & LAN intrusion detector
//
// events.php - Front module. Server side. Manage Events
//------------------------------------------------------------------------------
# Puche 2021 / 2022+ jokob jokob@duck.com GNU GPLv3
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /sessions /events
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// External files
require dirname(__FILE__).'/init.php';
//------------------------------------------------------------------------------
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
//------------------------------------------------------------------------------
// Action selector
//------------------------------------------------------------------------------
// Set maximum execution time to 1 minute
ini_set ('max_execution_time','60');
// Action functions
if (isset ($_REQUEST['action']) && !empty ($_REQUEST['action'])) {
$action = $_REQUEST['action'];
switch ($action) {
case 'getEventsTotals': getEventsTotals(); break;
case 'getEvents': getEvents(); break;
case 'getDeviceSessions': getDeviceSessions(); break;
case 'getDevicePresence': getDevicePresence(); break;
case 'getEventsCalendar': getEventsCalendar(); break;
default: logServerConsole ('Action: '. $action); break;
}
}
//------------------------------------------------------------------------------
// Query total numbers of Events
//------------------------------------------------------------------------------
function getEventsTotals() {
global $db;
// Request Parameters
$periodDate = $_REQUEST['period'];
$periodDateSQL = "";
$days = "";
switch ($periodDate) {
case '7 days':
$days = "7";
break;
case '1 month':
$days = "30";
break;
case '1 year':
$days = "365";
break;
case '100 years':
$days = "3650"; //10 years
break;
default:
$days = "1";
}
$periodDateSQL = "-".$days." day";
$resultJSON = "";
// check cache if JSON available in a cookie
if(getCache("getEventsTotals".$days) != "")
{
$resultJSON = getCache("getEventsTotals".$days);
} else
{
// one query to get all numbers, which is quicker than multiple queries
$sql = "select
(SELECT Count(*) FROM Events WHERE eve_DateTime >= date('now', '".$periodDateSQL."')) as all_events,
(SELECT Count(*) FROM Sessions as sessions WHERE ( ses_DateTimeConnection >= date('now', '".$periodDateSQL."') OR ses_DateTimeDisconnection >= date('now', '".$periodDateSQL."') OR ses_StillConnected = 1 )) as sessions,
(SELECT Count(*) FROM Sessions WHERE ((ses_DateTimeConnection IS NULL AND ses_DateTimeDisconnection >= date('now', '".$periodDateSQL."' )) OR (ses_DateTimeDisconnection IS NULL AND ses_StillConnected = 0 AND ses_DateTimeConnection >= date('now', '".$periodDateSQL."' )))) as missing,
(SELECT Count(*) FROM Events WHERE eve_DateTime >= date('now', '".$periodDateSQL."') AND eve_EventType LIKE 'VOIDED%' ) as voided,
(SELECT Count(*) FROM Events WHERE eve_DateTime >= date('now', '".$periodDateSQL."') AND eve_EventType LIKE 'New Device' ) as new,
(SELECT Count(*) FROM Events WHERE eve_DateTime >= date('now', '".$periodDateSQL."') AND eve_EventType LIKE 'Device Down' ) as down";
$result = $db->query($sql);
$row = $result -> fetchArray (SQLITE3_NUM);
$resultJSON = json_encode (array ($row[0], $row[1], $row[2], $row[3], $row[4], $row[5]));
// save JSON result to cache
setCache("getEventsTotals".$days, $resultJSON );
}
// Return json
echo ($resultJSON);
}
//------------------------------------------------------------------------------
// Query the List of events
//------------------------------------------------------------------------------
function getEvents() {
global $db;
// Request Parameters
$type = $_REQUEST ['type'];
$periodDate = getDateFromPeriod();
// SQL
$SQL1 = 'SELECT eve_DateTime AS eve_DateTimeOrder, devName, devOwner, eve_DateTime, eve_EventType, NULL, NULL, NULL, NULL, eve_IP, NULL, eve_AdditionalInfo, NULL, devMac, eve_PendingAlertEmail
FROM Events_Devices
WHERE eve_DateTime >= '. $periodDate;
$SQL2 = 'SELECT IFNULL (ses_DateTimeConnection, ses_DateTimeDisconnection) ses_DateTimeOrder,
devName, devOwner, Null, Null, ses_DateTimeConnection, ses_DateTimeDisconnection, NULL, NULL, ses_IP, NULL, ses_AdditionalInfo, ses_StillConnected, devMac
FROM Sessions_Devices ';
// SQL Variations for status
switch ($type) {
case 'all': $SQL = $SQL1; break;
case 'sessions':
$SQL = $SQL2 . ' WHERE ( ses_DateTimeConnection >= '. $periodDate .' OR ses_DateTimeDisconnection >= '. $periodDate .' OR ses_StillConnected = 1 ) ';
break;
case 'missing':
$SQL = $SQL2 . ' WHERE (ses_DateTimeConnection IS NULL AND ses_DateTimeDisconnection >= '. $periodDate .' )
OR (ses_DateTimeDisconnection IS NULL AND ses_StillConnected = 0 AND ses_DateTimeConnection >= '. $periodDate .' )';
break;
case 'voided': $SQL = $SQL1 .' AND eve_EventType LIKE "VOIDED%" '; break;
case 'new': $SQL = $SQL1 .' AND eve_EventType = "New Device" '; break;
case 'down': $SQL = $SQL1 .' AND eve_EventType = "Device Down" '; break;
default: $SQL = $SQL1 .' AND 1==0 '; break;
}
// Query
$result = $db->query($SQL);
$tableData = array();
while ($row = $result -> fetchArray (SQLITE3_NUM)) {
if ($type == 'sessions' || $type == 'missing' ) {
// Duration
if (!empty ($row[5]) && !empty($row[6]) ) {
$row[7] = formatDateDiff ($row[5], $row[6]);
$row[8] = abs(strtotime($row[6]) - strtotime($row[5]));
} elseif ($row[12] == 1) {
$row[7] = formatDateDiff ($row[5], '');
$row[8] = abs(strtotime("now") - strtotime($row[5]));
} else {
$row[7] = '...';
$row[8] = 0;
}
// Connection
if (!empty ($row[5]) ) {
$row[5] = formatDate ($row[5]);
} else {
$row[5] = '<missing event>';
}
// Disconnection
if (!empty ($row[6]) ) {
$row[6] = formatDate ($row[6]);
} elseif ($row[12] == 0) {
$row[6] = '<missing event>';
} else {
$row[6] = '...';
}
} else {
// Event Date
$row[3] = formatDate ($row[3]);
}
// IP Order
$row[10] = formatIPlong ($row[9]);
$tableData['data'][] = $row;
}
// Control no rows
if (empty($tableData['data'])) {
$tableData['data'] = '';
}
// Return json
echo (json_encode ($tableData));
}
//------------------------------------------------------------------------------
// Query Device Sessions
//------------------------------------------------------------------------------
function getDeviceSessions() {
global $db;
// Request Parameters
$mac = $_REQUEST['mac'];
$periodDate = getDateFromPeriod();
// SQL
$SQL = 'SELECT IFNULL (ses_DateTimeConnection, ses_DateTimeDisconnection) ses_DateTimeOrder,
ses_EventTypeConnection, ses_DateTimeConnection,
ses_EventTypeDisconnection, ses_DateTimeDisconnection, ses_StillConnected,
ses_IP, ses_AdditionalInfo
FROM Sessions
WHERE ses_MAC="' . $mac .'"
AND ( ses_DateTimeConnection >= '. $periodDate .'
OR ses_DateTimeDisconnection >= '. $periodDate .'
OR ses_StillConnected = 1 ) ';
$result = $db->query($SQL);
// arrays of rows
$tableData = array();
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
// Connection DateTime
if ($row['ses_EventTypeConnection'] == '<missing event>') {
$ini = $row['ses_EventTypeConnection'];
} else {
$ini = formatDate ($row['ses_DateTimeConnection']);
}
// Disconnection DateTime
if ($row['ses_StillConnected'] == true) {
$end = '...';
} elseif ($row['ses_EventTypeDisconnection'] == '<missing event>') {
$end = $row['ses_EventTypeDisconnection'];
} else {
$end = formatDate ($row['ses_DateTimeDisconnection']);
}
// Duration
if ($row['ses_EventTypeConnection'] == '<missing event>' || $row['ses_EventTypeConnection'] == NULL || $row['ses_EventTypeDisconnection'] == '<missing event>' || $row['ses_EventTypeDisconnection'] == NULL) {
$dur = '...';
} elseif ($row['ses_StillConnected'] == true) {
$dur = formatDateDiff ($row['ses_DateTimeConnection'], ''); //***********
} else {
$dur = formatDateDiff ($row['ses_DateTimeConnection'], $row['ses_DateTimeDisconnection']);
}
// Additional Info
$info = $row['ses_AdditionalInfo'];
if ($row['ses_EventTypeConnection'] == 'New Device' ) {
$info = $row['ses_EventTypeConnection'] .': '. $info;
}
// Push row data
$tableData['data'][] = array($row['ses_DateTimeOrder'], $ini, $end, $dur, $row['ses_IP'], $info);
}
// Control no rows
if (empty($tableData['data'])) {
$tableData['data'] = '';
}
// Return json
echo (json_encode ($tableData));
}
//------------------------------------------------------------------------------
// Query Device Presence Calendar
//------------------------------------------------------------------------------
function getDevicePresence() {
global $db;
// Request Parameters
$mac = $_REQUEST['mac'];
$startDate = '"'. formatDateISO ($_REQUEST ['start']) .'"';
$endDate = '"'. formatDateISO ($_REQUEST ['end']) .'"';
// SQL
$SQL = 'SELECT ses_EventTypeConnection, ses_DateTimeConnection,
ses_EventTypeDisconnection, ses_DateTimeDisconnection, ses_IP, ses_AdditionalInfo, ses_StillConnected,
CASE
WHEN ses_EventTypeConnection = "<missing event>" THEN
IFNULL ((SELECT MAX(ses_DateTimeDisconnection) FROM Sessions AS SES2 WHERE SES2.ses_MAC = SES1.ses_MAC AND SES2.ses_DateTimeDisconnection < SES1.ses_DateTimeDisconnection), DATETIME(ses_DateTimeDisconnection, "-1 hour"))
ELSE ses_DateTimeConnection
END AS ses_DateTimeConnectionCorrected,
CASE
WHEN ses_EventTypeDisconnection = "<missing event>" OR ses_EventTypeDisconnection = NULL THEN
(SELECT MIN(ses_DateTimeConnection) FROM Sessions AS SES2 WHERE SES2.ses_MAC = SES1.ses_MAC AND SES2.ses_DateTimeConnection > SES1.ses_DateTimeConnection)
ELSE ses_DateTimeDisconnection
END AS ses_DateTimeDisconnectionCorrected
FROM Sessions AS SES1
WHERE ses_MAC="' . $mac .'"
AND (ses_DateTimeConnectionCorrected <= date('. $endDate .')
AND (ses_DateTimeDisconnectionCorrected >= date('. $startDate .') OR ses_StillConnected = 1 )) ';
$result = $db->query($SQL);
// arrays of rows
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
// Event color
if ($row['ses_EventTypeConnection'] == '<missing event>' || $row['ses_EventTypeDisconnection'] == '<missing event>') {
$color = '#f39c12';
} elseif ($row['ses_StillConnected'] == 1 ) {
$color = '#00a659';
} else {
$color = '#0073b7';
}
// tooltip
$tooltip = 'Connection: ' . formatEventDate ($row['ses_DateTimeConnection'], $row['ses_EventTypeConnection']) . chr(13) .
'Disconnection: ' . formatEventDate ($row['ses_DateTimeDisconnection'], $row['ses_EventTypeDisconnection']) . chr(13) .
'IP: ' . $row['ses_IP'];
// Save row data
$tableData[] = array(
'title' => '',
'start' => formatDateISO ($row['ses_DateTimeConnectionCorrected']),
'end' => formatDateISO ($row['ses_DateTimeDisconnectionCorrected']),
'color' => $color,
'tooltip' => $tooltip
);
}
// Control no rows
if (empty($tableData)) {
$tableData = '';
}
// Return json
echo (json_encode($tableData));
}
//------------------------------------------------------------------------------
// Query Presence Calendar for all Devices
//------------------------------------------------------------------------------
function getEventsCalendar() {
global $db;
// Request Parameters
$startDate = '"'. $_REQUEST ['start'] .'"';
$endDate = '"'. $_REQUEST ['end'] .'"';
// SQL
$SQL = 'SELECT SES1.ses_MAC, SES1.ses_EventTypeConnection, SES1.ses_DateTimeConnection,
SES1.ses_EventTypeDisconnection, SES1.ses_DateTimeDisconnection, SES1.ses_IP,
SES1.ses_AdditionalInfo, SES1.ses_StillConnected,
CASE
WHEN SES1.ses_EventTypeConnection = "<missing event>" THEN
IFNULL (
(SELECT MAX(SES2.ses_DateTimeDisconnection)
FROM Sessions AS SES2
WHERE SES2.ses_MAC = SES1.ses_MAC
AND SES2.ses_DateTimeDisconnection < SES1.ses_DateTimeDisconnection
AND SES2.ses_DateTimeDisconnection BETWEEN Date('. $startDate .') AND Date('. $endDate .')
),
DATETIME(SES1.ses_DateTimeDisconnection, "-1 hour")
)
ELSE SES1.ses_DateTimeConnection
END AS ses_DateTimeConnectionCorrected,
CASE
WHEN SES1.ses_EventTypeDisconnection = "<missing event>" THEN
(SELECT MIN(SES2.ses_DateTimeConnection)
FROM Sessions AS SES2
WHERE SES2.ses_MAC = SES1.ses_MAC
AND SES2.ses_DateTimeConnection > SES1.ses_DateTimeConnection
AND SES2.ses_DateTimeConnection BETWEEN Date('. $startDate .') AND Date('. $endDate .')
)
ELSE SES1.ses_DateTimeDisconnection
END AS ses_DateTimeDisconnectionCorrected
FROM Sessions AS SES1
WHERE (SES1.ses_DateTimeConnection BETWEEN Date('. $startDate .') AND Date('. $endDate .'))
OR (SES1.ses_DateTimeDisconnection BETWEEN Date('. $startDate .') AND Date('. $endDate .'))
OR SES1.ses_StillConnected = 1';
$result = $db->query($SQL);
// arrays of rows
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
// Event color
if ($row['ses_EventTypeConnection'] == '<missing event>' || $row['ses_EventTypeDisconnection'] == '<missing event>') {
$color = '#f39c12';
} elseif ($row['ses_StillConnected'] == 1 ) {
$color = '#00a659';
} else {
$color = '#0073b7';
}
// tooltip
$tooltip = 'Connection: ' . formatEventDate ($row['ses_DateTimeConnection'], $row['ses_EventTypeConnection']) . chr(13) .
'Disconnection: ' . formatEventDate ($row['ses_DateTimeDisconnection'], $row['ses_EventTypeDisconnection']) . chr(13) .
'IP: ' . $row['ses_IP'];
// Save row data
$tableData[] = array(
'resourceId' => $row['ses_MAC'],
'title' => '',
'start' => formatDateISO ($row['ses_DateTimeConnectionCorrected']),
'end' => formatDateISO ($row['ses_DateTimeDisconnectionCorrected']),
'color' => $color,
'tooltip' => $tooltip,
'className' => 'no-border'
);
}
// Control no rows
if (empty($tableData)) {
$tableData = '';
}
// Return json
echo (json_encode($tableData));
}
?>

View File

@@ -510,7 +510,7 @@ def mqtt_start(db):
"group": device["devGroup"],
"location": device["devLocation"],
"network_parent_mac": device["devParentMAC"],
"network_parent_name": next((dev["devName"] for dev in devices if dev["devMAC"] == device["devParentMAC"]), "")
"network_parent_name": next((dev["devName"] for dev in devices if dev["devMac"] == device["devParentMAC"]), "")
}
# bulk update device sensors in home assistant

View File

@@ -1,7 +1,7 @@
<!--
#---------------------------------------------------------------------------------#
# NetAlertX #
# Open Source Network Guard / WIFI & LAN intrusion detector #
# Open Source Network Guard / WIFI & LAN intrusion detector #
# #
# presence.php - Front module. Device Presence calendar page #
#---------------------------------------------------------------------------------#
@@ -44,7 +44,7 @@
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getDevicesPresence('connected');">
<div class="small-box bg-green">
<div class="inner"> <h3 id="devicesConnected"> -- </h3>
<div class="inner"> <h3 id="devicesConnected"> -- </h3>
<p class="infobox_label"><?= lang('Presence_Shortcut_Connected');?></p>
</div>
<div class="icon"> <i class="fa fa-plug text-green-40"></i> </div>
@@ -113,16 +113,16 @@
<div class="chart">
<script src="lib/chart.js/Chart.js"></script>
<!-- presence chart -->
<?php
<?php
require 'php/components/graph_online_history.php';
?>
?>
</div>
</div>
<!-- /.box-body -->
</div>
</div>
</div>
<!-- /.row -->
<!-- Calendar -------------------------------------------------------------- -->
@@ -147,7 +147,7 @@
</div>
<!-- box-body -->
<div class="box-body table-responsive">
<div class="box-body table-responsive">
<!-- Calendar -->
<div id="calendar"></div>
@@ -236,7 +236,7 @@ function initializeCalendar () {
height : 'auto',
firstDay : 1,
allDaySlot : false,
timeFormat : 'H:mm',
timeFormat : 'H:mm',
resourceLabelText : '<?= lang('Presence_CallHead_Devices');?>',
resourceAreaWidth : '160px',
@@ -291,24 +291,24 @@ function initializeCalendar () {
slotDuration : '00:30:00'
}
},
// Needed due hack partial day events 23:59:59
dayRender: function (date, cell) {
if ($('#calendar').fullCalendar('getView').name == 'timelineYear') {
cell.removeClass('fc-sat');
cell.removeClass('fc-sun');
cell.removeClass('fc-sat');
cell.removeClass('fc-sun');
return;
};
};
if (date.day() == 0) {
cell.addClass('fc-sun'); };
if (date.day() == 6) {
cell.addClass('fc-sat'); };
if (date.format('YYYY-MM-DD') == moment().format('YYYY-MM-DD')) {
cell.addClass ('fc-today'); };
if ($('#calendar').fullCalendar('getView').name == 'timelineDay') {
cell.removeClass('fc-sat');
cell.removeClass('fc-sun');
@@ -318,7 +318,7 @@ function initializeCalendar () {
}
};
},
resourceRender: function (resourceObj, labelTds, bodyTds) {
labelTds.find('span.fc-cell-text').html (
'<b><a href="deviceDetails.php?mac='+ resourceObj.id+ '" class="">'+ resourceObj.title +'</a></b>');
@@ -326,7 +326,7 @@ function initializeCalendar () {
// Resize heihgt
// $(".fc-content table tbody tr .fc-widget-content div").addClass('fc-resized-row');
},
eventRender: function (event, element, view) {
// $(element).tooltip({container: 'body', placement: 'bottom', title: event.tooltip});
tltp = event.tooltip.replace('\n',' | ')
@@ -387,7 +387,7 @@ function getDevicesPresence (status) {
case 'down': tableTitle = '<?= lang('Presence_Shortcut_DownAlerts');?>'; color = 'red'; break;
case 'archived': tableTitle = '<?= lang('Presence_Shortcut_Archived');?>'; color = 'gray'; break;
default: tableTitle = '<?= lang('Presence_Shortcut_Devices');?>'; color = 'gray'; break;
}
}
period = "7 days"
@@ -421,12 +421,60 @@ function getDevicesPresence (status) {
$('#tableDevicesBox')[0].className = 'box box-'+ color;
$('#tableDevicesTitle').html (tableTitle);
// Define new datasource URL and reload
$('#calendar').fullCalendar ('option', 'resources', 'php/server/devices.php?action=getDevicesListCalendar&status='+ deviceStatus);
$('#calendar').fullCalendar ('refetchResources');
const protocol = window.location.protocol.replace(':', '');
const host = window.location.hostname;
const port = getSetting("GRAPHQL_PORT"); // Or Flask server port
const apiToken = getSetting("API_TOKEN");
const apiBase = `${protocol}://${host}:${port}`;
// -----------------------------
// Load Devices as Resources
// -----------------------------
const devicesUrl = `${apiBase}/devices/by-status?status=${deviceStatus}`;
$.ajax({
url: devicesUrl,
method: "GET",
headers: {
"Authorization": `Bearer ${apiToken}`
},
success: function(devices) {
// FullCalendar expects resources array
const resources = devices.map(dev => ({
id: dev.devMac,
title: dev.devName
}));
$('#calendar').fullCalendar('option', 'resources', resources);
$('#calendar').fullCalendar('refetchResources');
}
});
// -----------------------------
// Load Events
// -----------------------------
const eventsUrl = `${apiBase}/sessions/calendar?start=${startDate}&end=${endDate}`;
$('#calendar').fullCalendar('removeEventSources');
$('#calendar').fullCalendar('addEventSource', { url: `php/server/events.php?period=${period}&start=${startDate}&end=${endDate}&action=getEventsCalendar` });
$('#calendar').fullCalendar('addEventSource', {
url: eventsUrl,
method: "GET",
headers: {
"Authorization": `Bearer ${apiToken}`
},
success: function(response) {
// Flask returns { "sessions": [...] } → FullCalendar needs array
const events = response.sessions || [];
$('#calendar').fullCalendar('removeEvents');
$('#calendar').fullCalendar('renderEvents', events, true);
},
error: function(err) {
console.error('Failed to load events:', err);
}
});
};
</script>

View File

@@ -176,10 +176,10 @@ class DeviceInstance:
if "*" in mac:
# Wildcard matching
sql_pattern = mac.replace("*", "%")
cur.execute("DELETE FROM Devices WHERE devMAC LIKE ?", (sql_pattern,))
cur.execute("DELETE FROM Devices WHERE devMac LIKE ?", (sql_pattern,))
else:
# Exact match
cur.execute("DELETE FROM Devices WHERE devMAC = ?", (mac,))
cur.execute("DELETE FROM Devices WHERE devMac = ?", (mac,))
deleted_count += cur.rowcount
conn.commit()
@@ -191,7 +191,7 @@ class DeviceInstance:
"""Delete devices with empty MAC addresses."""
conn = get_temp_db_connection()
cur = conn.cursor()
cur.execute("DELETE FROM Devices WHERE devMAC IS NULL OR devMAC = ''")
cur.execute("DELETE FROM Devices WHERE devMac IS NULL OR devMac = ''")
deleted = cur.rowcount
conn.commit()
conn.close()