mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-03-30 23:03:03 -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;
|
||||
}
|
||||
|
||||
|
||||
.fc-ltr .fc-time-grid .fc-event-container {
|
||||
margin: 0 2.5% 0 0px !important;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Notification float banner
|
||||
|
||||
@@ -210,14 +210,14 @@ function getDeviceData() {
|
||||
groupSettings.forEach(setting => {
|
||||
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)
|
||||
fieldData = deviceData[setting.setKey.replace('NEWDEV_', '')]
|
||||
fieldData = fieldData == null ? "" : fieldData;
|
||||
fieldOptionsOverride = null;
|
||||
|
||||
console.log(setting.setKey);
|
||||
// console.log(setting.setKey);
|
||||
// console.log(fieldData);
|
||||
|
||||
// Additional form elements like the random MAC address button for devMac
|
||||
|
||||
@@ -30,51 +30,72 @@
|
||||
|
||||
<script>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function loadEventsData() {
|
||||
const hideConnections = $('#chkHideConnectionEvents')[0].checked;
|
||||
const hideConnectionsStr = hideConnections ? 'true' : 'false';
|
||||
|
||||
mac = getMac()
|
||||
let period = $("#period").val();
|
||||
let { start, end } = getPeriodStartEnd(period);
|
||||
|
||||
const rawSql = `
|
||||
SELECT eve_DateTime, eve_DateTime, eve_EventType, eve_IP, eve_AdditionalInfo
|
||||
SELECT eve_DateTime, eve_EventType, eve_IP, eve_AdditionalInfo
|
||||
FROM Events
|
||||
WHERE eve_MAC = "${mac}"
|
||||
AND eve_DateTime BETWEEN "${start}" AND "${end}"
|
||||
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))}`;
|
||||
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
|
||||
$.get(apiUrl, function (data) {
|
||||
const parsed = JSON.parse(data);
|
||||
const apiBase = `${protocol}://${host}:${port}`;
|
||||
const url = `${apiBase}/dbquery/read`;
|
||||
|
||||
const rows = parsed.map(row => {
|
||||
const rawDate = row.eve_DateTime;
|
||||
const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-';
|
||||
return [
|
||||
formattedDate,
|
||||
row.eve_DateTime,
|
||||
row.eve_EventType,
|
||||
row.eve_IP,
|
||||
row.eve_AdditionalInfo
|
||||
];
|
||||
});
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: "POST",
|
||||
contentType: "application/json",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${apiToken}`
|
||||
},
|
||||
data: JSON.stringify({
|
||||
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
|
||||
const table = $('#tableEvents').DataTable();
|
||||
table.clear();
|
||||
table.rows.add(rows); // assuming each row is an array
|
||||
table.draw();
|
||||
return [
|
||||
formattedDate,
|
||||
row.eve_DateTime,
|
||||
row.eve_EventType,
|
||||
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) {
|
||||
|
||||
if ($.fn.dataTable.isDataTable('#tableEvents')) {
|
||||
|
||||
@@ -35,95 +35,49 @@
|
||||
|
||||
// ---------------------------------------
|
||||
// query data
|
||||
function loadPresenceData()
|
||||
{
|
||||
// Define Presence datasource and query data
|
||||
function loadPresenceData() {
|
||||
const protocol = window.location.protocol.replace(":", "");
|
||||
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('addEventSource',
|
||||
{ url: 'php/server/events.php?action=getDevicePresence&mac=' + mac});
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
function initializeCalendar_() {
|
||||
|
||||
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
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'
|
||||
$('#calendar').fullCalendar('addEventSource', function(start, end, timezone, callback) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${apiToken}`
|
||||
},
|
||||
{
|
||||
title: 'Long Event',
|
||||
start: '2023-01-07',
|
||||
end: '2023-01-10'
|
||||
data: {
|
||||
start: start.format('YYYY-MM-DD'),
|
||||
end: end.format('YYYY-MM-DD'),
|
||||
mac: mac
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-09T16:00:00'
|
||||
success: function(response) {
|
||||
if (response && response.success) {
|
||||
callback(response.sessions || []);
|
||||
} else {
|
||||
console.warn("Presence calendar API error:", response);
|
||||
callback([]);
|
||||
}
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-16T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
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'
|
||||
error: function(xhr) {
|
||||
console.error(
|
||||
"Presence calendar request failed:",
|
||||
xhr.status,
|
||||
xhr.responseText
|
||||
);
|
||||
callback([]);
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function initializeCalendar() {
|
||||
@@ -138,7 +92,7 @@ function initializeCalendar() {
|
||||
slotDuration : '02:00:00',
|
||||
slotLabelInterval : '04:00:00',
|
||||
slotLabelFormat : 'H:mm',
|
||||
timeFormat : 'H:mm',
|
||||
timeFormat : 'H:mm',
|
||||
locale : '<?= lang('Presence_CalHead_lang');?>',
|
||||
header: {
|
||||
left : 'prev,next today',
|
||||
@@ -146,7 +100,7 @@ function initializeCalendar() {
|
||||
right : 'agendaYear,agendaMonth,agendaWeek'
|
||||
},
|
||||
|
||||
views: {
|
||||
views: {
|
||||
agendaYear: {
|
||||
type : 'agenda',
|
||||
duration : { year: 1 },
|
||||
@@ -162,16 +116,7 @@ function initializeCalendar() {
|
||||
},
|
||||
agendaWeek: {
|
||||
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) {
|
||||
@@ -191,40 +136,40 @@ function initializeCalendar() {
|
||||
listContent[i+1].style.borderRightColor = '#808080';
|
||||
}
|
||||
listHeader[i].style.paddingLeft = '10px';
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
columnHeaderText: function(mom) {
|
||||
switch ($('#calendar').fullCalendar('getView').name) {
|
||||
case 'agendaYear':
|
||||
if (mom.date() == 1) {
|
||||
return mom.format('MMM');
|
||||
} else {
|
||||
return '';
|
||||
case 'agendaYear':
|
||||
if (mom.date() == 1) {
|
||||
return mom.format('MMM');
|
||||
} else {
|
||||
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) {
|
||||
// $(element).tooltip({container: 'body', placement: 'bottom', title: event.tooltip});
|
||||
element.attr ('title', event.tooltip); // Alternative tooltip
|
||||
},
|
||||
|
||||
|
||||
loading: function( isLoading, view ) {
|
||||
if (isLoading) {
|
||||
showSpinner()
|
||||
} else {
|
||||
} else {
|
||||
hideSpinner()
|
||||
}
|
||||
}
|
||||
@@ -250,7 +195,7 @@ function initDevicePresencePage() {
|
||||
|
||||
showSpinner();
|
||||
|
||||
initializeCalendar();
|
||||
initializeCalendar();
|
||||
loadPresenceData();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
?>
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
|
||||
<!-- Datatable Session -->
|
||||
<table id="tableSessions" class="table table-bordered table-hover table-striped ">
|
||||
@@ -53,20 +53,20 @@ function initializeSessionsDatatable (sessionsRows) {
|
||||
{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);
|
||||
|
||||
$(td).html (result);
|
||||
} }
|
||||
],
|
||||
|
||||
@@ -94,22 +94,57 @@ function initializeSessionsDatatable (sessionsRows) {
|
||||
// INIT with polling for panel element visibility
|
||||
// -----------------------------------------------
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// -----------------------------------------------------------
|
||||
// Init datatable
|
||||
function loadSessionsData(period){
|
||||
function loadSessionsData() {
|
||||
const table = $('#tableSessions').DataTable();
|
||||
let period = $("#period").val()
|
||||
|
||||
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
|
||||
.url('php/server/events.php?action=getDeviceSessions&mac=' + getMac() + '&period=' + period)
|
||||
.load(function () {
|
||||
const apiBase = `${protocol}://${host}:${port}`;
|
||||
const url = `${apiBase}/sessions/${getMac()}?period=${encodeURIComponent(period)}`;
|
||||
|
||||
// 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();
|
||||
});
|
||||
},
|
||||
error: function (xhr, status, err) {
|
||||
console.error("Failed to load sessions:", err, xhr.responseText);
|
||||
hideSpinner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var sessionsPageInitialized = false;
|
||||
|
||||
// -----------------------------------------------------------
|
||||
@@ -128,10 +163,9 @@ function initDeviceSessionsPage()
|
||||
showSpinner();
|
||||
|
||||
var sessionsRows = 10;
|
||||
var period = '1 month';
|
||||
|
||||
initializeSessionsDatatable(sessionsRows);
|
||||
loadSessionsData(period);
|
||||
loadSessionsData();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -144,6 +178,6 @@ function deviceSessionsPageUpdater() {
|
||||
}
|
||||
|
||||
// start updater
|
||||
deviceSessionsPageUpdater();
|
||||
deviceSessionsPageUpdater();
|
||||
|
||||
</script>
|
||||
@@ -373,7 +373,7 @@ function getLangCode() {
|
||||
const LOCALE = getSetting('UI_LOCALE') || 'en-GB';
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// String utilities
|
||||
// DateTime utilities
|
||||
// -----------------------------------------------------------------------------
|
||||
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
|
||||
start_date = request.args.get("start")
|
||||
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"])
|
||||
|
||||
@@ -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 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 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})
|
||||
|
||||
|
||||
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.
|
||||
Returns JSON list of calendar sessions.
|
||||
Returns FullCalendar-compatible JSON.
|
||||
"""
|
||||
|
||||
if not start_date or not end_date:
|
||||
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.row_factory = sqlite3.Row
|
||||
cur = conn.cursor()
|
||||
|
||||
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
|
||||
SES1.ses_MAC, SES1.ses_EventTypeConnection, SES1.ses_DateTimeConnection,
|
||||
SES1.ses_EventTypeDisconnection, SES1.ses_DateTimeDisconnection, SES1.ses_IP,
|
||||
SES1.ses_AdditionalInfo, SES1.ses_StillConnected,
|
||||
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(?) AND Date(?)
|
||||
(
|
||||
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(?) AND Date(?)
|
||||
),
|
||||
DATETIME(SES1.ses_DateTimeDisconnection, '-1 hour')
|
||||
)
|
||||
@@ -126,41 +132,46 @@ def get_sessions_calendar(start_date, end_date):
|
||||
|
||||
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(?) AND Date(?)
|
||||
(
|
||||
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(?) AND Date(?)
|
||||
)
|
||||
ELSE SES1.ses_DateTimeDisconnection
|
||||
END AS ses_DateTimeDisconnectionCorrected
|
||||
|
||||
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_StillConnected = 1
|
||||
)
|
||||
AND (? IS NULL OR SES1.ses_MAC = ?)
|
||||
"""
|
||||
|
||||
cur.execute(
|
||||
sql,
|
||||
(
|
||||
start_date,
|
||||
end_date,
|
||||
start_date,
|
||||
end_date,
|
||||
start_date,
|
||||
end_date,
|
||||
start_date,
|
||||
end_date,
|
||||
start_date, end_date,
|
||||
start_date, end_date,
|
||||
start_date, end_date,
|
||||
start_date, end_date,
|
||||
mac, mac,
|
||||
),
|
||||
)
|
||||
|
||||
rows = cur.fetchall()
|
||||
conn.close()
|
||||
|
||||
table_data = []
|
||||
for r in rows:
|
||||
row = dict(r)
|
||||
now_iso = timeNowDB()
|
||||
|
||||
# Determine color
|
||||
events = []
|
||||
for row in rows:
|
||||
row = dict(row)
|
||||
|
||||
# Color logic (unchanged from PHP)
|
||||
if (
|
||||
row["ses_EventTypeConnection"] == "<missing event>" or row["ses_EventTypeDisconnection"] == "<missing event>"
|
||||
):
|
||||
@@ -170,28 +181,31 @@ def get_sessions_calendar(start_date, end_date):
|
||||
else:
|
||||
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 = (
|
||||
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"IP: {row['ses_IP']}"
|
||||
)
|
||||
|
||||
# Append calendar entry
|
||||
table_data.append(
|
||||
events.append(
|
||||
{
|
||||
"resourceId": row["ses_MAC"],
|
||||
"title": "",
|
||||
"start": format_date_iso(row["ses_DateTimeConnectionCorrected"]),
|
||||
"end": format_date_iso(row["ses_DateTimeDisconnectionCorrected"]),
|
||||
"end": format_date_iso(end_dt),
|
||||
"color": color,
|
||||
"tooltip": tooltip,
|
||||
"className": "no-border",
|
||||
}
|
||||
)
|
||||
|
||||
conn.close()
|
||||
return jsonify({"success": True, "sessions": table_data})
|
||||
return jsonify({"success": True, "sessions": events})
|
||||
|
||||
|
||||
def get_device_sessions(mac, period):
|
||||
|
||||
Reference in New Issue
Block a user