/data and /tmp standarization

This commit is contained in:
Adam Outler
2025-11-04 22:26:35 +00:00
parent 90a07c61eb
commit 5b871865db
250 changed files with 7462 additions and 4940 deletions

View File

@@ -1,14 +1,9 @@
import sys
# Register NetAlertX directories
INSTALL_PATH="/app"
sys.path.extend([f"{INSTALL_PATH}/server"])
from logger import mylog
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
# Device object handling (WIP)
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
class DeviceInstance:
def __init__(self, db):
self.db = db
@@ -19,7 +14,7 @@ class DeviceInstance:
SELECT * FROM Devices
""")
return self.db.sql.fetchall()
# Get all with unknown names
def getUnknown(self):
self.db.sql.execute("""
@@ -29,7 +24,6 @@ class DeviceInstance:
# Get specific column value based on devMac
def getValueWithMac(self, column_name, devMac):
query = f"SELECT {column_name} FROM Devices WHERE devMac = ?"
self.db.sql.execute(query, (devMac,))
result = self.db.sql.fetchone()
@@ -41,7 +35,7 @@ class DeviceInstance:
SELECT * FROM Devices WHERE devAlertDown = 1 and devPresentLastScan = 0
""")
return self.db.sql.fetchall()
# Get all down
def getOffline(self):
self.db.sql.execute("""
@@ -57,7 +51,9 @@ class DeviceInstance:
# Check if a device exists by devGUID
def exists(self, devGUID):
self.db.sql.execute("SELECT COUNT(*) AS count FROM Devices WHERE devGUID = ?", (devGUID,))
self.db.sql.execute(
"SELECT COUNT(*) AS count FROM Devices WHERE devGUID = ?", (devGUID,)
)
result = self.db.sql.fetchone()
return result["count"] > 0
@@ -65,20 +61,23 @@ class DeviceInstance:
def updateField(self, devGUID, field, value):
if not self.exists(devGUID):
m = f"[Device] In 'updateField': GUID {devGUID} not found."
mylog('none', m)
mylog("none", m)
raise ValueError(m)
self.db.sql.execute(f"""
self.db.sql.execute(
f"""
UPDATE Devices SET {field} = ? WHERE devGUID = ?
""", (value, devGUID))
""",
(value, devGUID),
)
self.db.commitDB()
# Delete a device by devGUID
def delete(self, devGUID):
if not self.exists(devGUID):
m = f"[Device] In 'delete': GUID {devGUID} not found."
mylog('none', m)
mylog("none", m)
raise ValueError(m)
self.db.sql.execute("DELETE FROM Devices WHERE devGUID = ?", (devGUID,))
self.db.commitDB()
self.db.commitDB()

View File

