BE+FE: refactor timezone UTC #1506

Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
jokob-sk
2026-02-11 16:15:49 +11:00
parent 70c3530a5c
commit b57d36607a
4 changed files with 116 additions and 79 deletions

View File

@@ -116,7 +116,7 @@ function initializeEventsDatatable (eventsRows) {
{ {
targets: [0], targets: [0],
'createdCell': function (td, cellData, rowData, row, col) { 'createdCell': function (td, cellData, rowData, row, col) {
$(td).html(translateHTMLcodes(localizeTimestamp(cellData))); $(td).html(translateHTMLcodes((cellData)));
} }
} }
], ],

View File

@@ -447,6 +447,7 @@ function localizeTimestamp(input) {
return formatSafe(input, tz); return formatSafe(input, tz);
function formatSafe(str, tz) { function formatSafe(str, tz) {
// CHECK: Does the input string have timezone information? // CHECK: Does the input string have timezone information?
// - Ends with Z: "2026-02-11T11:37:02Z" // - Ends with Z: "2026-02-11T11:37:02Z"
// - Has GMT±offset: "Wed Feb 11 2026 12:34:12 GMT+1100 (...)" // - Has GMT±offset: "Wed Feb 11 2026 12:34:12 GMT+1100 (...)"

View File

@@ -1,10 +1,6 @@
import sys import conf
import os from zoneinfo import ZoneInfo
import datetime as dt
# Register NetAlertX directories
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
sys.path.extend([f"{INSTALL_PATH}/server"])
from logger import mylog # noqa: E402 [flake8 lint suppression] from logger import mylog # noqa: E402 [flake8 lint suppression]
from messaging.in_app import write_notification # noqa: E402 [flake8 lint suppression] from messaging.in_app import write_notification # noqa: E402 [flake8 lint suppression]
@@ -574,64 +570,92 @@ def is_timestamps_in_utc(sql) -> bool:
def migrate_timestamps_to_utc(sql) -> bool: def migrate_timestamps_to_utc(sql) -> bool:
""" """
Migrate all timestamp columns in the database from local time to UTC. Safely migrate timestamp columns from local time to UTC.
This function determines if migration is needed based on the VERSION setting: Migration rules (fail-safe):
- Fresh installs (no VERSION): Skip migration - timestamps already UTC from timeNowUTC() - Default behaviour: RUN migration unless proven safe to skip
- Version >= 26.2.6: Skip migration - already using UTC timestamps - Version > 26.2.6 → timestamps already UTC → skip
- Version < 26.2.6: Run migration - convert local timestamps to UTC - Missing / unknown / unparsable version → migrate
- Migration flag present → skip
Affected tables: - Detection says already UTC → skip
- Devices: devFirstConnection, devLastConnection, devLastNotification
- Events: eve_DateTime
- Sessions: ses_DateTimeConnection, ses_DateTimeDisconnection
- Notifications: DateTimeCreated, DateTimePushed
- Online_History: Scan_Date
- Plugins_Objects: DateTimeCreated, DateTimeChanged
- Plugins_Events: DateTimeCreated, DateTimeChanged
- Plugins_History: DateTimeCreated, DateTimeChanged
- AppEvents: DateTimeCreated
Returns: Returns:
bool: True if migration completed or wasn't needed, False on error bool: True if migration completed or not needed, False on error
""" """
try:
import conf
from zoneinfo import ZoneInfo
import datetime as dt
# Check VERSION from Settings table (from previous app run) try:
sql.execute("SELECT setValue FROM Settings WHERE setKey = 'VERSION'") # -------------------------------------------------
# Check migration flag (idempotency protection)
# -------------------------------------------------
try:
sql.execute("SELECT setValue FROM Settings WHERE setKey='DB_TIMESTAMPS_UTC_MIGRATED'")
result = sql.fetchone()
if result and str(result[0]) == "1":
mylog("verbose", "[db_upgrade] UTC timestamp migration already completed - skipping")
return True
except Exception:
pass
# -------------------------------------------------
# Read previous version
# -------------------------------------------------
sql.execute("SELECT setValue FROM Settings WHERE setKey='VERSION'")
result = sql.fetchone() result = sql.fetchone()
prev_version = result[0] if result else "" prev_version = result[0] if result else ""
# Fresh install: VERSION is empty → timestamps already UTC from timeNowUTC() mylog("verbose", f"[db_upgrade] Version '{prev_version}' detected.")
if not prev_version or prev_version == "" or prev_version == "unknown":
mylog("verbose", "[db_upgrade] Fresh install detected - timestamps already in UTC format") # Default behaviour: migrate unless proven safe
should_migrate = True
# -------------------------------------------------
# Version-based safety check
# -------------------------------------------------
if prev_version and str(prev_version).lower() != "unknown":
try:
version_parts = prev_version.lstrip('v').split('.')
major = int(version_parts[0]) if len(version_parts) > 0 else 0
minor = int(version_parts[1]) if len(version_parts) > 1 else 0
patch = int(version_parts[2]) if len(version_parts) > 2 else 0
# UTC timestamps introduced AFTER v26.2.6
if (major, minor, patch) > (26, 2, 6):
should_migrate = False
mylog(
"verbose",
f"[db_upgrade] Version {prev_version} confirmed UTC timestamps - skipping migration",
)
except (ValueError, IndexError) as e:
mylog(
"warn",
f"[db_upgrade] Could not parse version '{prev_version}': {e} - running migration as safety measure",
)
else:
mylog(
"warn",
"[db_upgrade] VERSION missing/unknown - running migration as safety measure",
)
# -------------------------------------------------
# Detection fallback
# -------------------------------------------------
if should_migrate:
try:
if is_timestamps_in_utc(sql):
mylog(
"verbose",
"[db_upgrade] Timestamps appear already UTC - skipping migration",
)
return True
except Exception as e:
mylog(
"warn",
f"[db_upgrade] UTC detection failed ({e}) - continuing with migration",
)
else:
return True return True
# Parse version - format: "26.2.6" or "v26.2.6"
try:
version_parts = prev_version.strip('v').split('.')
major = int(version_parts[0]) if len(version_parts) > 0 else 0
minor = int(version_parts[1]) if len(version_parts) > 1 else 0
patch = int(version_parts[2]) if len(version_parts) > 2 else 0
# UTC timestamps introduced in v26.2.6
# If upgrading from 26.2.6 or later, timestamps are already UTC
if (major > 26) or (major == 26 and minor > 2) or (major == 26 and minor == 2 and patch >= 6):
mylog("verbose", f"[db_upgrade] Version {prev_version} already uses UTC timestamps - skipping migration")
return True
mylog("verbose", f"[db_upgrade] Upgrading from {prev_version} (< v26.2.6) - migrating timestamps to UTC")
except (ValueError, IndexError) as e:
mylog("warn", f"[db_upgrade] Could not parse version '{prev_version}': {e} - checking timestamps")
# Fallback: use detection logic
if is_timestamps_in_utc(sql):
mylog("verbose", "[db_upgrade] Timestamps appear to be in UTC - skipping migration")
return True
# Get timezone offset # Get timezone offset
try: try:
if isinstance(conf.tz, dt.tzinfo): if isinstance(conf.tz, dt.tzinfo):

View File

@@ -49,6 +49,18 @@ class PluginObjectInstance:
"SELECT * FROM Plugins_Objects WHERE Plugin = ?", (plugin,) "SELECT * FROM Plugins_Objects WHERE Plugin = ?", (plugin,)
) )
def getLastNCreatedPerPLugin(self, plugin, entries=1):
return self._fetchall(
"""
SELECT *
FROM Plugins_Objects
WHERE Plugin = ?
ORDER BY DateTimeCreated DESC
LIMIT ?
""",
(plugin, entries),
)
def getByField(self, plugPrefix, matchedColumn, matchedKey, returnFields=None): def getByField(self, plugPrefix, matchedColumn, matchedKey, returnFields=None):
rows = self._fetchall( rows = self._fetchall(
f"SELECT * FROM Plugins_Objects WHERE Plugin = ? AND {matchedColumn} = ?", f"SELECT * FROM Plugins_Objects WHERE Plugin = ? AND {matchedColumn} = ?",