presence rework #1119, plugin history filter

This commit is contained in:
jokob-sk
2025-07-26 08:45:07 +10:00
parent 0265c41612
commit 170a3c0ae1
7 changed files with 186 additions and 196 deletions

View File

@@ -286,21 +286,21 @@ function main () {
// Events tab toggle conenction events // // Events tab toggle conenction events
$('input').on('ifToggled', function(event){ // $('input').on('ifToggled', function(event){
// Hide / Show Events // // Hide / Show Events
if (event.currentTarget.id == 'chkHideConnectionEvents') { // if (event.currentTarget.id == 'chkHideConnectionEvents') {
getDeviceEvents(); // getDeviceEvents();
} else { // } else {
// Activate save & restore // // Activate save & restore
// activateSaveRestoreData(); // // activateSaveRestoreData();
// Ask skip notifications // // Ask skip notifications
// if (event.currentTarget.id == 'chkArchived' ) { // // if (event.currentTarget.id == 'chkArchived' ) {
// askSkipNotifications(); // // askSkipNotifications();
// } // // }
} // }
}); // });
} }

View File

@@ -7,11 +7,11 @@
<!-- Hide Connections --> <!-- Hide Connections -->
<div class="text-center"> <div class="col-sm-12 col-xs-12">
<label> <label class="col-sm-3 col-xs-10">
<input class="checkbox blue hidden" id="chkHideConnectionEvents" type="checkbox" checked> <?= lang('DevDetail_Events_CheckBox');?>
<?= lang('DevDetail_Events_CheckBox');?>
</label> </label>
<input class="checkbox blue col-sm-1 col-xs-2" id="chkHideConnectionEvents" type="checkbox" onChange="loadEventsData()">
</div> </div>
<!-- Datatable Events --> <!-- Datatable Events -->
@@ -29,21 +29,58 @@
<script> <script>
var eventsRows = 10; var eventsRows = 10;
var eventsHide = true; var eventsHide = true;
var parEventsRows = 'Front_Details_Events_Rows'; var parEventsRows = 'Front_Details_Events_Rows';
var parEventsHide = 'Front_Details_Events_Hide'; var parEventsHide = 'Front_Details_Events_Hide';
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
function loadEventsData() {
const hideConnections = $('#chkHideConnectionEvents')[0].checked;
const hideConnectionsStr = hideConnections ? 'true' : 'false';
const rawSql = `
SELECT eve_DateTime, eve_EventType, eve_IP, eve_AdditionalInfo
FROM Events
WHERE eve_MAC = "${mac}"
AND (
(eve_EventType NOT IN ("Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected"))
OR "${hideConnectionsStr}" = "false"
)
`;
const apiUrl = `php/server/dbHelper.php?action=read&rawSql=${btoa(encodeURIComponent(rawSql))}`;
// 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;
const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-';
return [
formattedDate,
row.eve_EventType,
row.eve_IP,
row.eve_AdditionalInfo
];
});
console.log(rows);
function loadEventsData() { // Fill the table manually
// Define Events datasource and query dada const table = $('#tableEvents').DataTable();
hideConnections = $('#chkHideConnectionEvents')[0].checked; table.clear();
$('#tableEvents').DataTable().ajax.url('php/server/events.php?action=getDeviceEvents&mac=' + mac +'&period='+ period +'&hideConnections='+ hideConnections).load(); table.rows.add(rows); // assuming each row is an array
} table.draw();
});
}
function initializeSessionsDatatable () { function initializeSessionsDatatable () {

View File

@@ -362,18 +362,64 @@ function getLangCode() {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// String utilities // String utilities
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
function localizeTimestamp(input) {
let tz = getSetting("TIMEZONE") || 'Europe/Berlin';
// Convert to string and trim
input = String(input || '').trim();
function localizeTimestamp(result) // Normalize multiple spaces and remove commas
{ const cleaned = input.replace(',', ' ').replace(/\s+/g, ' ');
// contains TZ in format Europe/Berlin
tz = getSetting("TIMEZONE")
// set default if not available or app still loading // DD/MM/YYYY format check
tz == "" ? tz = 'Europe/Berlin' : tz = tz; const dateTimeParts = cleaned.split(' ');
if (dateTimeParts.length >= 2 && dateTimeParts[0].includes('/')) {
const date = new Date(result); // Assumes result is a timestamp or ISO string const [day, month, year] = dateTimeParts[0].split('/');
const formatter = new Intl.DateTimeFormat('default', { const timePart = dateTimeParts[1];
if (day && month && year && timePart) {
const isoString = `${year}-${month}-${day}T${timePart.length === 5 ? timePart + ':00' : timePart}`;
const date = new Date(isoString);
if (!isFinite(date)) return 'b-';
return new Intl.DateTimeFormat('default', {
timeZone: tz,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).format(date);
}
}
// ISO style YYYY-MM-DD HH:mm(:ss)?
const match = cleaned.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})(:\d{2})?$/);
if (match) {
let iso = `${match[1]}T${match[2]}${match[3] || ':00'}`;
const date = new Date(iso);
if (!isFinite(date)) return 'c-';
return new Intl.DateTimeFormat('default', {
timeZone: tz,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).format(date);
}
// Fallback: try to parse any other string input
const date = new Date(input);
if (!isFinite(date)) return 'Failed conversion: ' + input;
return new Intl.DateTimeFormat('default', {
timeZone: tz, timeZone: tz,
year: 'numeric', year: 'numeric',
month: '2-digit', month: '2-digit',
@@ -381,12 +427,12 @@ function localizeTimestamp(result)
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
second: '2-digit', second: '2-digit',
hour12: false // change to true if you want AM/PM format hour12: false
}); }).format(date);
return formatter.format(date);
} }
// ---------------------------------------------------- // ----------------------------------------------------
/** /**
* Replaces double quotes within single-quoted strings, then converts all single quotes to double quotes, * Replaces double quotes within single-quoted strings, then converts all single quotes to double quotes,

View File

@@ -29,7 +29,6 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
case 'getEvents': getEvents(); break; case 'getEvents': getEvents(); break;
case 'getDeviceSessions': getDeviceSessions(); break; case 'getDeviceSessions': getDeviceSessions(); break;
case 'getDevicePresence': getDevicePresence(); break; case 'getDevicePresence': getDevicePresence(); break;
case 'getDeviceEvents': getDeviceEvents(); break;
case 'getEventsCalendar': getEventsCalendar(); break; case 'getEventsCalendar': getEventsCalendar(); break;
default: logServerConsole ('Action: '. $action); break; default: logServerConsole ('Action: '. $action); break;
} }
@@ -410,41 +409,4 @@ function getEventsCalendar() {
echo (json_encode($tableData)); echo (json_encode($tableData));
} }
//------------------------------------------------------------------------------
// Query Device events
//------------------------------------------------------------------------------
function getDeviceEvents() {
global $db;
// Request Parameters
$mac = $_REQUEST['mac'];
$periodDate = getDateFromPeriod();
$hideConnections = $_REQUEST ['hideConnections'];
// SQL
$SQL = 'SELECT eve_DateTime, eve_EventType, eve_IP, eve_AdditionalInfo
FROM Events
WHERE eve_MAC="'. $mac .'" AND eve_DateTime >= '. $periodDate .'
AND ( (eve_EventType <> "Connected" AND eve_EventType <> "Disconnected" AND
eve_EventType <> "VOIDED - Connected" AND eve_EventType <> "VOIDED - Disconnected")
OR "'. $hideConnections .'" = "false" ) ';
$result = $db->query($SQL);
// arrays of rows
$tableData = array();
while ($row = $result -> fetchArray (SQLITE3_NUM)) {
$row[0] = formatDate ($row[0]);
$tableData['data'][] = $row;
}
// Control no rows
if (empty($tableData['data'])) {
$tableData['data'] = '';
}
// Return json
echo (json_encode ($tableData));
}
?> ?>

View File

@@ -344,7 +344,7 @@ function createTabContent(pluginObj) {
// Get data for events, objects, and history related to the plugin // Get data for events, objects, and history related to the plugin
const objectData = getObjectData(prefix, colDefinitions, pluginObj); const objectData = getObjectData(prefix, colDefinitions, pluginObj);
const eventData = getEventData(prefix, colDefinitions); const eventData = getEventData(prefix, colDefinitions, pluginObj);
const historyData = getHistoryData(prefix, colDefinitions, pluginObj); const historyData = getHistoryData(prefix, colDefinitions, pluginObj);
// Append the content structure for the plugin's tab to the content location // Append the content structure for the plugin's tab to the content location
@@ -378,10 +378,10 @@ function getColumnDefinitions(pluginObj) {
return pluginObj["database_column_definitions"].filter(colDef => colDef.show); return pluginObj["database_column_definitions"].filter(colDef => colDef.show);
} }
function getEventData(prefix, colDefinitions) { function getEventData(prefix, colDefinitions, pluginObj) {
// Extract event data specific to the plugin and format it for DataTables // Extract event data specific to the plugin and format it for DataTables
return pluginUnprocessedEvents return pluginUnprocessedEvents
.filter(event => event.Plugin === prefix) // Filter events for the specific plugin .filter(event => event.Plugin === prefix && shouldBeShown(event, pluginObj)) // Filter events for the specific plugin
.map(event => colDefinitions.map(colDef => event[colDef.column] || '')); // Map to the defined columns .map(event => colDefinitions.map(colDef => event[colDef.column] || '')); // Map to the defined columns
} }
@@ -395,7 +395,7 @@ function getObjectData(prefix, colDefinitions, pluginObj) {
function getHistoryData(prefix, colDefinitions, pluginObj) { function getHistoryData(prefix, colDefinitions, pluginObj) {
return pluginHistory return pluginHistory
.filter(history => history.Plugin === prefix) // First, filter based on the plugin prefix .filter(history => history.Plugin === prefix && shouldBeShown(history, pluginObj)) // First, filter based on the plugin prefix
.sort((a, b) => b.Index - a.Index) // Then, sort by the Index field in descending order .sort((a, b) => b.Index - a.Index) // Then, sort by the Index field in descending order
.slice(0, 50) // Limit the result to the first 50 entries .slice(0, 50) // Limit the result to the first 50 entries
.map(object => .map(object =>

View File

@@ -354,31 +354,45 @@ def create_new_devices (db):
sql = db.sql # TO-DO sql = db.sql # TO-DO
startTime = timeNowTZ() startTime = timeNowTZ()
# Insert events for new devices from CurrentScan # Insert events for new devices from CurrentScan (not yet in Devices)
mylog('debug','[New Devices] New devices - 1 Events')
query = f"""INSERT INTO Events (eve_MAC, eve_IP, eve_DateTime,
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail)
SELECT cur_MAC, cur_IP, '{startTime}', 'New Device', cur_Vendor, 1
FROM CurrentScan
WHERE NOT EXISTS (SELECT 1 FROM Devices
WHERE devMac = cur_MAC)
"""
mylog('debug', '[New Devices] Insert "New Device" Events')
query_new_device_events = f"""
INSERT INTO Events (
eve_MAC, eve_IP, eve_DateTime,
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail
)
SELECT cur_MAC, cur_IP, '{startTime}', 'New Device', cur_Vendor, 1
FROM CurrentScan
WHERE NOT EXISTS (
SELECT 1 FROM Devices
WHERE devMac = cur_MAC
)
"""
mylog('debug',f'[New Devices] Log Events Query: {query}') # mylog('debug',f'[New Devices] Log Events Query: {query_new_device_events}')
sql.execute(query) sql.execute(query_new_device_events)
mylog('debug',f'[New Devices] Insert Connection into session table') mylog('debug',f'[New Devices] Insert Connection into session table')
sql.execute (f"""INSERT INTO Sessions (ses_MAC, ses_IP, ses_EventTypeConnection, ses_DateTimeConnection, sql.execute (f"""INSERT INTO Sessions (
ses_EventTypeDisconnection, ses_DateTimeDisconnection, ses_StillConnected, ses_AdditionalInfo) ses_MAC, ses_IP, ses_EventTypeConnection, ses_DateTimeConnection,
SELECT cur_MAC, cur_IP,'Connected','{startTime}', NULL , NULL ,1, cur_Vendor ses_EventTypeDisconnection, ses_DateTimeDisconnection,
FROM CurrentScan ses_StillConnected, ses_AdditionalInfo
WHERE NOT EXISTS (SELECT 1 FROM Sessions )
WHERE ses_MAC = cur_MAC) SELECT cur_MAC, cur_IP, 'Connected', '{startTime}', NULL, NULL, 1, cur_Vendor
FROM CurrentScan
WHERE EXISTS (
SELECT 1 FROM Devices
WHERE devMac = cur_MAC
)
AND NOT EXISTS (
SELECT 1 FROM Sessions
WHERE ses_MAC = cur_MAC AND ses_StillConnected = 1
)
""") """)
# Create new devices from CurrentScan # Create new devices from CurrentScan

View File

@@ -48,10 +48,6 @@ def process_scan (db):
mylog('verbose','[Process Scan] Updating Devices Info') mylog('verbose','[Process Scan] Updating Devices Info')
update_devices_data_from_scan (db) update_devices_data_from_scan (db)
# Void false connection - disconnections
mylog('verbose','[Process Scan] Voiding false (ghost) disconnections')
void_ghost_disconnections (db)
# Pair session events (Connection / Disconnection) # Pair session events (Connection / Disconnection)
mylog('verbose','[Process Scan] Pairing session events (connection / disconnection) ') mylog('verbose','[Process Scan] Pairing session events (connection / disconnection) ')
pair_sessions_events(db) pair_sessions_events(db)
@@ -75,104 +71,39 @@ def process_scan (db):
# Commit changes # Commit changes
db.commitDB() db.commitDB()
#-------------------------------------------------------------------------------
def void_ghost_disconnections (db):
sql = db.sql #TO-DO
startTime = timeNowTZ()
# Void connect ghost events (disconnect event exists in last X min.)
mylog('debug','[Void Ghost Con] - 1 Connect ghost events')
sql.execute("""UPDATE Events SET eve_PairEventRowid = Null,
eve_EventType ='VOIDED - ' || eve_EventType
WHERE eve_MAC != 'Internet'
AND eve_EventType in ('Connected', 'Down Reconnected')
AND eve_DateTime = ?
AND eve_MAC IN (
SELECT Events.eve_MAC
FROM CurrentScan, Devices, Events
WHERE devMac = cur_MAC
AND eve_MAC = cur_MAC
AND eve_EventType = 'Disconnected'
AND eve_DateTime >= DATETIME(?, '-3 minutes')
) """,
(startTime, startTime))
# Void connect paired events
mylog('debug','[Void Ghost Con] - 2 Paired events')
sql.execute("""UPDATE Events SET eve_PairEventRowid = Null
WHERE eve_MAC != 'Internet'
AND eve_PairEventRowid IN (
SELECT Events.RowID
FROM CurrentScan, Devices, Events
WHERE devMac = cur_MAC
AND eve_MAC = cur_MAC
AND eve_EventType = 'Disconnected'
AND eve_DateTime >= DATETIME(?, '-3 minutes')
) """,
(startTime,))
# Void disconnect ghost events
mylog('debug','[Void Ghost Con] - 3 Disconnect ghost events')
sql.execute("""UPDATE Events SET eve_PairEventRowid = Null,
eve_EventType = 'VOIDED - '|| eve_EventType
WHERE eve_MAC != 'Internet'
AND ROWID IN (
SELECT Events.RowID
FROM CurrentScan, Devices, Events
WHERE devMac = cur_MAC
AND eve_MAC = cur_MAC
AND eve_EventType = 'Disconnected'
AND eve_DateTime >= DATETIME(?, '-3 minutes')
) """,
(startTime,))
mylog('debug','[Void Ghost Con] Void Ghost Connections end')
db.commitDB()
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
def pair_sessions_events (db): def pair_sessions_events (db):
# db.commitDB()
sql = db.sql #TO-DO sql = db.sql #TO-DO
mylog('debug', '[Pair Session] - START') # Pair Connection / New Device events
# Step 1: Pair connection-related events with future unpaired disconnections mylog('debug','[Pair Session] - 1 Connections / New Devices')
mylog('debug', '[Pair Session] - 1: Pair Connections → Disconnections') sql.execute ("""UPDATE Events
sql.execute(""" SET eve_PairEventRowid =
UPDATE Events (SELECT ROWID
SET eve_PairEventRowid = ( FROM Events AS EVE2
SELECT E2.ROWID WHERE EVE2.eve_EventType IN ('New Device', 'Connected', 'Down Reconnected',
FROM Events AS E2 'Device Down', 'Disconnected')
WHERE E2.eve_EventType IN ('Disconnected', 'Device Down') AND EVE2.eve_MAC = Events.eve_MAC
AND E2.eve_MAC = Events.eve_MAC AND EVE2.eve_Datetime > Events.eve_DateTime
AND E2.eve_PairEventRowid IS NULL ORDER BY EVE2.eve_DateTime ASC LIMIT 1)
AND E2.eve_DateTime > Events.eve_DateTime WHERE eve_EventType IN ('New Device', 'Connected', 'Down Reconnected')
ORDER BY E2.eve_DateTime ASC AND eve_PairEventRowid IS NULL
LIMIT 1 """ )
)
WHERE eve_EventType IN ('New Device', 'Connected', 'Down Reconnected')
AND eve_PairEventRowid IS NULL
""")
# Step 2: Pair disconnection-related events with previous unpaired connections # Pair Disconnection / Device Down
mylog('debug', '[Pair Session] - 2: Pair Disconnections → Connections') mylog('debug','[Pair Session] - 2 Disconnections')
sql.execute(""" sql.execute ("""UPDATE Events
UPDATE Events SET eve_PairEventRowid =
SET eve_PairEventRowid = ( (SELECT ROWID
SELECT E2.ROWID FROM Events AS EVE2
FROM Events AS E2 WHERE EVE2.eve_PairEventRowid = Events.ROWID)
WHERE E2.eve_EventType IN ('New Device', 'Connected', 'Down Reconnected') WHERE eve_EventType IN ('Device Down', 'Disconnected')
AND E2.eve_MAC = Events.eve_MAC AND eve_PairEventRowid IS NULL
AND E2.eve_PairEventRowid IS NULL """ )
AND E2.eve_DateTime < Events.eve_DateTime
ORDER BY E2.eve_DateTime DESC
LIMIT 1
)
WHERE eve_EventType IN ('Disconnected', 'Device Down')
AND eve_PairEventRowid IS NULL
""")
mylog('debug', '[Pair Session] - END')
mylog('debug','[Pair Session] Pair session end')
db.commitDB() db.commitDB()