@@ -1,25 +1,22 @@
import json
import sys
import uuid
import socket
import subprocess
from yattag import indent
from json2table import convert
# Register NetAlertX directories
INSTALL_PATH = "/app"
sys.path.extend([f"{INSTALL_PATH}/server"])
# Register NetAlertX modules
import conf
from const import applicationPath, logPath, apiPath, reportTemplatesPath
from logger import mylog, Logger
from helper import generate_mac_links, \
removeDuplicateNewLines, \
timeNowTZ, \
write_file, \
get_setting_value, \
get_timezone_offset
from helper import (
generate_mac_links,
removeDuplicateNewLines,
timeNowTZ,
write_file,
get_setting_value,
get_timezone_offset,
)
from messaging.in_app import write_notification
@@ -47,38 +44,42 @@ class NotificationInstance:
""")
# Make sure log level is initialized correctly
Logger(get_setting_value('LOG_LEVEL'))
Logger(get_setting_value("LOG_LEVEL"))
self.save()
# Method to override processing of notifications
def on_before_create(self, JSON, Extra):
return JSON, Extra
# Create a new DB entry if new notifications available, otherwise skip
def create(self, JSON, Extra=""):
JSON, Extra = self.on_before_create(JSON, Extra)
# Write output data for debug
write_file(logPath + '/report_output.json', json.dumps(JSON))
write_file(logPath + "/report_output.json", json.dumps(JSON))
# Check if nothing to report, end
if JSON["new_devices"] == [] and JSON["down_devices"] == [] and JSON["events"] == [] and JSON["plugins"] == [] and JSON["down_reconnected"] == []:
if (
JSON["new_devices"] == []
and JSON["down_devices"] == []
and JSON["events"] == []
and JSON["plugins"] == []
and JSON["down_reconnected"] == []
):
self.HasNotifications = False
else:
self.HasNotifications = True
self.GUID = str(uuid.uuid4())
self.DateTimeCreated = timeNowTZ()
self.DateTimePushed = ""
self.Status = "new"
self.JSON = JSON
self.Text = ""
self.HTML = ""
self.PublishedVia = ""
self.Extra = Extra
self.GUID = str(uuid.uuid4())
self.DateTimeCreated = timeNowTZ()
self.DateTimePushed = ""
self.Status = "new"
self.JSON = JSON
self.Text = ""
self.HTML = ""
self.PublishedVia = ""
self.Extra = Extra
if self.HasNotifications:
# if not notiStruc.json['data'] and not notiStruc.text and not notiStruc.html:
@@ -88,136 +89,130 @@ class NotificationInstance:
Text = ""
HTML = ""
template_file_path = reportTemplatesPath + 'report_template.html'
template_file_path = reportTemplatesPath + "report_template.html"
# Open text Template
mylog('verbose', ['[Notification] Open text Template'])
template_file = open(reportTemplatesPath + 'report_template.txt', 'r')
mylog("verbose", ["[Notification] Open text Template"])
template_file = open(reportTemplatesPath + "report_template.txt", "r")
mail_text = template_file.read()
template_file.close()
# Open html Template
mylog('verbose', ['[Notification] Open html Template'])
mylog("verbose", ["[Notification] Open html Template"])
template_file = open(template_file_path, 'r')
template_file = open(template_file_path, "r")
mail_html = template_file.read()
template_file.close()
# prepare new version text
newVersionText = ''
newVersionText = ""
if conf.newVersionAvailable:
newVersionText = '🚀A new version is available.'
newVersionText = "🚀A new version is available."
mail_text = mail_text.replace('<NEW_VERSION>', newVersionText)
mail_html = mail_html.replace('<NEW_VERSION>', newVersionText)
mail_text = mail_text.replace("<NEW_VERSION>", newVersionText)
mail_html = mail_html.replace("<NEW_VERSION>", newVersionText)
# Report "REPORT_DATE" in Header & footer
timeFormated = timeNowTZ().strftime('%Y-%m-%d %H:%M')
mail_text = mail_text.replace('<REPORT_DATE>', timeFormated)
mail_html = mail_html.replace('<REPORT_DATE>', timeFormated)
timeFormated = timeNowTZ().strftime("%Y-%m-%d %H:%M")
mail_text = mail_text.replace("<REPORT_DATE>", timeFormated)
mail_html = mail_html.replace("<REPORT_DATE>", timeFormated)
# Report "SERVER_NAME" in Header & footer
mail_text = mail_text.replace('<SERVER_NAME>', socket.gethostname())
mail_html = mail_html.replace('<SERVER_NAME>', socket.gethostname())
mail_text = mail_text.replace("<SERVER_NAME>", socket.gethostname())
mail_html = mail_html.replace("<SERVER_NAME>", socket.gethostname())
# Report "VERSION" in Header & footer
try:
VERSIONFILE = subprocess.check_output(
['php', applicationPath + '/front/php/templates/version.php'],
timeout=5
).decode('utf-8')
["php", applicationPath + "/front/php/templates/version.php"],
timeout=5,
).decode("utf-8")
except Exception as e:
mylog('debug', [f'[Notification] Unable to read version.php: {e}'])
VERSIONFILE = 'unknown'
mylog("debug", [f"[Notification] Unable to read version.php: {e}"])
VERSIONFILE = "unknown"
mail_text = mail_text.replace('<BUILD_VERSION>', VERSIONFILE)
mail_html = mail_html.replace('<BUILD_VERSION>', VERSIONFILE)
mail_text = mail_text.replace("<BUILD_VERSION>", VERSIONFILE)
mail_html = mail_html.replace("<BUILD_VERSION>", VERSIONFILE)
# Report "BUILD" in Header & footer
try:
BUILDFILE = subprocess.check_output(
['php', applicationPath + '/front/php/templates/build.php'],
timeout=5
).decode('utf-8')
["php", applicationPath + "/front/php/templates/build.php"],
timeout=5,
).decode("utf-8")
except Exception as e:
mylog('debug', [f'[Notification] Unable to read build.php: {e}'])
BUILDFILE = 'unknown'
mylog("debug", [f"[Notification] Unable to read build.php: {e}"])
BUILDFILE = "unknown"
mail_text = mail_text.replace('<BUILD_DATE>', BUILDFILE)
mail_html = mail_html.replace('<BUILD_DATE>', BUILDFILE)
mail_text = mail_text.replace("<BUILD_DATE>", BUILDFILE)
mail_html = mail_html.replace("<BUILD_DATE>", BUILDFILE)
# Start generating the TEXT & HTML notification messages
# new_devices
# ---
html, text = construct_notifications(self.JSON, "new_devices")
mail_text = mail_text.replace('<NEW_DEVICES_TABLE>', text + '\n')
mail_html = mail_html.replace('<NEW_DEVICES_TABLE>', html)
mylog('verbose', ['[Notification] New Devices sections done.'])
mail_text = mail_text.replace("<NEW_DEVICES_TABLE>", text + "\n")
mail_html = mail_html.replace("<NEW_DEVICES_TABLE>", html)
mylog("verbose", ["[Notification] New Devices sections done."])
# down_devices
# ---
html, text = construct_notifications(self.JSON, "down_devices")
mail_text = mail_text.replace('<DOWN_DEVICES_TABLE>', text + '\n')
mail_html = mail_html.replace('<DOWN_DEVICES_TABLE>', html)
mylog('verbose', ['[Notification] Down Devices sections done.'])
mail_text = mail_text.replace("<DOWN_DEVICES_TABLE>", text + "\n")
mail_html = mail_html.replace("<DOWN_DEVICES_TABLE>", html)
mylog("verbose", ["[Notification] Down Devices sections done."])
# down_reconnected
# ---
html, text = construct_notifications(self.JSON, "down_reconnected")
mail_text = mail_text.replace('<DOWN_RECONNECTED_TABLE>', text + '\n')
mail_html = mail_html.replace('<DOWN_RECONNECTED_TABLE>', html)
mylog('verbose', ['[Notification] Reconnected Down Devices sections done.'])
mail_text = mail_text.replace("<DOWN_RECONNECTED_TABLE>", text + "\n")
mail_html = mail_html.replace("<DOWN_RECONNECTED_TABLE>", html)
mylog("verbose", ["[Notification] Reconnected Down Devices sections done."])
# events
# ---
html, text = construct_notifications(self.JSON, "events")
mail_text = mail_text.replace('<EVENTS_TABLE>', text + '\n')
mail_html = mail_html.replace('<EVENTS_TABLE>', html)
mylog('verbose', ['[Notification] Events sections done.'])
mail_text = mail_text.replace("<EVENTS_TABLE>", text + "\n")
mail_html = mail_html.replace("<EVENTS_TABLE>", html)
mylog("verbose", ["[Notification] Events sections done."])
# plugins
# ---
html, text = construct_notifications(self.JSON, "plugins")
mail_text = mail_text.replace('<PLUGINS_TABLE>', text + '\n')
mail_html = mail_html.replace('<PLUGINS_TABLE>', html)
mail_text = mail_text.replace("<PLUGINS_TABLE>", text + "\n")
mail_html = mail_html.replace("<PLUGINS_TABLE>", html)
mylog('verbose', ['[Notification] Plugins sections done.'])
mylog("verbose", ["[Notification] Plugins sections done."])
final_text = removeDuplicateNewLines(mail_text)
# Create clickable MAC links
mail_html = generate_mac_links(mail_html, conf.REPORT_DASHBOARD_URL + '/deviceDetails.php?mac=')
mail_html = generate_mac_links(
mail_html, conf.REPORT_DASHBOARD_URL + "/deviceDetails.php?mac="
)
final_html = indent(
mail_html,
indentation=' ',
newline='\r\n',
indent_text=True
mail_html, indentation=" ", newline="\r\n", indent_text=True
)
send_api(self.JSON, final_text, final_html)
# Write output data for debug
write_file(logPath + '/report_output.txt', final_text)
write_file(logPath + '/report_output.html', final_html)
write_file(logPath + "/report_output.txt", final_text)
write_file(logPath + "/report_output.html", final_html)
mylog('minimal', ['[Notification] Udating API files'])
mylog("minimal", ["[Notification] Udating API files"])
self.Text = final_text
self.HTML = final_html
self.Text = final_text
self.HTML = final_html
# Notify frontend
write_notification(f'Report:{self.GUID}', "alert", self.DateTimeCreated)
write_notification(f"Report:{self.GUID}", "alert", self.DateTimeCreated)
self.upsert()
@@ -236,20 +231,36 @@ class NotificationInstance:
# create or update a notification
def upsert(self):
self.db.sql.execute("""
self.db.sql.execute(
"""
INSERT OR REPLACE INTO Notifications (GUID, DateTimeCreated, DateTimePushed, Status, JSON, Text, HTML, PublishedVia, Extra)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (self.GUID, self.DateTimeCreated, self.DateTimePushed, self.Status, json.dumps(self.JSON), self.Text, self.HTML, self.PublishedVia, self.Extra))
""",
(
self.GUID,
self.DateTimeCreated,
self.DateTimePushed,
self.Status,
json.dumps(self.JSON),
self.Text,
self.HTML,
self.PublishedVia,
self.Extra,
),
)
self.save()
# Remove notification object by GUID
def remove(self, GUID):
# Execute an SQL query to delete the notification with the specified GUID
self.db.sql.execute("""
self.db.sql.execute(
"""
DELETE FROM Notifications
WHERE GUID = ?
""", (GUID,))
""",
(GUID,),
)
self.save()
# Get all with the "new" status
@@ -262,7 +273,6 @@ class NotificationInstance:
# Set all to "processed" status
def setAllProcessed(self):
# Execute an SQL query to update the status of all notifications
self.db.sql.execute("""
UPDATE Notifications
@@ -274,15 +284,17 @@ class NotificationInstance:
# Clear the Pending Email flag from all events and devices
def clearPendingEmailFlag(self):
# Clean Pending Alert Events
self.db.sql.execute("""
self.db.sql.execute(
"""
UPDATE Devices SET devLastNotification = ?
WHERE devMac IN (
SELECT eve_MAC FROM Events
WHERE eve_PendingAlertEmail = 1
)
""", (timeNowTZ(),))
""",
(timeNowTZ(),),
)
self.db.sql.execute("""
UPDATE Events SET eve_PendingAlertEmail = 0
@@ -290,23 +302,26 @@ class NotificationInstance:
AND eve_EventType !='Device Down' """)
# Clear down events flag after the reporting window passed
minutes = int(get_setting_value('NTFPRCS_alert_down_time') or 0)
minutes = int(get_setting_value("NTFPRCS_alert_down_time") or 0)
tz_offset = get_timezone_offset()
self.db.sql.execute("""
self.db.sql.execute(
"""
UPDATE Events
SET eve_PendingAlertEmail = 0
WHERE eve_PendingAlertEmail = 1
AND eve_EventType = 'Device Down'
AND eve_DateTime < datetime('now', ?, ?)
""", (f"-{minutes} minutes", tz_offset))
""",
(f"-{minutes} minutes", tz_offset),
)
mylog('minimal', ['[Notification] Notifications changes: ',
self.db.sql.rowcount])
mylog(
"minimal", ["[Notification] Notifications changes: ", self.db.sql.rowcount]
)
# clear plugin events
self.clearPluginEvents()
def clearPluginEvents(self):
# clear plugin events table
self.db.sql.execute("DELETE FROM Plugins_Events")
@@ -321,20 +336,20 @@ class NotificationInstance:
# Reporting
# -----------------------------------------------------------------------------
# ------------------------------------------------------------------------------
def construct_notifications(JSON, section):
jsn = JSON[section]
# Return if empty
if jsn == []:
return '', ''
return "", ""
tableTitle = JSON[section + "_meta"]["title"]
headers = JSON[section + "_meta"]["columnNames"]
tableTitle = JSON[section + "_meta"]["title"]
headers = JSON[section + "_meta"]["columnNames"]
html = ''
text = ''
html = ""
text = ""
table_attributes = {
"style": "border-collapse: collapse; font-size: 12px; color:#70707",
@@ -342,28 +357,32 @@ def construct_notifications(JSON, section):
"cellspacing": 0,
"cellpadding": "3px",
"bordercolor": "#C0C0C0",
"border": "1"
}
headerProps = "width='120px' style='color:white; font-size: 16px;' bgcolor='#64a0d6' "
"border": "1",
}
headerProps = (
"width='120px' style='color:white; font-size: 16px;' bgcolor='#64a0d6' "
)
thProps = "width='120px' style='color:#F0F0F0' bgcolor='#64a0d6' "
build_direction = "TOP_TO_BOTTOM"
text_line = '{}\t{}\n'
text_line = "{}\t{}\n"
if len(jsn) > 0:
text = tableTitle + "\n---------\n"
# Convert a JSON into an HTML table
html = convert({"data": jsn}, build_direction=build_direction, table_attributes=table_attributes)
html = convert(
{"data": jsn},
build_direction=build_direction,
table_attributes=table_attributes,
)
# Cleanup the generated HTML table notification
html = format_table(html,
"data",
headerProps,
tableTitle).replace('<ul>',
'<ul style="list-style:none;padding-left:0">'
).replace("<td>null</td>",
"<td></td>")
html = (
format_table(html, "data", headerProps, tableTitle)
.replace("<ul>", '<ul style="list-style:none;padding-left:0">')
.replace("<td>null</td>", "<td></td>")
)
# prepare text-only message
for device in jsn:
@@ -371,8 +390,8 @@ def construct_notifications(JSON, section):
padding = ""
if len(header) < 4:
padding = "\t"
text += text_line.format(header + ': ' + padding, device[header])
text += '\n'
text += text_line.format(header + ": " + padding, device[header])
text += "\n"
# Format HTML table headers
for header in headers:
@@ -383,18 +402,19 @@ def construct_notifications(JSON, section):
# -----------------------------------------------------------------------------
def send_api(json_final, mail_text, mail_html):
mylog('verbose', ['[Send API] Updating notification_* files in ', apiPath])
mylog("verbose", ["[Send API] Updating notification_* files in ", apiPath])
write_file(apiPath + 'notification_text.txt', mail_text)
write_file(apiPath + 'notification_text.html', mail_html)
write_file(apiPath + 'notification_json_final.json', json.dumps(json_final))
write_file(apiPath + "notification_text.txt", mail_text)
write_file(apiPath + "notification_text.html", mail_html)
write_file(apiPath + "notification_json_final.json", json.dumps(json_final))
# -----------------------------------------------------------------------------
# Replacing table headers
def format_table(html, thValue, props, newThValue=''):
if newThValue == '':
def format_table(html, thValue, props, newThValue=""):
if newThValue == "":
newThValue = thValue
return html.replace("<th>"+thValue+"</th>", "<th "+props+" >"+newThValue+"</th>")
return html.replace(
"<th>" + thValue + "</th>", "<th " + props + " >" + newThValue + "</th>"
)

View File

@@ -1,14 +1,9 @@
import sys
# Register NetAlertX directories
INSTALL_PATH="/app"
sys.path.extend([f"{INSTALL_PATH}/server"])
from logger import mylog
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
# Plugin object handling (WIP)
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
class PluginObjectInstance:
def __init__(self, db):
self.db = db
@@ -19,16 +14,21 @@ class PluginObjectInstance:
SELECT * FROM Plugins_Objects
""")
return self.db.sql.fetchall()
# Get plugin object by ObjectGUID
def getByGUID(self, ObjectGUID):
self.db.sql.execute("SELECT * FROM Plugins_Objects WHERE ObjectGUID = ?", (ObjectGUID,))
self.db.sql.execute(
"SELECT * FROM Plugins_Objects WHERE ObjectGUID = ?", (ObjectGUID,)
)
result = self.db.sql.fetchone()
return dict(result) if result else None
# Check if a plugin object exists by ObjectGUID
def exists(self, ObjectGUID):
self.db.sql.execute("SELECT COUNT(*) AS count FROM Plugins_Objects WHERE ObjectGUID = ?", (ObjectGUID,))
self.db.sql.execute(
"SELECT COUNT(*) AS count FROM Plugins_Objects WHERE ObjectGUID = ?",
(ObjectGUID,),
)
result = self.db.sql.fetchone()
return result["count"] > 0
@@ -36,30 +36,35 @@ class PluginObjectInstance:
def getByPlugin(self, plugin):
self.db.sql.execute("SELECT * FROM Plugins_Objects WHERE Plugin = ?", (plugin,))
return self.db.sql.fetchall()
# Get objects by status
def getByStatus(self, status):
self.db.sql.execute("SELECT * FROM Plugins_Objects WHERE Status = ?", (status,))
return self.db.sql.fetchall()
# Update a specific field for a plugin object
def updateField(self, ObjectGUID, field, value):
if not self.exists(ObjectGUID):
m = f"[PluginObject] In 'updateField': GUID {ObjectGUID} not found."
mylog('none', m)
mylog("none", m)
raise ValueError(m)
self.db.sql.execute(f"""
self.db.sql.execute(
f"""
UPDATE Plugins_Objects SET {field} = ? WHERE ObjectGUID = ?
""", (value, ObjectGUID))
""",
(value, ObjectGUID),
)
self.db.commitDB()
# Delete a plugin object by ObjectGUID
def delete(self, ObjectGUID):
if not self.exists(ObjectGUID):
m = f"[PluginObject] In 'delete': GUID {ObjectGUID} not found."
mylog('none', m)
mylog("none", m)
raise ValueError(m)
self.db.sql.execute("DELETE FROM Plugins_Objects WHERE ObjectGUID = ?", (ObjectGUID,))
self.db.sql.execute(
"DELETE FROM Plugins_Objects WHERE ObjectGUID = ?", (ObjectGUID,)
)
self.db.commitDB()

