mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 01:26:11 -08:00
384 lines
12 KiB
Python
Executable File
384 lines
12 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import json
|
|
import subprocess
|
|
import argparse
|
|
import os
|
|
import pathlib
|
|
import sqlite3
|
|
import time
|
|
import sys
|
|
from datetime import datetime
|
|
from flask import jsonify, request
|
|
|
|
# Register NetAlertX directories
|
|
INSTALL_PATH="/app"
|
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
|
|
|
from database import get_temp_db_connection
|
|
from helper import is_random_mac, format_date, get_setting_value, format_date_iso, format_event_date, mylog, timeNowTZ, format_date_diff, format_ip_long, parse_datetime
|
|
from db.db_helper import row_to_json, get_date_from_period
|
|
|
|
|
|
# --------------------------
|
|
# Sessions Endpoints Functions
|
|
# --------------------------
|
|
# -------------------------------------------------------------------------------------------
|
|
def create_session(mac, ip, start_time, end_time=None, event_type_conn="Connected", event_type_disc="Disconnected"):
|
|
"""Insert a new session into Sessions table"""
|
|
conn = get_temp_db_connection()
|
|
cur = conn.cursor()
|
|
|
|
cur.execute("""
|
|
INSERT INTO Sessions (ses_MAC, ses_IP, ses_DateTimeConnection, ses_DateTimeDisconnection,
|
|
ses_EventTypeConnection, ses_EventTypeDisconnection)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
""", (mac, ip, start_time, end_time, event_type_conn, event_type_disc))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return jsonify({"success": True, "message": f"Session created for MAC {mac}"})
|
|
|
|
|
|
# -------------------------------------------------------------------------------------------
|
|
def delete_session(mac):
|
|
"""Delete all sessions for a given MAC"""
|
|
conn = get_temp_db_connection()
|
|
cur = conn.cursor()
|
|
|
|
cur.execute("DELETE FROM Sessions WHERE ses_MAC = ?", (mac,))
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return jsonify({"success": True, "message": f"Deleted sessions for MAC {mac}"})
|
|
|
|
|
|
# -------------------------------------------------------------------------------------------
|
|
def get_sessions(mac=None, start_date=None, end_date=None):
|
|
"""Retrieve sessions optionally filtered by MAC and date range"""
|
|
conn = get_temp_db_connection()
|
|
cur = conn.cursor()
|
|
|
|
sql = "SELECT * FROM Sessions WHERE 1=1"
|
|
params = []
|
|
|
|
if mac:
|
|
sql += " AND ses_MAC = ?"
|
|
params.append(mac)
|
|
if start_date:
|
|
sql += " AND ses_DateTimeConnection >= ?"
|
|
params.append(start_date)
|
|
if end_date:
|
|
sql += " AND ses_DateTimeDisconnection <= ?"
|
|
params.append(end_date)
|
|
|
|
cur.execute(sql, tuple(params))
|
|
rows = cur.fetchall()
|
|
conn.close()
|
|
|
|
# Convert rows to list of dicts
|
|
table_data = [dict(r) for r in rows]
|
|
|
|
return jsonify({"success": True, "sessions": table_data})
|
|
|
|
|
|
|
|
def get_sessions_calendar(start_date, end_date):
|
|
"""
|
|
Fetch sessions between a start and end date for calendar display.
|
|
Returns JSON list of calendar sessions.
|
|
"""
|
|
|
|
if not start_date or not end_date:
|
|
return jsonify({"success": False, "error": "Missing start or end date"}), 400
|
|
|
|
conn = get_temp_db_connection()
|
|
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,
|
|
|
|
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(?)
|
|
),
|
|
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(?) AND Date(?)
|
|
)
|
|
ELSE SES1.ses_DateTimeDisconnection
|
|
END AS ses_DateTimeDisconnectionCorrected
|
|
|
|
FROM Sessions AS SES1
|
|
WHERE (SES1.ses_DateTimeConnection BETWEEN Date(?) AND Date(?))
|
|
OR (SES1.ses_DateTimeDisconnection BETWEEN Date(?) AND Date(?))
|
|
OR SES1.ses_StillConnected = 1
|
|
"""
|
|
|
|
cur.execute(sql, (start_date, end_date, start_date, end_date, start_date, end_date, start_date, end_date))
|
|
rows = cur.fetchall()
|
|
|
|
table_data = []
|
|
for r in rows:
|
|
row = dict(r)
|
|
|
|
# Determine color
|
|
if row["ses_EventTypeConnection"] == "<missing event>" or row["ses_EventTypeDisconnection"] == "<missing event>":
|
|
color = "#f39c12"
|
|
elif row["ses_StillConnected"] == 1:
|
|
color = "#00a659"
|
|
else:
|
|
color = "#0073b7"
|
|
|
|
# Tooltip
|
|
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({
|
|
"resourceId": row["ses_MAC"],
|
|
"title": "",
|
|
"start": format_date_iso(row["ses_DateTimeConnectionCorrected"]),
|
|
"end": format_date_iso(row["ses_DateTimeDisconnectionCorrected"]),
|
|
"color": color,
|
|
"tooltip": tooltip,
|
|
"className": "no-border"
|
|
})
|
|
|
|
conn.close()
|
|
return jsonify({"success": True, "sessions": table_data})
|
|
|
|
|
|
|
|
def get_device_sessions(mac, period):
|
|
"""
|
|
Fetch device sessions for a given MAC address and period.
|
|
"""
|
|
period_date = get_date_from_period(period)
|
|
|
|
conn = get_temp_db_connection()
|
|
cur = conn.cursor()
|
|
|
|
sql = f"""
|
|
SELECT
|
|
IFNULL(ses_DateTimeConnection, ses_DateTimeDisconnection) AS ses_DateTimeOrder,
|
|
ses_EventTypeConnection,
|
|
ses_DateTimeConnection,
|
|
ses_EventTypeDisconnection,
|
|
ses_DateTimeDisconnection,
|
|
ses_StillConnected,
|
|
ses_IP,
|
|
ses_AdditionalInfo
|
|
FROM Sessions
|
|
WHERE ses_MAC = ?
|
|
AND (
|
|
ses_DateTimeConnection >= {period_date}
|
|
OR ses_DateTimeDisconnection >= {period_date}
|
|
OR ses_StillConnected = 1
|
|
)
|
|
"""
|
|
|
|
|
|
cur.execute(sql, (mac,))
|
|
rows = cur.fetchall()
|
|
conn.close()
|
|
|
|
table_data = {"data": []}
|
|
|
|
for row in rows:
|
|
# Connection DateTime
|
|
if row["ses_EventTypeConnection"] == "<missing event>":
|
|
ini = row["ses_EventTypeConnection"]
|
|
else:
|
|
ini = format_date(row["ses_DateTimeConnection"])
|
|
|
|
# Disconnection DateTime
|
|
if row["ses_StillConnected"]:
|
|
end = "..."
|
|
elif row["ses_EventTypeDisconnection"] == "<missing event>":
|
|
end = row["ses_EventTypeDisconnection"]
|
|
else:
|
|
end = format_date(row["ses_DateTimeDisconnection"])
|
|
|
|
# Duration
|
|
if row["ses_EventTypeConnection"] in ("<missing event>", None) or row["ses_EventTypeDisconnection"] in ("<missing event>", None):
|
|
dur = "..."
|
|
elif row["ses_StillConnected"]:
|
|
dur = format_date_diff(row["ses_DateTimeConnection"], None)["text"]
|
|
else:
|
|
dur = format_date_diff(row["ses_DateTimeConnection"], row["ses_DateTimeDisconnection"])["text"]
|
|
|
|
# Additional Info
|
|
info = row["ses_AdditionalInfo"]
|
|
if row["ses_EventTypeConnection"] == "New Device":
|
|
info = f"{row['ses_EventTypeConnection']}: {info}"
|
|
|
|
# Push row data
|
|
table_data["data"].append({
|
|
"ses_MAC": mac,
|
|
"ses_DateTimeOrder": row["ses_DateTimeOrder"],
|
|
"ses_Connection": ini,
|
|
"ses_Disconnection": end,
|
|
"ses_Duration": dur,
|
|
"ses_IP": row["ses_IP"],
|
|
"ses_Info": info,
|
|
})
|
|
|
|
# Control no rows
|
|
if not table_data["data"]:
|
|
table_data["data"] = []
|
|
|
|
sessions = table_data["data"]
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"sessions": sessions
|
|
})
|
|
|
|
|
|
def get_session_events(event_type, period_date):
|
|
"""
|
|
Fetch events or sessions based on type and period.
|
|
"""
|
|
conn = get_temp_db_connection()
|
|
conn.row_factory = sqlite3.Row
|
|
cur = conn.cursor()
|
|
|
|
# Base SQLs
|
|
sql_events = f"""
|
|
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 >= {period_date}
|
|
"""
|
|
|
|
sql_sessions = f"""
|
|
SELECT
|
|
IFNULL(ses_DateTimeConnection, ses_DateTimeDisconnection) AS ses_DateTimeOrder,
|
|
devName,
|
|
devOwner,
|
|
NULL,
|
|
NULL,
|
|
ses_DateTimeConnection,
|
|
ses_DateTimeDisconnection,
|
|
NULL,
|
|
NULL,
|
|
ses_IP,
|
|
NULL,
|
|
ses_AdditionalInfo,
|
|
ses_StillConnected,
|
|
devMac
|
|
FROM Sessions_Devices
|
|
"""
|
|
|
|
# Build SQL based on type
|
|
if event_type == "all":
|
|
sql = sql_events
|
|
elif event_type == "sessions":
|
|
sql = sql_sessions + f"""
|
|
WHERE (
|
|
ses_DateTimeConnection >= {period_date}
|
|
OR ses_DateTimeDisconnection >= {period_date}
|
|
OR ses_StillConnected = 1
|
|
)
|
|
"""
|
|
elif event_type == "missing":
|
|
sql = sql_sessions + f"""
|
|
WHERE (
|
|
(ses_DateTimeConnection IS NULL AND ses_DateTimeDisconnection >= {period_date})
|
|
OR (ses_DateTimeDisconnection IS NULL AND ses_StillConnected = 0 AND ses_DateTimeConnection >= {period_date})
|
|
)
|
|
"""
|
|
elif event_type == "voided":
|
|
sql = sql_events + ' AND eve_EventType LIKE "VOIDED%"'
|
|
elif event_type == "new":
|
|
sql = sql_events + ' AND eve_EventType = "New Device"'
|
|
elif event_type == "down":
|
|
sql = sql_events + ' AND eve_EventType = "Device Down"'
|
|
else:
|
|
sql = sql_events + ' AND 1=0'
|
|
|
|
cur.execute(sql)
|
|
rows = cur.fetchall()
|
|
conn.close()
|
|
|
|
table_data = {"data": []}
|
|
|
|
for row in rows:
|
|
row = list(row) # make mutable
|
|
|
|
if event_type in ("sessions", "missing"):
|
|
# Duration
|
|
if row[5] and row[6]:
|
|
delta = format_date_diff(row[5], row[6])
|
|
row[7] = delta["text"]
|
|
row[8] = int(delta["total_minutes"] * 60) # seconds
|
|
elif row[12] == 1:
|
|
delta = format_date_diff(row[5], None)
|
|
row[7] = delta["text"]
|
|
row[8] = int(delta["total_minutes"] * 60) # seconds
|
|
else:
|
|
row[7] = "..."
|
|
row[8] = 0
|
|
|
|
# Connection
|
|
row[5] = format_date(row[5]) if row[5] else "<missing event>"
|
|
|
|
# Disconnection
|
|
if row[6]:
|
|
row[6] = format_date(row[6])
|
|
elif row[12] == 0:
|
|
row[6] = "<missing event>"
|
|
else:
|
|
row[6] = "..."
|
|
|
|
else:
|
|
# Event Date
|
|
row[3] = format_date(row[3])
|
|
|
|
# IP Order
|
|
row[10] = format_ip_long(row[9])
|
|
|
|
table_data["data"].append(row)
|
|
|
|
return jsonify(table_data)
|