mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
2
.github/copilot-instructions.md
vendored
2
.github/copilot-instructions.md
vendored
@@ -42,7 +42,7 @@ Backend loop phases (see `server/__main__.py` and `server/plugin.py`): `once`, `
|
|||||||
## Conventions & helpers to reuse
|
## Conventions & helpers to reuse
|
||||||
- Settings: add/modify via `ccd()` in `server/initialise.py` or per‑plugin manifest. Never hardcode ports or secrets; use `get_setting_value()`.
|
- Settings: add/modify via `ccd()` in `server/initialise.py` or per‑plugin manifest. Never hardcode ports or secrets; use `get_setting_value()`.
|
||||||
- Logging: use `logger.mylog(level, [message])`; levels: none/minimal/verbose/debug/trace.
|
- Logging: use `logger.mylog(level, [message])`; levels: none/minimal/verbose/debug/trace.
|
||||||
- Time/MAC/strings: `helper.py` (`timeNowTZ`, `normalize_mac`, sanitizers). Validate MACs before DB writes.
|
- Time/MAC/strings: `helper.py` (`timeNowDB`, `normalize_mac`, sanitizers). Validate MACs before DB writes.
|
||||||
- DB helpers: prefer `server/db/db_helper.py` functions (e.g., `get_table_json`, device condition helpers) over raw SQL in new paths.
|
- DB helpers: prefer `server/db/db_helper.py` functions (e.g., `get_table_json`, device condition helpers) over raw SQL in new paths.
|
||||||
|
|
||||||
## Dev workflow (devcontainer)
|
## Dev workflow (devcontainer)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ You can completely ignore detected devices globally. This could be because your
|
|||||||
|
|
||||||
## Ignoring notifications 🔕
|
## Ignoring notifications 🔕
|
||||||
|
|
||||||
You can filter out unwanted notifications globally. This could be because of a misbehaving device (GoogleNest/GoogleHub (See also [ARPSAN docs and the `--exclude-broadcast` flag](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/arp_scan#ip-flipping-on-google-nest-devices))) which flips between IP addresses, or because you want to ignore new device notifications of a certian pattern.
|
You can filter out unwanted notifications globally. This could be because of a misbehaving device (GoogleNest/GoogleHub (See also [ARPSAN docs and the `--exclude-broadcast` flag](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/arp_scan#ip-flipping-on-google-nest-devices))) which flips between IP addresses, or because you want to ignore new device notifications of a certain pattern.
|
||||||
|
|
||||||
1. Events Filter (`NTFPRCS_event_condition`) - filter out Events from notifications.
|
1. Events Filter (`NTFPRCS_event_condition`) - filter out Events from notifications.
|
||||||
2. New Devices Filter (`NTFPRCS_new_dev_condition`) - filter out New Devices from notifications, but log and keep a new device in the system.
|
2. New Devices Filter (`NTFPRCS_new_dev_condition`) - filter out New Devices from notifications, but log and keep a new device in the system.
|
||||||
@@ -16,7 +16,8 @@ import conf
|
|||||||
from const import confFileName, logPath
|
from const import confFileName, logPath
|
||||||
from plugin_helper import Plugin_Objects
|
from plugin_helper import Plugin_Objects
|
||||||
from logger import mylog, Logger, append_line_to_file
|
from logger import mylog, Logger, append_line_to_file
|
||||||
from helper import timeNowDB, get_setting_value
|
from helper import get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from models.notification_instance import NotificationInstance
|
from models.notification_instance import NotificationInstance
|
||||||
from database import DB
|
from database import DB
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ import conf
|
|||||||
from const import confFileName, logPath
|
from const import confFileName, logPath
|
||||||
from plugin_helper import Plugin_Objects
|
from plugin_helper import Plugin_Objects
|
||||||
from logger import mylog, Logger, append_line_to_file
|
from logger import mylog, Logger, append_line_to_file
|
||||||
from helper import timeNowDB, get_setting_value, hide_email
|
from helper import get_setting_value, hide_email
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from models.notification_instance import NotificationInstance
|
from models.notification_instance import NotificationInstance
|
||||||
from database import DB
|
from database import DB
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ from const import confFileName, logPath
|
|||||||
from plugin_utils import getPluginObject
|
from plugin_utils import getPluginObject
|
||||||
from plugin_helper import Plugin_Objects
|
from plugin_helper import Plugin_Objects
|
||||||
from logger import mylog, Logger
|
from logger import mylog, Logger
|
||||||
from helper import timeNowDB, get_setting_value, bytes_to_string, \
|
from helper import get_setting_value, bytes_to_string, \
|
||||||
sanitize_string, normalize_string
|
sanitize_string, normalize_string
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from database import DB, get_device_stats
|
from database import DB, get_device_stats
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import conf
|
|||||||
from const import confFileName, logPath
|
from const import confFileName, logPath
|
||||||
from plugin_helper import Plugin_Objects, handleEmpty
|
from plugin_helper import Plugin_Objects, handleEmpty
|
||||||
from logger import mylog, Logger, append_line_to_file
|
from logger import mylog, Logger, append_line_to_file
|
||||||
from helper import timeNowDB, get_setting_value
|
from helper import get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from models.notification_instance import NotificationInstance
|
from models.notification_instance import NotificationInstance
|
||||||
from database import DB
|
from database import DB
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
|||||||
|
|
||||||
from plugin_helper import Plugin_Objects, handleEmpty # noqa: E402
|
from plugin_helper import Plugin_Objects, handleEmpty # noqa: E402
|
||||||
from logger import mylog, Logger # noqa: E402
|
from logger import mylog, Logger # noqa: E402
|
||||||
from helper import timeNowDB, get_setting_value, hide_string # noqa: E402
|
from helper import get_setting_value, hide_string # noqa: E402
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from models.notification_instance import NotificationInstance # noqa: E402
|
from models.notification_instance import NotificationInstance # noqa: E402
|
||||||
from database import DB # noqa: E402
|
from database import DB # noqa: E402
|
||||||
import conf
|
import conf
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import conf
|
|||||||
from const import confFileName, logPath
|
from const import confFileName, logPath
|
||||||
from plugin_helper import Plugin_Objects, handleEmpty
|
from plugin_helper import Plugin_Objects, handleEmpty
|
||||||
from logger import mylog, Logger, append_line_to_file
|
from logger import mylog, Logger, append_line_to_file
|
||||||
from helper import timeNowDB, get_setting_value, hide_string
|
from helper import get_setting_value, hide_string
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from models.notification_instance import NotificationInstance
|
from models.notification_instance import NotificationInstance
|
||||||
from database import DB
|
from database import DB
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import conf
|
|||||||
from const import confFileName, logPath
|
from const import confFileName, logPath
|
||||||
from plugin_helper import Plugin_Objects
|
from plugin_helper import Plugin_Objects
|
||||||
from logger import mylog, Logger, append_line_to_file
|
from logger import mylog, Logger, append_line_to_file
|
||||||
from helper import timeNowDB, get_setting_value
|
from helper import get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from models.notification_instance import NotificationInstance
|
from models.notification_instance import NotificationInstance
|
||||||
from database import DB
|
from database import DB
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import conf
|
|||||||
from const import logPath, confFileName
|
from const import logPath, confFileName
|
||||||
from plugin_helper import Plugin_Objects, handleEmpty
|
from plugin_helper import Plugin_Objects, handleEmpty
|
||||||
from logger import mylog, Logger, append_line_to_file
|
from logger import mylog, Logger, append_line_to_file
|
||||||
from helper import timeNowDB, get_setting_value, hide_string, write_file
|
from helper import get_setting_value, hide_string, write_file
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from models.notification_instance import NotificationInstance
|
from models.notification_instance import NotificationInstance
|
||||||
from database import DB
|
from database import DB
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
|||||||
|
|
||||||
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
|
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
|
||||||
from logger import mylog, Logger, append_line_to_file
|
from logger import mylog, Logger, append_line_to_file
|
||||||
from helper import timeNowDB, check_IP_format, get_setting_value
|
from helper import check_IP_format, get_setting_value
|
||||||
from const import logPath, applicationPath, fullDbPath
|
from const import logPath, applicationPath, fullDbPath
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
import conf
|
import conf
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
|||||||
|
|
||||||
from plugin_helper import Plugin_Objects
|
from plugin_helper import Plugin_Objects
|
||||||
from logger import mylog, Logger, append_line_to_file
|
from logger import mylog, Logger, append_line_to_file
|
||||||
from helper import timeNowDB, get_setting_value
|
from helper import get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
import conf
|
import conf
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
from const import logPath
|
from const import logPath
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
|||||||
|
|
||||||
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
|
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
|
||||||
from logger import mylog, Logger, append_line_to_file
|
from logger import mylog, Logger, append_line_to_file
|
||||||
from helper import timeNowDB, get_setting_value
|
from helper import get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from const import logPath, applicationPath
|
from const import logPath, applicationPath
|
||||||
import conf
|
import conf
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ INSTALL_PATH = "/app"
|
|||||||
sys.path.append(f"{INSTALL_PATH}/front/plugins")
|
sys.path.append(f"{INSTALL_PATH}/front/plugins")
|
||||||
sys.path.append(f'{INSTALL_PATH}/server')
|
sys.path.append(f'{INSTALL_PATH}/server')
|
||||||
|
|
||||||
from logger import mylog, Logger, timeNowDB
|
from logger import mylog, Logger
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from const import confFileName, default_tz
|
from const import confFileName, default_tz
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
|
|||||||
from plugin_utils import get_plugins_configs, decode_and_rename_files
|
from plugin_utils import get_plugins_configs, decode_and_rename_files
|
||||||
from logger import mylog, Logger
|
from logger import mylog, Logger
|
||||||
from const import pluginsPath, fullDbPath, logPath
|
from const import pluginsPath, fullDbPath, logPath
|
||||||
from helper import timeNowDB, get_setting_value
|
from helper import get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from crypto_utils import encrypt_data
|
from crypto_utils import encrypt_data
|
||||||
from messaging.in_app import write_notification
|
from messaging.in_app import write_notification
|
||||||
import conf
|
import conf
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ from pathlib import Path
|
|||||||
import conf
|
import conf
|
||||||
from const import *
|
from const import *
|
||||||
from logger import mylog
|
from logger import mylog
|
||||||
from helper import filePermissions, timeNowTZ, get_setting_value
|
from helper import filePermissions, get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowTZ
|
||||||
from app_state import updateState
|
from app_state import updateState
|
||||||
from api import update_api
|
from api import update_api
|
||||||
from scan.session_events import process_scan
|
from scan.session_events import process_scan
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import datetime
|
|||||||
import conf
|
import conf
|
||||||
from const import (apiPath, sql_appevents, sql_devices_all, sql_events_pending_alert, sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings, sql_notifications_all, sql_online_history, sql_devices_tiles, sql_devices_filters)
|
from const import (apiPath, sql_appevents, sql_devices_all, sql_events_pending_alert, sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings, sql_notifications_all, sql_online_history, sql_devices_tiles, sql_devices_filters)
|
||||||
from logger import mylog
|
from logger import mylog
|
||||||
from helper import write_file, get_setting_value, timeNowTZ
|
from helper import write_file, get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowTZ
|
||||||
from app_state import updateState
|
from app_state import updateState
|
||||||
from models.user_events_queue_instance import UserEventsQueueInstance
|
from models.user_events_queue_instance import UserEventsQueueInstance
|
||||||
from messaging.in_app import write_notification
|
from messaging.in_app import write_notification
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ INSTALL_PATH="/app"
|
|||||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
from database import get_temp_db_connection
|
from database import get_temp_db_connection
|
||||||
from helper import is_random_mac, format_date, get_setting_value, timeNowDB
|
from helper import is_random_mac, get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowDB, format_date
|
||||||
from db.db_helper import row_to_json, get_date_from_period
|
from db.db_helper import row_to_json, get_date_from_period
|
||||||
|
|
||||||
# --------------------------
|
# --------------------------
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ INSTALL_PATH="/app"
|
|||||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
from database import get_temp_db_connection
|
from database import get_temp_db_connection
|
||||||
from helper import is_random_mac, format_date, get_setting_value
|
from helper import is_random_mac, get_setting_value
|
||||||
from db.db_helper import get_table_json, get_device_condition_by_status
|
from db.db_helper import get_table_json, get_device_condition_by_status
|
||||||
|
from utils.datetime_utils import format_date
|
||||||
|
|
||||||
|
|
||||||
# --------------------------
|
# --------------------------
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ INSTALL_PATH="/app"
|
|||||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
from database import get_temp_db_connection
|
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, ensure_datetime
|
from helper import is_random_mac, get_setting_value, mylog
|
||||||
from db.db_helper import row_to_json, get_date_from_period
|
from db.db_helper import row_to_json, get_date_from_period
|
||||||
|
from utils.datetime_utils import format_date, format_date_iso, format_event_date, ensure_datetime
|
||||||
|
|
||||||
|
|
||||||
# --------------------------
|
# --------------------------
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ INSTALL_PATH="/app"
|
|||||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
from database import get_temp_db_connection
|
from database import get_temp_db_connection
|
||||||
from helper import is_random_mac, format_date, get_setting_value
|
from helper import is_random_mac, get_setting_value
|
||||||
|
from utils.datetime_utils import format_date
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ INSTALL_PATH="/app"
|
|||||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
from database import get_temp_db_connection
|
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, format_date_diff, format_ip_long, parse_datetime
|
from helper import is_random_mac, get_setting_value, mylog, format_ip_long
|
||||||
from db.db_helper import row_to_json, get_date_from_period
|
from db.db_helper import row_to_json, get_date_from_period
|
||||||
|
from utils.datetime_utils import format_date_iso, format_event_date, format_date_diff, parse_datetime, format_date
|
||||||
|
|
||||||
|
|
||||||
# --------------------------
|
# --------------------------
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import os
|
|||||||
import base64
|
import base64
|
||||||
from flask import jsonify, request
|
from flask import jsonify, request
|
||||||
from logger import mylog
|
from logger import mylog
|
||||||
from helper import get_setting_value, timeNowDB
|
from helper import get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from messaging.in_app import write_notification
|
from messaging.in_app import write_notification
|
||||||
|
|
||||||
INSTALL_PATH = "/app"
|
INSTALL_PATH = "/app"
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import json
|
|||||||
import conf
|
import conf
|
||||||
from const import *
|
from const import *
|
||||||
from logger import mylog, logResult
|
from logger import mylog, logResult
|
||||||
from helper import timeNowDB, timeNow, checkNewVersion
|
from helper import checkNewVersion
|
||||||
|
from utils.datetime_utils import timeNowDB, timeNow
|
||||||
|
|
||||||
# Register NetAlertX directories
|
# Register NetAlertX directories
|
||||||
INSTALL_PATH="/app"
|
INSTALL_PATH="/app"
|
||||||
|
|||||||
139
server/helper.py
139
server/helper.py
@@ -7,7 +7,6 @@ import os
|
|||||||
import re
|
import re
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Union
|
|
||||||
import pytz
|
import pytz
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
import json
|
import json
|
||||||
@@ -29,144 +28,6 @@ from logger import mylog, logResult
|
|||||||
# Register NetAlertX directories
|
# Register NetAlertX directories
|
||||||
INSTALL_PATH="/app"
|
INSTALL_PATH="/app"
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
|
||||||
# DateTime
|
|
||||||
#-------------------------------------------------------------------------------
|
|
||||||
def timeNowTZ():
|
|
||||||
if conf.tz:
|
|
||||||
return datetime.datetime.now(conf.tz).replace(microsecond=0)
|
|
||||||
else:
|
|
||||||
return datetime.datetime.now().replace(microsecond=0)
|
|
||||||
|
|
||||||
def timeNow():
|
|
||||||
return datetime.datetime.now().replace(microsecond=0)
|
|
||||||
|
|
||||||
def get_timezone_offset():
|
|
||||||
now = datetime.datetime.now(conf.tz)
|
|
||||||
offset_hours = now.utcoffset().total_seconds() / 3600
|
|
||||||
offset_formatted = "{:+03d}:{:02d}".format(int(offset_hours), int((offset_hours % 1) * 60))
|
|
||||||
return offset_formatted
|
|
||||||
|
|
||||||
def timeNowDB(local=True):
|
|
||||||
"""
|
|
||||||
Return the current time (local or UTC) as ISO 8601 for DB storage.
|
|
||||||
Safe for SQLite, PostgreSQL, etc.
|
|
||||||
|
|
||||||
Example local: '2025-11-04 18:09:11'
|
|
||||||
Example UTC: '2025-11-04 07:09:11'
|
|
||||||
"""
|
|
||||||
if local:
|
|
||||||
try:
|
|
||||||
if isinstance(conf.tz, datetime.tzinfo):
|
|
||||||
tz = conf.tz
|
|
||||||
elif conf.tz:
|
|
||||||
tz = ZoneInfo(conf.tz)
|
|
||||||
else:
|
|
||||||
tz = None
|
|
||||||
except Exception:
|
|
||||||
tz = None
|
|
||||||
return datetime.datetime.now(tz).strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
else:
|
|
||||||
return datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
|
||||||
# Date and time methods
|
|
||||||
#-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------------------------
|
|
||||||
def format_date_iso(date1: str) -> str:
|
|
||||||
"""Return ISO 8601 string for a date or None if empty"""
|
|
||||||
if date1 is None:
|
|
||||||
return None
|
|
||||||
dt = datetime.datetime.fromisoformat(date1) if isinstance(date1, str) else date1
|
|
||||||
return dt.isoformat()
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------------------------
|
|
||||||
def format_event_date(date_str: str, event_type: str) -> str:
|
|
||||||
"""Format event date with fallback rules."""
|
|
||||||
if date_str:
|
|
||||||
return format_date(date_str)
|
|
||||||
elif event_type == "<missing event>":
|
|
||||||
return "<missing event>"
|
|
||||||
else:
|
|
||||||
return "<still connected>"
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------------------------
|
|
||||||
def ensure_datetime(dt: Union[str, datetime.datetime, None]) -> datetime.datetime:
|
|
||||||
if dt is None:
|
|
||||||
return timeNowTZ()
|
|
||||||
if isinstance(dt, str):
|
|
||||||
return datetime.datetime.fromisoformat(dt)
|
|
||||||
return dt
|
|
||||||
|
|
||||||
|
|
||||||
def parse_datetime(dt_str):
|
|
||||||
if not dt_str:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
# Try ISO8601 first
|
|
||||||
return datetime.datetime.fromisoformat(dt_str)
|
|
||||||
except ValueError:
|
|
||||||
# Try RFC1123 / HTTP format
|
|
||||||
try:
|
|
||||||
return datetime.datetime.strptime(dt_str, '%a, %d %b %Y %H:%M:%S GMT')
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def format_date(date_str: str) -> str:
|
|
||||||
try:
|
|
||||||
dt = parse_datetime(date_str)
|
|
||||||
if dt.tzinfo is None:
|
|
||||||
# Set timezone if missing — change to timezone.utc if you prefer UTC
|
|
||||||
now = datetime.datetime.now(conf.tz)
|
|
||||||
dt = dt.replace(tzinfo=now.astimezone().tzinfo)
|
|
||||||
return dt.astimezone().isoformat()
|
|
||||||
except Exception:
|
|
||||||
return "invalid"
|
|
||||||
|
|
||||||
def format_date_diff(date1, date2):
|
|
||||||
"""
|
|
||||||
Return difference between two datetimes as 'Xd HH:MM'.
|
|
||||||
Uses app timezone if datetime is naive.
|
|
||||||
date2 can be None (uses now).
|
|
||||||
"""
|
|
||||||
# Get timezone from settings
|
|
||||||
tz_name = get_setting_value("TIMEZONE") or "UTC"
|
|
||||||
tz = pytz.timezone(tz_name)
|
|
||||||
|
|
||||||
def parse_dt(dt):
|
|
||||||
if dt is None:
|
|
||||||
return datetime.datetime.now(tz)
|
|
||||||
if isinstance(dt, str):
|
|
||||||
try:
|
|
||||||
dt_parsed = email.utils.parsedate_to_datetime(dt)
|
|
||||||
except Exception:
|
|
||||||
# fallback: parse ISO string
|
|
||||||
dt_parsed = datetime.datetime.fromisoformat(dt)
|
|
||||||
# convert naive GMT/UTC to app timezone
|
|
||||||
if dt_parsed.tzinfo is None:
|
|
||||||
dt_parsed = tz.localize(dt_parsed)
|
|
||||||
else:
|
|
||||||
dt_parsed = dt_parsed.astimezone(tz)
|
|
||||||
return dt_parsed
|
|
||||||
return dt if dt.tzinfo else tz.localize(dt)
|
|
||||||
|
|
||||||
dt1 = parse_dt(date1)
|
|
||||||
dt2 = parse_dt(date2)
|
|
||||||
|
|
||||||
delta = dt2 - dt1
|
|
||||||
total_minutes = int(delta.total_seconds() // 60)
|
|
||||||
days, rem_minutes = divmod(total_minutes, 1440) # 1440 mins in a day
|
|
||||||
hours, minutes = divmod(rem_minutes, 60)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"text": f"{days}d {hours:02}:{minutes:02}",
|
|
||||||
"days": days,
|
|
||||||
"hours": hours,
|
|
||||||
"minutes": minutes,
|
|
||||||
"total_minutes": total_minutes
|
|
||||||
}
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
# File system permission handling
|
# File system permission handling
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import re
|
|||||||
# Register NetAlertX libraries
|
# Register NetAlertX libraries
|
||||||
import conf
|
import conf
|
||||||
from const import fullConfPath, applicationPath, fullConfFolder, default_tz
|
from const import fullConfPath, applicationPath, fullConfFolder, default_tz
|
||||||
from helper import getBuildTimeStamp, fixPermissions, collect_lang_strings, updateSubnets, isJsonObject, setting_value_to_python_type, timeNowDB, get_setting_value, generate_random_string
|
from helper import getBuildTimeStamp, fixPermissions, collect_lang_strings, updateSubnets, isJsonObject, setting_value_to_python_type, get_setting_value, generate_random_string
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from app_state import updateState
|
from app_state import updateState
|
||||||
from logger import mylog
|
from logger import mylog
|
||||||
from api import update_api
|
from api import update_api
|
||||||
|
|||||||
@@ -7,40 +7,15 @@ import time
|
|||||||
import logging
|
import logging
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
# Register NetAlertX directories
|
||||||
|
INSTALL_PATH="/app"
|
||||||
|
|
||||||
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
# NetAlertX imports
|
# NetAlertX imports
|
||||||
import conf
|
import conf
|
||||||
from const import *
|
from const import *
|
||||||
|
from utils.datetime_utils import timeNowTZ
|
||||||
#-------------------------------------------------------------------------------
|
|
||||||
# duplication from helper to avoid circle
|
|
||||||
#-------------------------------------------------------------------------------
|
|
||||||
def timeNowTZ():
|
|
||||||
if conf.tz:
|
|
||||||
return datetime.datetime.now(conf.tz).replace(microsecond=0)
|
|
||||||
else:
|
|
||||||
return datetime.datetime.now().replace(microsecond=0)
|
|
||||||
|
|
||||||
def timeNowDB(local=True):
|
|
||||||
"""
|
|
||||||
Return the current time (local or UTC) as ISO 8601 for DB storage.
|
|
||||||
Safe for SQLite, PostgreSQL, etc.
|
|
||||||
|
|
||||||
Example local: '2025-11-04 18:09:11'
|
|
||||||
Example UTC: '2025-11-04 07:09:11'
|
|
||||||
"""
|
|
||||||
if local:
|
|
||||||
try:
|
|
||||||
if isinstance(conf.tz, datetime.tzinfo):
|
|
||||||
tz = conf.tz
|
|
||||||
elif conf.tz:
|
|
||||||
tz = ZoneInfo(conf.tz)
|
|
||||||
else:
|
|
||||||
tz = None
|
|
||||||
except Exception:
|
|
||||||
tz = None
|
|
||||||
return datetime.datetime.now(tz).strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
else:
|
|
||||||
return datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
# Map custom debug levels to Python logging levels
|
# Map custom debug levels to Python logging levels
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ sys.path.extend([f"{INSTALL_PATH}/server"])
|
|||||||
import conf
|
import conf
|
||||||
from const import applicationPath, logPath, apiPath, confFileName, reportTemplatesPath
|
from const import applicationPath, logPath, apiPath, confFileName, reportTemplatesPath
|
||||||
from logger import logResult, mylog
|
from logger import logResult, mylog
|
||||||
from helper import generate_mac_links, removeDuplicateNewLines, timeNowDB, get_file_content, write_file, get_setting_value, get_timezone_offset
|
from helper import generate_mac_links, removeDuplicateNewLines, get_file_content, write_file, get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
|
|
||||||
NOTIFICATION_API_FILE = apiPath + 'user_notifications.json'
|
NOTIFICATION_API_FILE = apiPath + 'user_notifications.json'
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ sys.path.extend([f"{INSTALL_PATH}/server"])
|
|||||||
|
|
||||||
import conf
|
import conf
|
||||||
from const import applicationPath, logPath, apiPath, confFileName
|
from const import applicationPath, logPath, apiPath, confFileName
|
||||||
from helper import get_file_content, write_file, get_timezone_offset, get_setting_value
|
from helper import get_file_content, write_file, get_setting_value
|
||||||
from logger import logResult, mylog
|
from logger import logResult, mylog
|
||||||
from db.sql_safe_builder import create_safe_condition_builder
|
from db.sql_safe_builder import create_safe_condition_builder
|
||||||
|
from utils.datetime_utils import get_timezone_offset
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# REPORTING
|
# REPORTING
|
||||||
|
|||||||
@@ -16,12 +16,10 @@ from const import applicationPath, logPath, apiPath, reportTemplatesPath
|
|||||||
from logger import mylog, Logger
|
from logger import mylog, Logger
|
||||||
from helper import generate_mac_links, \
|
from helper import generate_mac_links, \
|
||||||
removeDuplicateNewLines, \
|
removeDuplicateNewLines, \
|
||||||
timeNowDB, \
|
|
||||||
timeNowTZ, \
|
|
||||||
write_file, \
|
write_file, \
|
||||||
get_setting_value, \
|
get_setting_value
|
||||||
get_timezone_offset
|
|
||||||
from messaging.in_app import write_notification
|
from messaging.in_app import write_notification
|
||||||
|
from utils.datetime_utils import timeNowDB, get_timezone_offset
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ from collections import namedtuple
|
|||||||
import conf
|
import conf
|
||||||
from const import pluginsPath, logPath, applicationPath, reportTemplatesPath
|
from const import pluginsPath, logPath, applicationPath, reportTemplatesPath
|
||||||
from logger import mylog, Logger
|
from logger import mylog, Logger
|
||||||
from helper import timeNowDB, timeNowTZ, get_file_content, write_file, get_setting, get_setting_value
|
from helper import get_file_content, write_file, get_setting, get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowTZ, timeNowDB
|
||||||
from app_state import updateState
|
from app_state import updateState
|
||||||
from api import update_api
|
from api import update_api
|
||||||
from plugin_utils import logEventStatusCounts, get_plugin_string, get_plugin_setting_obj, print_plugin_info, list_to_csv, combine_plugin_objects, resolve_wildcards_arr, handle_empty, custom_plugin_decoder, decode_and_rename_files
|
from plugin_utils import logEventStatusCounts, get_plugin_string, get_plugin_setting_obj, print_plugin_info, list_to_csv, combine_plugin_objects, resolve_wildcards_arr, handle_empty, custom_plugin_decoder, decode_and_rename_files
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from dateutil import parser
|
|||||||
INSTALL_PATH="/app"
|
INSTALL_PATH="/app"
|
||||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
from helper import timeNowDB, timeNowTZ, get_setting_value, check_IP_format
|
from helper import get_setting_value, check_IP_format
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from logger import mylog, Logger
|
from logger import mylog, Logger
|
||||||
from const import vendorsPath, vendorsPathNewest, sql_generateGuid
|
from const import vendorsPath, vendorsPathNewest, sql_generateGuid
|
||||||
from models.device_instance import DeviceInstance
|
from models.device_instance import DeviceInstance
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ sys.path.extend([f"{INSTALL_PATH}/server"])
|
|||||||
|
|
||||||
import conf
|
import conf
|
||||||
from scan.device_handling import create_new_devices, print_scan_stats, save_scanned_devices, exclude_ignored_devices, update_devices_data_from_scan
|
from scan.device_handling import create_new_devices, print_scan_stats, save_scanned_devices, exclude_ignored_devices, update_devices_data_from_scan
|
||||||
from helper import timeNowDB, get_setting_value
|
from helper import get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from db.db_helper import print_table_schema
|
from db.db_helper import print_table_schema
|
||||||
from logger import mylog, Logger
|
from logger import mylog, Logger
|
||||||
from messaging.reporting import skip_repeated_notifications
|
from messaging.reporting import skip_repeated_notifications
|
||||||
|
|||||||
162
server/utils/datetime_utils.py
Normal file
162
server/utils/datetime_utils.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
import pytz
|
||||||
|
from pytz import timezone
|
||||||
|
import datetime
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
# Register NetAlertX directories
|
||||||
|
INSTALL_PATH="/app"
|
||||||
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
|
|
||||||
|
# Register NetAlertX directories
|
||||||
|
INSTALL_PATH="/app"
|
||||||
|
|
||||||
|
import conf
|
||||||
|
from const import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
# DateTime
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
def timeNowTZ():
|
||||||
|
if conf.tz:
|
||||||
|
return datetime.datetime.now(conf.tz).replace(microsecond=0)
|
||||||
|
else:
|
||||||
|
return datetime.datetime.now().replace(microsecond=0)
|
||||||
|
|
||||||
|
def timeNow():
|
||||||
|
return datetime.datetime.now().replace(microsecond=0)
|
||||||
|
|
||||||
|
def get_timezone_offset():
|
||||||
|
now = datetime.datetime.now(conf.tz)
|
||||||
|
offset_hours = now.utcoffset().total_seconds() / 3600
|
||||||
|
offset_formatted = "{:+03d}:{:02d}".format(int(offset_hours), int((offset_hours % 1) * 60))
|
||||||
|
return offset_formatted
|
||||||
|
|
||||||
|
def timeNowDB(local=True):
|
||||||
|
"""
|
||||||
|
Return the current time (local or UTC) as ISO 8601 for DB storage.
|
||||||
|
Safe for SQLite, PostgreSQL, etc.
|
||||||
|
|
||||||
|
Example local: '2025-11-04 18:09:11'
|
||||||
|
Example UTC: '2025-11-04 07:09:11'
|
||||||
|
"""
|
||||||
|
if local:
|
||||||
|
try:
|
||||||
|
if isinstance(conf.tz, datetime.tzinfo):
|
||||||
|
tz = conf.tz
|
||||||
|
elif conf.tz:
|
||||||
|
tz = ZoneInfo(conf.tz)
|
||||||
|
else:
|
||||||
|
tz = None
|
||||||
|
except Exception:
|
||||||
|
tz = None
|
||||||
|
return datetime.datetime.now(tz).strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
else:
|
||||||
|
return datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
# Date and time methods
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------------------------
|
||||||
|
def format_date_iso(date1: str) -> str:
|
||||||
|
"""Return ISO 8601 string for a date or None if empty"""
|
||||||
|
if date1 is None:
|
||||||
|
return None
|
||||||
|
dt = datetime.datetime.fromisoformat(date1) if isinstance(date1, str) else date1
|
||||||
|
return dt.isoformat()
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------------------------
|
||||||
|
def format_event_date(date_str: str, event_type: str) -> str:
|
||||||
|
"""Format event date with fallback rules."""
|
||||||
|
if date_str:
|
||||||
|
return format_date(date_str)
|
||||||
|
elif event_type == "<missing event>":
|
||||||
|
return "<missing event>"
|
||||||
|
else:
|
||||||
|
return "<still connected>"
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------------------------
|
||||||
|
def ensure_datetime(dt: Union[str, datetime.datetime, None]) -> datetime.datetime:
|
||||||
|
if dt is None:
|
||||||
|
return timeNowTZ()
|
||||||
|
if isinstance(dt, str):
|
||||||
|
return datetime.datetime.fromisoformat(dt)
|
||||||
|
return dt
|
||||||
|
|
||||||
|
|
||||||
|
def parse_datetime(dt_str):
|
||||||
|
if not dt_str:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
# Try ISO8601 first
|
||||||
|
return datetime.datetime.fromisoformat(dt_str)
|
||||||
|
except ValueError:
|
||||||
|
# Try RFC1123 / HTTP format
|
||||||
|
try:
|
||||||
|
return datetime.datetime.strptime(dt_str, '%a, %d %b %Y %H:%M:%S GMT')
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def format_date(date_str: str) -> str:
|
||||||
|
try:
|
||||||
|
dt = parse_datetime(date_str)
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
# Set timezone if missing — change to timezone.utc if you prefer UTC
|
||||||
|
now = datetime.datetime.now(conf.tz)
|
||||||
|
dt = dt.replace(tzinfo=now.astimezone().tzinfo)
|
||||||
|
return dt.astimezone().isoformat()
|
||||||
|
except Exception:
|
||||||
|
return "invalid"
|
||||||
|
|
||||||
|
def format_date_diff(date1, date2):
|
||||||
|
"""
|
||||||
|
Return difference between two datetimes as 'Xd HH:MM'.
|
||||||
|
Uses app timezone if datetime is naive.
|
||||||
|
date2 can be None (uses now).
|
||||||
|
"""
|
||||||
|
# Get timezone from settings
|
||||||
|
tz_name = get_setting_value("TIMEZONE") or "UTC"
|
||||||
|
tz = pytz.timezone(tz_name)
|
||||||
|
|
||||||
|
def parse_dt(dt):
|
||||||
|
if dt is None:
|
||||||
|
return datetime.datetime.now(tz)
|
||||||
|
if isinstance(dt, str):
|
||||||
|
try:
|
||||||
|
dt_parsed = email.utils.parsedate_to_datetime(dt)
|
||||||
|
except Exception:
|
||||||
|
# fallback: parse ISO string
|
||||||
|
dt_parsed = datetime.datetime.fromisoformat(dt)
|
||||||
|
# convert naive GMT/UTC to app timezone
|
||||||
|
if dt_parsed.tzinfo is None:
|
||||||
|
dt_parsed = tz.localize(dt_parsed)
|
||||||
|
else:
|
||||||
|
dt_parsed = dt_parsed.astimezone(tz)
|
||||||
|
return dt_parsed
|
||||||
|
return dt if dt.tzinfo else tz.localize(dt)
|
||||||
|
|
||||||
|
dt1 = parse_dt(date1)
|
||||||
|
dt2 = parse_dt(date2)
|
||||||
|
|
||||||
|
delta = dt2 - dt1
|
||||||
|
total_minutes = int(delta.total_seconds() // 60)
|
||||||
|
days, rem_minutes = divmod(total_minutes, 1440) # 1440 mins in a day
|
||||||
|
hours, minutes = divmod(rem_minutes, 60)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"text": f"{days}d {hours:02}:{minutes:02}",
|
||||||
|
"days": days,
|
||||||
|
"hours": hours,
|
||||||
|
"minutes": minutes,
|
||||||
|
"total_minutes": total_minutes
|
||||||
|
}
|
||||||
@@ -6,7 +6,8 @@ import pytest
|
|||||||
INSTALL_PATH = "/app"
|
INSTALL_PATH = "/app"
|
||||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
from helper import get_setting_value, timeNowDB
|
from helper import get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowDB
|
||||||
from api_server.api_server_start import app
|
from api_server.api_server_start import app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from datetime import datetime, timedelta
|
|||||||
INSTALL_PATH = "/app"
|
INSTALL_PATH = "/app"
|
||||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
from helper import timeNowTZ, get_setting_value
|
from helper import get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowTZ
|
||||||
from api_server.api_server_start import app
|
from api_server.api_server_start import app
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from datetime import datetime, timedelta
|
|||||||
INSTALL_PATH = "/app"
|
INSTALL_PATH = "/app"
|
||||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
from helper import timeNowDB, timeNowTZ, get_setting_value
|
from helper import get_setting_value
|
||||||
|
from utils.datetime_utils import timeNowTZ, timeNowDB
|
||||||
from api_server.api_server_start import app
|
from api_server.api_server_start import app
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
|
|||||||
Reference in New Issue
Block a user