View File

@@ -1,14 +1,9 @@
import os
import sys
# Register NetAlertX directories
INSTALL_PATH="/app"
sys.path.extend([f"{INSTALL_PATH}/server"])
# Register NetAlertX modules
from const import pluginsPath, logPath, applicationPath, reportTemplatesPath
from const import logPath
from logger import mylog
class UserEventsQueueInstance:
"""
Handles the execution queue log file, allowing reading, writing,
@@ -19,12 +14,11 @@ class UserEventsQueueInstance:
self.log_path = logPath
self.log_file = os.path.join(self.log_path, "execution_queue.log")
def has_update_devices(self):
lines = self.read_log()
for line in lines:
if 'update_api|devices' in line:
if "update_api|devices" in line:
return True
return False
@@ -35,7 +29,10 @@ class UserEventsQueueInstance:
Returns an empty list if the file doesn't exist.
"""
if not os.path.exists(self.log_file):
mylog('none', ['[UserEventsQueueInstance] Log file not found: ', self.log_file])
mylog(
"none",
["[UserEventsQueueInstance] Log file not found: ", self.log_file],
)
return [] # No log file, return empty list
with open(self.log_file, "r") as file:
return file.readlines()
@@ -64,7 +61,9 @@ class UserEventsQueueInstance:
# Process the log file line by line
with open(self.log_file, "r") as file:
for line in file:
columns = line.strip().split('|')[2:4] # Extract event and param columns
columns = line.strip().split("|")[
2:4
] # Extract event and param columns
if len(columns) == 2:
event_name, _ = columns
if event_name == event and not removed:
@@ -76,10 +75,6 @@ class UserEventsQueueInstance:
# Write back the remaining lines
self.write_log(updated_lines)
mylog('minimal', ['[UserEventsQueueInstance] Processed event: ', event])
mylog("minimal", ["[UserEventsQueueInstance] Processed event: ", event])
return removed