mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-04-04 17:21:23 -07:00
FE+BE: use of new sessions endpoint
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
@@ -664,7 +664,9 @@ body
|
|||||||
border-right: 5px solid #606060;
|
border-right: 5px solid #606060;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fc-ltr .fc-time-grid .fc-event-container {
|
||||||
|
margin: 0 2.5% 0 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------------
|
/* -----------------------------------------------------------------------------
|
||||||
Notification float banner
|
Notification float banner
|
||||||
|
|||||||
@@ -210,14 +210,14 @@ function getDeviceData() {
|
|||||||
groupSettings.forEach(setting => {
|
groupSettings.forEach(setting => {
|
||||||
const column = $('<div>'); // Create a column for each setting (Bootstrap column)
|
const column = $('<div>'); // Create a column for each setting (Bootstrap column)
|
||||||
|
|
||||||
console.log(setting);
|
// console.log(setting);
|
||||||
|
|
||||||
// Get the field data (replace 'NEWDEV_' prefix from the key)
|
// Get the field data (replace 'NEWDEV_' prefix from the key)
|
||||||
fieldData = deviceData[setting.setKey.replace('NEWDEV_', '')]
|
fieldData = deviceData[setting.setKey.replace('NEWDEV_', '')]
|
||||||
fieldData = fieldData == null ? "" : fieldData;
|
fieldData = fieldData == null ? "" : fieldData;
|
||||||
fieldOptionsOverride = null;
|
fieldOptionsOverride = null;
|
||||||
|
|
||||||
console.log(setting.setKey);
|
// console.log(setting.setKey);
|
||||||
// console.log(fieldData);
|
// console.log(fieldData);
|
||||||
|
|
||||||
// Additional form elements like the random MAC address button for devMac
|
// Additional form elements like the random MAC address button for devMac
|
||||||
|
|||||||
@@ -30,51 +30,72 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
function loadEventsData() {
|
function loadEventsData() {
|
||||||
const hideConnections = $('#chkHideConnectionEvents')[0].checked;
|
const hideConnections = $('#chkHideConnectionEvents')[0].checked;
|
||||||
const hideConnectionsStr = hideConnections ? 'true' : 'false';
|
const hideConnectionsStr = hideConnections ? 'true' : 'false';
|
||||||
|
|
||||||
mac = getMac()
|
let period = $("#period").val();
|
||||||
|
let { start, end } = getPeriodStartEnd(period);
|
||||||
|
|
||||||
const rawSql = `
|
const rawSql = `
|
||||||
SELECT eve_DateTime, eve_DateTime, eve_EventType, eve_IP, eve_AdditionalInfo
|
SELECT eve_DateTime, eve_EventType, eve_IP, eve_AdditionalInfo
|
||||||
FROM Events
|
FROM Events
|
||||||
WHERE eve_MAC = "${mac}"
|
WHERE eve_MAC = "${mac}"
|
||||||
|
AND eve_DateTime BETWEEN "${start}" AND "${end}"
|
||||||
AND (
|
AND (
|
||||||
(eve_EventType NOT IN ("Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected"))
|
(eve_EventType NOT IN ("Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected"))
|
||||||
OR "${hideConnectionsStr}" = "false"
|
OR "${hideConnectionsStr}" = "false"
|
||||||
)
|
)
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const apiUrl = `php/server/dbHelper.php?action=read&rawSql=${btoa(encodeURIComponent(rawSql))}`;
|
const protocol = window.location.protocol.replace(':', '');
|
||||||
|
const host = window.location.hostname;
|
||||||
|
const port = getSetting("GRAPHQL_PORT");
|
||||||
|
const apiToken = getSetting("API_TOKEN");
|
||||||
|
|
||||||
// Manually load the data first
|
const apiBase = `${protocol}://${host}:${port}`;
|
||||||
$.get(apiUrl, function (data) {
|
const url = `${apiBase}/dbquery/read`;
|
||||||
const parsed = JSON.parse(data);
|
|
||||||
|
|
||||||
const rows = parsed.map(row => {
|
$.ajax({
|
||||||
const rawDate = row.eve_DateTime;
|
url: url,
|
||||||
const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-';
|
method: "POST",
|
||||||
return [
|
contentType: "application/json",
|
||||||
formattedDate,
|
headers: {
|
||||||
row.eve_DateTime,
|
"Authorization": `Bearer ${apiToken}`
|
||||||
row.eve_EventType,
|
},
|
||||||
row.eve_IP,
|
data: JSON.stringify({
|
||||||
row.eve_AdditionalInfo
|
rawSql: btoa(rawSql)
|
||||||
];
|
}),
|
||||||
});
|
success: function (data) {
|
||||||
|
// assuming read_query returns rows directly
|
||||||
|
const rows = data["results"].map(row => {
|
||||||
|
const rawDate = row.eve_DateTime;
|
||||||
|
const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-';
|
||||||
|
|
||||||
// Fill the table manually
|
return [
|
||||||
const table = $('#tableEvents').DataTable();
|
formattedDate,
|
||||||
table.clear();
|
row.eve_DateTime,
|
||||||
table.rows.add(rows); // assuming each row is an array
|
row.eve_EventType,
|
||||||
table.draw();
|
row.eve_IP,
|
||||||
|
row.eve_AdditionalInfo
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
hideSpinner();
|
const table = $('#tableEvents').DataTable();
|
||||||
|
table.clear();
|
||||||
|
table.rows.add(rows);
|
||||||
|
table.draw();
|
||||||
|
|
||||||
|
hideSpinner();
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
console.error("Failed to load events", xhr.responseText);
|
||||||
|
hideSpinner();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function initializeEventsDatatable (eventsRows) {
|
function initializeEventsDatatable (eventsRows) {
|
||||||
|
|
||||||
if ($.fn.dataTable.isDataTable('#tableEvents')) {
|
if ($.fn.dataTable.isDataTable('#tableEvents')) {
|
||||||
|
|||||||
@@ -35,95 +35,49 @@
|
|||||||
|
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
// query data
|
// query data
|
||||||
function loadPresenceData()
|
function loadPresenceData() {
|
||||||
{
|
const protocol = window.location.protocol.replace(":", "");
|
||||||
// Define Presence datasource and query data
|
const host = window.location.hostname;
|
||||||
|
const port = getSetting("GRAPHQL_PORT");
|
||||||
|
const apiToken = getSetting("API_TOKEN");
|
||||||
|
|
||||||
|
const apiBase = `${protocol}://${host}:${port}`;
|
||||||
|
const url = `${apiBase}/sessions/calendar`;
|
||||||
|
|
||||||
$('#calendar').fullCalendar('removeEventSources');
|
$('#calendar').fullCalendar('removeEventSources');
|
||||||
$('#calendar').fullCalendar('addEventSource',
|
|
||||||
{ url: 'php/server/events.php?action=getDevicePresence&mac=' + mac});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------
|
$('#calendar').fullCalendar('addEventSource', function(start, end, timezone, callback) {
|
||||||
function initializeCalendar_() {
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
method: "GET",
|
||||||
var calendarEl = document.getElementById('calendar');
|
dataType: "json",
|
||||||
|
headers: {
|
||||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
"Authorization": `Bearer ${apiToken}`
|
||||||
headerToolbar: {
|
|
||||||
left: 'prev,next today',
|
|
||||||
center: 'title',
|
|
||||||
right: 'dayGridYear,dayGridMonth,timeGridWeek'
|
|
||||||
},
|
|
||||||
initialView: 'dayGridYear',
|
|
||||||
initialDate: '2023-01-12',
|
|
||||||
editable: true,
|
|
||||||
selectable: true,
|
|
||||||
dayMaxEvents: true, // allow "more" link when too many events
|
|
||||||
// businessHours: true,
|
|
||||||
// weekends: false,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
title: 'All Day Event',
|
|
||||||
start: '2023-01-01'
|
|
||||||
},
|
},
|
||||||
{
|
data: {
|
||||||
title: 'Long Event',
|
start: start.format('YYYY-MM-DD'),
|
||||||
start: '2023-01-07',
|
end: end.format('YYYY-MM-DD'),
|
||||||
end: '2023-01-10'
|
mac: mac
|
||||||
},
|
},
|
||||||
{
|
success: function(response) {
|
||||||
groupId: 999,
|
if (response && response.success) {
|
||||||
title: 'Repeating Event',
|
callback(response.sessions || []);
|
||||||
start: '2023-01-09T16:00:00'
|
} else {
|
||||||
|
console.warn("Presence calendar API error:", response);
|
||||||
|
callback([]);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
error: function(xhr) {
|
||||||
groupId: 999,
|
console.error(
|
||||||
title: 'Repeating Event',
|
"Presence calendar request failed:",
|
||||||
start: '2023-01-16T16:00:00'
|
xhr.status,
|
||||||
},
|
xhr.responseText
|
||||||
{
|
);
|
||||||
title: 'Conference',
|
callback([]);
|
||||||
start: '2023-01-11',
|
|
||||||
end: '2023-01-13'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Meeting',
|
|
||||||
start: '2023-01-12T10:30:00',
|
|
||||||
end: '2023-01-12T12:30:00'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Lunch',
|
|
||||||
start: '2023-01-12T12:00:00'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Meeting',
|
|
||||||
start: '2023-01-12T14:30:00'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Happy Hour',
|
|
||||||
start: '2023-01-12T17:30:00'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Dinner',
|
|
||||||
start: '2023-01-12T20:00:00'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Birthday Party',
|
|
||||||
start: '2023-01-13T07:00:00'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Click for Google',
|
|
||||||
url: 'http://google.com/',
|
|
||||||
start: '2023-01-28'
|
|
||||||
}
|
}
|
||||||
]
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
calendar.render();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
function initializeCalendar() {
|
function initializeCalendar() {
|
||||||
@@ -138,7 +92,7 @@ function initializeCalendar() {
|
|||||||
slotDuration : '02:00:00',
|
slotDuration : '02:00:00',
|
||||||
slotLabelInterval : '04:00:00',
|
slotLabelInterval : '04:00:00',
|
||||||
slotLabelFormat : 'H:mm',
|
slotLabelFormat : 'H:mm',
|
||||||
timeFormat : 'H:mm',
|
timeFormat : 'H:mm',
|
||||||
locale : '<?= lang('Presence_CalHead_lang');?>',
|
locale : '<?= lang('Presence_CalHead_lang');?>',
|
||||||
header: {
|
header: {
|
||||||
left : 'prev,next today',
|
left : 'prev,next today',
|
||||||
@@ -146,7 +100,7 @@ function initializeCalendar() {
|
|||||||
right : 'agendaYear,agendaMonth,agendaWeek'
|
right : 'agendaYear,agendaMonth,agendaWeek'
|
||||||
},
|
},
|
||||||
|
|
||||||
views: {
|
views: {
|
||||||
agendaYear: {
|
agendaYear: {
|
||||||
type : 'agenda',
|
type : 'agenda',
|
||||||
duration : { year: 1 },
|
duration : { year: 1 },
|
||||||
@@ -162,16 +116,7 @@ function initializeCalendar() {
|
|||||||
},
|
},
|
||||||
agendaWeek: {
|
agendaWeek: {
|
||||||
buttonText : '<?= lang('Presence_CalHead_week');?>',
|
buttonText : '<?= lang('Presence_CalHead_week');?>',
|
||||||
},
|
|
||||||
agendaDay: {
|
|
||||||
type : 'agenda',
|
|
||||||
duration : { day: 1 },
|
|
||||||
buttonText : '<?= lang('Presence_CalHead_day');?>',
|
|
||||||
slotLabelFormat : 'H',
|
|
||||||
slotDuration : '01:00:00'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
viewRender: function(view) {
|
viewRender: function(view) {
|
||||||
@@ -191,40 +136,40 @@ function initializeCalendar() {
|
|||||||
listContent[i+1].style.borderRightColor = '#808080';
|
listContent[i+1].style.borderRightColor = '#808080';
|
||||||
}
|
}
|
||||||
listHeader[i].style.paddingLeft = '10px';
|
listHeader[i].style.paddingLeft = '10px';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
columnHeaderText: function(mom) {
|
columnHeaderText: function(mom) {
|
||||||
switch ($('#calendar').fullCalendar('getView').name) {
|
switch ($('#calendar').fullCalendar('getView').name) {
|
||||||
case 'agendaYear':
|
case 'agendaYear':
|
||||||
if (mom.date() == 1) {
|
if (mom.date() == 1) {
|
||||||
return mom.format('MMM');
|
return mom.format('MMM');
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'agendaMonth':
|
||||||
|
return mom.date();
|
||||||
|
break;
|
||||||
|
case 'agendaWeek':
|
||||||
|
return mom.format ('ddd D');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return mom.date();
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 'agendaMonth':
|
|
||||||
return mom.date();
|
|
||||||
break;
|
|
||||||
case 'agendaWeek':
|
|
||||||
return mom.format ('ddd D');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return mom.date();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
eventRender: function (event, element) {
|
eventRender: function (event, element) {
|
||||||
// $(element).tooltip({container: 'body', placement: 'bottom', title: event.tooltip});
|
// $(element).tooltip({container: 'body', placement: 'bottom', title: event.tooltip});
|
||||||
element.attr ('title', event.tooltip); // Alternative tooltip
|
element.attr ('title', event.tooltip); // Alternative tooltip
|
||||||
},
|
},
|
||||||
|
|
||||||
loading: function( isLoading, view ) {
|
loading: function( isLoading, view ) {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
showSpinner()
|
showSpinner()
|
||||||
} else {
|
} else {
|
||||||
hideSpinner()
|
hideSpinner()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,7 +195,7 @@ function initDevicePresencePage() {
|
|||||||
|
|
||||||
showSpinner();
|
showSpinner();
|
||||||
|
|
||||||
initializeCalendar();
|
initializeCalendar();
|
||||||
loadPresenceData();
|
loadPresenceData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<!-- ----------------------------------------------------------------------- -->
|
<!-- ----------------------------------------------------------------------- -->
|
||||||
|
|
||||||
|
|
||||||
<!-- Datatable Session -->
|
<!-- Datatable Session -->
|
||||||
<table id="tableSessions" class="table table-bordered table-hover table-striped ">
|
<table id="tableSessions" class="table table-bordered table-hover table-striped ">
|
||||||
@@ -53,20 +53,20 @@ function initializeSessionsDatatable (sessionsRows) {
|
|||||||
{targets: [1,2],
|
{targets: [1,2],
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
// console.log(cellData);
|
// console.log(cellData);
|
||||||
|
|
||||||
if (!cellData.includes("missing event") && !cellData.includes("..."))
|
if (!cellData.includes("missing event") && !cellData.includes("..."))
|
||||||
{
|
{
|
||||||
if (cellData.includes("+")) { // Check if timezone offset is present
|
if (cellData.includes("+")) { // Check if timezone offset is present
|
||||||
cellData = cellData.split('+')[0]; // Remove timezone offset
|
cellData = cellData.split('+')[0]; // Remove timezone offset
|
||||||
}
|
}
|
||||||
// console.log(cellData);
|
// console.log(cellData);
|
||||||
result = localizeTimestamp(cellData);
|
result = localizeTimestamp(cellData);
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
result = translateHTMLcodes(cellData)
|
result = translateHTMLcodes(cellData)
|
||||||
}
|
}
|
||||||
|
|
||||||
$(td).html (result);
|
$(td).html (result);
|
||||||
} }
|
} }
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -94,22 +94,57 @@ function initializeSessionsDatatable (sessionsRows) {
|
|||||||
// INIT with polling for panel element visibility
|
// INIT with polling for panel element visibility
|
||||||
// -----------------------------------------------
|
// -----------------------------------------------
|
||||||
|
|
||||||
// -----------------------------------------------------------
|
// -----------------------------------------------------------
|
||||||
// Init datatable
|
// Init datatable
|
||||||
function loadSessionsData(period){
|
function loadSessionsData() {
|
||||||
const table = $('#tableSessions').DataTable();
|
const table = $('#tableSessions').DataTable();
|
||||||
|
let period = $("#period").val()
|
||||||
|
|
||||||
showSpinner();
|
showSpinner();
|
||||||
|
|
||||||
// table.clear().draw(); // Clear existing data before reloading
|
// Build API base
|
||||||
|
const protocol = window.location.protocol.replace(':', '');
|
||||||
|
const host = window.location.hostname;
|
||||||
|
const port = getSetting("GRAPHQL_PORT"); // or whatever port your Flask API runs on
|
||||||
|
const apiToken = getSetting("API_TOKEN");
|
||||||
|
|
||||||
table.ajax
|
const apiBase = `${protocol}://${host}:${port}`;
|
||||||
.url('php/server/events.php?action=getDeviceSessions&mac=' + getMac() + '&period=' + period)
|
const url = `${apiBase}/sessions/${getMac()}?period=${encodeURIComponent(period)}`;
|
||||||
.load(function () {
|
|
||||||
|
// Call API with Authorization header
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${apiToken}`
|
||||||
|
},
|
||||||
|
success: function (data) {
|
||||||
|
table.clear();
|
||||||
|
|
||||||
|
if (data.success && data.sessions.length) {
|
||||||
|
data.sessions.forEach(session => {
|
||||||
|
table.row.add([
|
||||||
|
session.ses_DateTimeOrder,
|
||||||
|
session.ses_Connection,
|
||||||
|
session.ses_Disconnection,
|
||||||
|
session.ses_Duration,
|
||||||
|
session.ses_IP,
|
||||||
|
session.ses_Info
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
table.draw();
|
||||||
hideSpinner();
|
hideSpinner();
|
||||||
});
|
},
|
||||||
|
error: function (xhr, status, err) {
|
||||||
|
console.error("Failed to load sessions:", err, xhr.responseText);
|
||||||
|
hideSpinner();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var sessionsPageInitialized = false;
|
var sessionsPageInitialized = false;
|
||||||
|
|
||||||
// -----------------------------------------------------------
|
// -----------------------------------------------------------
|
||||||
@@ -128,10 +163,9 @@ function initDeviceSessionsPage()
|
|||||||
showSpinner();
|
showSpinner();
|
||||||
|
|
||||||
var sessionsRows = 10;
|
var sessionsRows = 10;
|
||||||
var period = '1 month';
|
|
||||||
|
|
||||||
initializeSessionsDatatable(sessionsRows);
|
initializeSessionsDatatable(sessionsRows);
|
||||||
loadSessionsData(period);
|
loadSessionsData();
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -144,6 +178,6 @@ function deviceSessionsPageUpdater() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start updater
|
// start updater
|
||||||
deviceSessionsPageUpdater();
|
deviceSessionsPageUpdater();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -373,7 +373,7 @@ function getLangCode() {
|
|||||||
const LOCALE = getSetting('UI_LOCALE') || 'en-GB';
|
const LOCALE = getSetting('UI_LOCALE') || 'en-GB';
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// String utilities
|
// DateTime utilities
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
function localizeTimestamp(input) {
|
function localizeTimestamp(input) {
|
||||||
|
|
||||||
@@ -462,6 +462,54 @@ function localizeTimestamp(input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns start and end date for a given period.
|
||||||
|
* @param {string} period - "1 day", "7 days", "1 month", "1 year", "100 years"
|
||||||
|
* @returns {{start: string, end: string}} - Dates in "YYYY-MM-DD HH:MM:SS" format
|
||||||
|
*/
|
||||||
|
function getPeriodStartEnd(period) {
|
||||||
|
const now = new Date();
|
||||||
|
let start = new Date(now); // default start = now
|
||||||
|
let end = new Date(now); // default end = now
|
||||||
|
|
||||||
|
switch (period) {
|
||||||
|
case "1 day":
|
||||||
|
start.setDate(now.getDate() - 1);
|
||||||
|
break;
|
||||||
|
case "7 days":
|
||||||
|
start.setDate(now.getDate() - 7);
|
||||||
|
break;
|
||||||
|
case "1 month":
|
||||||
|
start.setMonth(now.getMonth() - 1);
|
||||||
|
break;
|
||||||
|
case "1 year":
|
||||||
|
start.setFullYear(now.getFullYear() - 1);
|
||||||
|
break;
|
||||||
|
case "100 years":
|
||||||
|
start = new Date(0); // very old date for "all"
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("Unknown period, using 1 month as default");
|
||||||
|
start.setMonth(now.getMonth() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to format date as "YYYY-MM-DD HH:MM:SS"
|
||||||
|
const formatDate = (date) => {
|
||||||
|
const pad = (n) => String(n).padStart(2, "0");
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
|
||||||
|
`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
start: formatDate(start),
|
||||||
|
end: formatDate(end)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// String utilities
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -938,8 +938,9 @@ def api_get_sessions_calendar():
|
|||||||
# Query params: /sessions/calendar?start=2025-08-01&end=2025-08-21
|
# Query params: /sessions/calendar?start=2025-08-01&end=2025-08-21
|
||||||
start_date = request.args.get("start")
|
start_date = request.args.get("start")
|
||||||
end_date = request.args.get("end")
|
end_date = request.args.get("end")
|
||||||
|
mac = request.args.get("mac")
|
||||||
|
|
||||||
return get_sessions_calendar(start_date, end_date)
|
return get_sessions_calendar(start_date, end_date, mac)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/sessions/<mac>", methods=["GET"])
|
@app.route("/sessions/<mac>", methods=["GET"])
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
|||||||
from database import get_temp_db_connection # noqa: E402 [flake8 lint suppression]
|
from database import get_temp_db_connection # noqa: E402 [flake8 lint suppression]
|
||||||
from helper import get_setting_value, format_ip_long # noqa: E402 [flake8 lint suppression]
|
from helper import get_setting_value, format_ip_long # noqa: E402 [flake8 lint suppression]
|
||||||
from db.db_helper import get_date_from_period # noqa: E402 [flake8 lint suppression]
|
from db.db_helper import get_date_from_period # noqa: E402 [flake8 lint suppression]
|
||||||
from utils.datetime_utils import format_date_iso, format_event_date, format_date_diff, format_date # noqa: E402 [flake8 lint suppression]
|
from utils.datetime_utils import timeNowDB, format_date_iso, format_event_date, format_date_diff, format_date # noqa: E402 [flake8 lint suppression]
|
||||||
|
|
||||||
|
|
||||||
# --------------------------
|
# --------------------------
|
||||||
@@ -88,36 +88,42 @@ def get_sessions(mac=None, start_date=None, end_date=None):
|
|||||||
return jsonify({"success": True, "sessions": table_data})
|
return jsonify({"success": True, "sessions": table_data})
|
||||||
|
|
||||||
|
|
||||||
def get_sessions_calendar(start_date, end_date):
|
def get_sessions_calendar(start_date, end_date, mac):
|
||||||
"""
|
"""
|
||||||
Fetch sessions between a start and end date for calendar display.
|
Fetch sessions between a start and end date for calendar display.
|
||||||
Returns JSON list of calendar sessions.
|
Returns FullCalendar-compatible JSON.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not start_date or not end_date:
|
if not start_date or not end_date:
|
||||||
return jsonify({"success": False, "error": "Missing start or end date"}), 400
|
return jsonify({"success": False, "error": "Missing start or end date"}), 400
|
||||||
|
|
||||||
|
# Normalize MAC (empty string → NULL)
|
||||||
|
mac = mac or None
|
||||||
|
|
||||||
conn = get_temp_db_connection()
|
conn = get_temp_db_connection()
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
sql = """
|
sql = """
|
||||||
-- Correct missing connection/disconnection sessions:
|
|
||||||
-- If ses_EventTypeConnection is missing, backfill from last disconnection
|
|
||||||
-- If ses_EventTypeDisconnection is missing, forward-fill from next connection
|
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
SES1.ses_MAC, SES1.ses_EventTypeConnection, SES1.ses_DateTimeConnection,
|
SES1.ses_MAC,
|
||||||
SES1.ses_EventTypeDisconnection, SES1.ses_DateTimeDisconnection, SES1.ses_IP,
|
SES1.ses_EventTypeConnection,
|
||||||
SES1.ses_AdditionalInfo, SES1.ses_StillConnected,
|
SES1.ses_DateTimeConnection,
|
||||||
|
SES1.ses_EventTypeDisconnection,
|
||||||
|
SES1.ses_DateTimeDisconnection,
|
||||||
|
SES1.ses_IP,
|
||||||
|
SES1.ses_AdditionalInfo,
|
||||||
|
SES1.ses_StillConnected,
|
||||||
|
|
||||||
CASE
|
CASE
|
||||||
WHEN SES1.ses_EventTypeConnection = '<missing event>' THEN
|
WHEN SES1.ses_EventTypeConnection = '<missing event>' THEN
|
||||||
IFNULL(
|
IFNULL(
|
||||||
(SELECT MAX(SES2.ses_DateTimeDisconnection)
|
(
|
||||||
FROM Sessions AS SES2
|
SELECT MAX(SES2.ses_DateTimeDisconnection)
|
||||||
WHERE SES2.ses_MAC = SES1.ses_MAC
|
FROM Sessions AS SES2
|
||||||
AND SES2.ses_DateTimeDisconnection < SES1.ses_DateTimeDisconnection
|
WHERE SES2.ses_MAC = SES1.ses_MAC
|
||||||
AND SES2.ses_DateTimeDisconnection BETWEEN Date(?) AND Date(?)
|
AND SES2.ses_DateTimeDisconnection < SES1.ses_DateTimeDisconnection
|
||||||
|
AND SES2.ses_DateTimeDisconnection BETWEEN Date(?) AND Date(?)
|
||||||
),
|
),
|
||||||
DATETIME(SES1.ses_DateTimeDisconnection, '-1 hour')
|
DATETIME(SES1.ses_DateTimeDisconnection, '-1 hour')
|
||||||
)
|
)
|
||||||
@@ -126,41 +132,46 @@ def get_sessions_calendar(start_date, end_date):
|
|||||||
|
|
||||||
CASE
|
CASE
|
||||||
WHEN SES1.ses_EventTypeDisconnection = '<missing event>' THEN
|
WHEN SES1.ses_EventTypeDisconnection = '<missing event>' THEN
|
||||||
(SELECT MIN(SES2.ses_DateTimeConnection)
|
(
|
||||||
FROM Sessions AS SES2
|
SELECT MIN(SES2.ses_DateTimeConnection)
|
||||||
WHERE SES2.ses_MAC = SES1.ses_MAC
|
FROM Sessions AS SES2
|
||||||
AND SES2.ses_DateTimeConnection > SES1.ses_DateTimeConnection
|
WHERE SES2.ses_MAC = SES1.ses_MAC
|
||||||
AND SES2.ses_DateTimeConnection BETWEEN Date(?) AND Date(?)
|
AND SES2.ses_DateTimeConnection > SES1.ses_DateTimeConnection
|
||||||
|
AND SES2.ses_DateTimeConnection BETWEEN Date(?) AND Date(?)
|
||||||
)
|
)
|
||||||
ELSE SES1.ses_DateTimeDisconnection
|
ELSE SES1.ses_DateTimeDisconnection
|
||||||
END AS ses_DateTimeDisconnectionCorrected
|
END AS ses_DateTimeDisconnectionCorrected
|
||||||
|
|
||||||
FROM Sessions AS SES1
|
FROM Sessions AS SES1
|
||||||
WHERE (SES1.ses_DateTimeConnection BETWEEN Date(?) AND Date(?))
|
WHERE (
|
||||||
|
(SES1.ses_DateTimeConnection BETWEEN Date(?) AND Date(?))
|
||||||
OR (SES1.ses_DateTimeDisconnection BETWEEN Date(?) AND Date(?))
|
OR (SES1.ses_DateTimeDisconnection BETWEEN Date(?) AND Date(?))
|
||||||
OR SES1.ses_StillConnected = 1
|
OR SES1.ses_StillConnected = 1
|
||||||
|
)
|
||||||
|
AND (? IS NULL OR SES1.ses_MAC = ?)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cur.execute(
|
cur.execute(
|
||||||
sql,
|
sql,
|
||||||
(
|
(
|
||||||
start_date,
|
start_date, end_date,
|
||||||
end_date,
|
start_date, end_date,
|
||||||
start_date,
|
start_date, end_date,
|
||||||
end_date,
|
start_date, end_date,
|
||||||
start_date,
|
mac, mac,
|
||||||
end_date,
|
|
||||||
start_date,
|
|
||||||
end_date,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
rows = cur.fetchall()
|
rows = cur.fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
table_data = []
|
now_iso = timeNowDB()
|
||||||
for r in rows:
|
|
||||||
row = dict(r)
|
|
||||||
|
|
||||||
# Determine color
|
events = []
|
||||||
|
for row in rows:
|
||||||
|
row = dict(row)
|
||||||
|
|
||||||
|
# Color logic (unchanged from PHP)
|
||||||
if (
|
if (
|
||||||
row["ses_EventTypeConnection"] == "<missing event>" or row["ses_EventTypeDisconnection"] == "<missing event>"
|
row["ses_EventTypeConnection"] == "<missing event>" or row["ses_EventTypeDisconnection"] == "<missing event>"
|
||||||
):
|
):
|
||||||
@@ -170,28 +181,31 @@ def get_sessions_calendar(start_date, end_date):
|
|||||||
else:
|
else:
|
||||||
color = "#0073b7"
|
color = "#0073b7"
|
||||||
|
|
||||||
# Tooltip
|
# --- IMPORTANT FIX ---
|
||||||
|
# FullCalendar v3 CANNOT handle end = null
|
||||||
|
end_dt = row["ses_DateTimeDisconnectionCorrected"]
|
||||||
|
if not end_dt and row["ses_StillConnected"] == 1:
|
||||||
|
end_dt = now_iso
|
||||||
|
|
||||||
tooltip = (
|
tooltip = (
|
||||||
f"Connection: {format_event_date(row['ses_DateTimeConnection'], row['ses_EventTypeConnection'])}\n"
|
f"Connection: {format_event_date(row['ses_DateTimeConnection'], row['ses_EventTypeConnection'])}\n"
|
||||||
f"Disconnection: {format_event_date(row['ses_DateTimeDisconnection'], row['ses_EventTypeDisconnection'])}\n"
|
f"Disconnection: {format_event_date(row['ses_DateTimeDisconnection'], row['ses_EventTypeDisconnection'])}\n"
|
||||||
f"IP: {row['ses_IP']}"
|
f"IP: {row['ses_IP']}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Append calendar entry
|
events.append(
|
||||||
table_data.append(
|
|
||||||
{
|
{
|
||||||
"resourceId": row["ses_MAC"],
|
"resourceId": row["ses_MAC"],
|
||||||
"title": "",
|
"title": "",
|
||||||
"start": format_date_iso(row["ses_DateTimeConnectionCorrected"]),
|
"start": format_date_iso(row["ses_DateTimeConnectionCorrected"]),
|
||||||
"end": format_date_iso(row["ses_DateTimeDisconnectionCorrected"]),
|
"end": format_date_iso(end_dt),
|
||||||
"color": color,
|
"color": color,
|
||||||
"tooltip": tooltip,
|
"tooltip": tooltip,
|
||||||
"className": "no-border",
|
"className": "no-border",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
conn.close()
|
return jsonify({"success": True, "sessions": events})
|
||||||
return jsonify({"success": True, "sessions": table_data})
|
|
||||||
|
|
||||||
|
|
||||||
def get_device_sessions(mac, period):
|
def get_device_sessions(mac, period):
|
||||||
|
|||||||
Reference in New Issue
Block a user