diff --git a/front/css/app.css b/front/css/app.css index 114e5655..d96ffdc7 100755 --- a/front/css/app.css +++ b/front/css/app.css @@ -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 diff --git a/front/deviceDetailsEdit.php b/front/deviceDetailsEdit.php index 8efcb855..971396b0 100755 --- a/front/deviceDetailsEdit.php +++ b/front/deviceDetailsEdit.php @@ -210,14 +210,14 @@ function getDeviceData() { groupSettings.forEach(setting => { const column = $('
'); // 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 diff --git a/front/deviceDetailsEvents.php b/front/deviceDetailsEvents.php index a8d988d5..9c2351fa 100755 --- a/front/deviceDetailsEvents.php +++ b/front/deviceDetailsEvents.php @@ -30,51 +30,72 @@ \ No newline at end of file diff --git a/front/js/common.js b/front/js/common.js index 15849d52..2ea86506 100755 --- a/front/js/common.js +++ b/front/js/common.js @@ -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 +// ----------------------------------------------------------------------------- + // ---------------------------------------------------- /** diff --git a/server/api_server/api_server_start.py b/server/api_server/api_server_start.py index 74edfa36..a374f8c4 100755 --- a/server/api_server/api_server_start.py +++ b/server/api_server/api_server_start.py @@ -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/", methods=["GET"]) diff --git a/server/api_server/sessions_endpoint.py b/server/api_server/sessions_endpoint.py index 225dbe39..8bf16e38 100755 --- a/server/api_server/sessions_endpoint.py +++ b/server/api_server/sessions_endpoint.py @@ -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 = '' 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 = '' 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"] == "" or row["ses_EventTypeDisconnection"] == "" ): @@ -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):