mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-09-12 22:25:49 -04:00
Merge branch 'next_release' of github.com:netalertx/NetAlertX into next_release
This commit is contained in:
commit
9adb39e0b3
4 files changed
+111
-19
No files matched your search
@@ -24,6 +24,7 @@ description: NetAlertX coding standards and conventions. Use this when writing c
|
||||
- follow existing code style and structure, and ensure backward compatibility with existing installations when submitting PRs
|
||||
- all code needs to be scalable to handle large networks with thousands of devices (10k+) without performance degradation
|
||||
- no inline imports, all imports must be at the top of the file
|
||||
- when using `server/logger.py` `mylog()`, only use valid levels: `none`, `minimal`, `verbose`, `debug`, `trace`; invalid levels silently degrade to `none`
|
||||
|
||||
|
||||
## File Length
|
||||
|
||||
@@ -207,13 +207,13 @@
|
||||
"Device_MultiEdit_Backup": "Cuidado, inserir valores errados abaixo interromperá sua configuração. Faça backup do seu banco de dados ou da configuração dos dispositivos primeiro (<a href=\"#\" onclick=\"ExportCSV()\">clique para baixar <i class=\"fa-solid fa-download fa-bounce\"></i> </a>). Leia como recuperar dispositivos deste arquivo no <a href=\"https://docs.netalertx.com/BACKUPS#scenario-2-corrupted-database\" target=\" _blank\">Documentação de backups</a>.",
|
||||
"Device_MultiEdit_Fields": "Editar campos:",
|
||||
"Device_MultiEdit_MassActions": "Ações em massa:",
|
||||
"Device_MultiEdit_No_Devices": "",
|
||||
"Device_MultiEdit_No_Devices": "Nenhum dispositivo selecionado.",
|
||||
"Device_MultiEdit_Tooltip": "Cuidadoso. Clicar aqui aplicará o valor à esquerda a todos os dispositivos selecionados acima.",
|
||||
"Device_NextScan_Imminent": "",
|
||||
"Device_NextScan_In": "",
|
||||
"Device_NextScan_In": "Próxima varredura em aproximadamente ",
|
||||
"Device_NoData_Help": "",
|
||||
"Device_NoData_Scanning": "",
|
||||
"Device_NoData_Title": "",
|
||||
"Device_NoData_Scanning": "Aguarde a primeira varredura - isso pode levar vários minutos após a configuração inicial.",
|
||||
"Device_NoData_Title": "Ainda não foram encontrados nenhum dispositivo",
|
||||
"Device_NoMatch_Title": "",
|
||||
"Device_Save_Failed": "",
|
||||
"Device_Save_Unauthorized": "",
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import html
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
import socket
|
||||
from yattag import indent
|
||||
from yattag.indentation import XMLTokenError
|
||||
from json2table import convert
|
||||
|
||||
# Register NetAlertX modules
|
||||
@@ -146,20 +148,7 @@ class NotificationInstance:
|
||||
mail_html, conf.REPORT_DASHBOARD_URL + "/deviceDetails.php?mac="
|
||||
)
|
||||
|
||||
# Add preheader for inbox preview after all links have been generated.
|
||||
# Invisible padding prevents email clients from showing the start of the email body.
|
||||
preheader = " • ".join(preheaders)
|
||||
|
||||
padding = (" ‌ " * 47)
|
||||
|
||||
mail_html = mail_html.replace(
|
||||
"PREHEADER",
|
||||
preheader + padding,
|
||||
)
|
||||
|
||||
final_html = indent(
|
||||
mail_html, indentation=" ", newline="\r\n", indent_text=True
|
||||
)
|
||||
final_html = finalize_html(mail_html, preheaders)
|
||||
|
||||
send_api(self.JSON, final_text, final_html)
|
||||
|
||||
@@ -335,8 +324,9 @@ def construct_notifications(JSON, section):
|
||||
text = tableTitle + "\n---------\n"
|
||||
|
||||
# Convert a JSON into an HTML table
|
||||
html_data = escape_html_rows(jsn)
|
||||
html = convert(
|
||||
{"data": jsn},
|
||||
{"data": html_data},
|
||||
build_direction=build_direction,
|
||||
table_attributes=table_attributes,
|
||||
)
|
||||
@@ -398,6 +388,45 @@ def format_table(html, thValue, props, newThValue=""):
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Escape free-text values before embedding them into notification HTML
|
||||
def escape_html_rows(rows):
|
||||
"""Return a copy of notification rows with only string values HTML-escaped."""
|
||||
return [
|
||||
{
|
||||
key: html.escape(value) if isinstance(value, str) else value
|
||||
for key, value in row.items()
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Finalize HTML and tolerate pretty-print failures
|
||||
def finalize_html(mail_html, preheaders):
|
||||
"""Insert an escaped preheader and pretty-print HTML, falling back to raw HTML on XML errors."""
|
||||
# Add preheader for inbox preview after all links have been generated.
|
||||
# Invisible padding prevents email clients from showing the start of the email body.
|
||||
preheader = " • ".join(html.escape(entry) for entry in preheaders)
|
||||
padding = (" ‌ " * 47)
|
||||
|
||||
mail_html = mail_html.replace(
|
||||
"PREHEADER",
|
||||
preheader + padding,
|
||||
)
|
||||
|
||||
try:
|
||||
return indent(
|
||||
mail_html, indentation=" ", newline="\r\n", indent_text=True
|
||||
)
|
||||
except XMLTokenError as err:
|
||||
mylog(
|
||||
"none",
|
||||
f"[Notification] Failed to pretty-print HTML report, sending unindented HTML instead: {err}",
|
||||
)
|
||||
return mail_html
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Pre-header Preview
|
||||
def build_preheader(tableTitle, jsn, headers):
|
||||
|
||||
@@ -295,6 +295,68 @@ class TestConstructNotificationsTemplates(unittest.TestCase):
|
||||
|
||||
self.assertEqual(html_without, html_with)
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# HTML output escapes free-text device values while text stays raw
|
||||
# -----------------------------------------------------------------
|
||||
@patch("models.notification_instance.get_setting_value")
|
||||
def test_html_escapes_free_text_values(self, mock_setting):
|
||||
from models.notification_instance import construct_notifications
|
||||
|
||||
mock_setting.side_effect = self._setting_factory({
|
||||
"NTFPRCS_TEXT_SECTION_HEADERS": True,
|
||||
"NTFPRCS_TEXT_TEMPLATE_new_devices": "",
|
||||
})
|
||||
|
||||
devices = [
|
||||
{
|
||||
"devName": "Meta Quest <Pro",
|
||||
"eveMac": "aa:bb:cc:dd:ee:ff",
|
||||
"devVendor": "Meta",
|
||||
"eveIp": "192.168.1.42",
|
||||
"eveDateTime": "2025-01-15 10:30:00",
|
||||
"eveEventType": "New Device",
|
||||
"devComments": "values <=2 break things",
|
||||
}
|
||||
]
|
||||
json_data = _make_json(
|
||||
"new_devices", devices, NEW_DEVICE_COLUMNS, "🆕 New devices"
|
||||
)
|
||||
|
||||
html, text, _ = construct_notifications(json_data, "new_devices")
|
||||
|
||||
self.assertIn("Meta Quest <Pro", html)
|
||||
self.assertIn("values <=2 break things", html)
|
||||
self.assertNotIn("Meta Quest <Pro", html)
|
||||
self.assertNotIn("values <=2 break things", html)
|
||||
self.assertIn("Meta Quest <Pro", text)
|
||||
self.assertIn("values <=2 break things", text)
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Final HTML escapes preheaders and tolerates indent failures
|
||||
# -----------------------------------------------------------------
|
||||
@patch("models.notification_instance.mylog")
|
||||
@patch("models.notification_instance.indent")
|
||||
def test_finalize_html_escapes_preheader_and_falls_back(self, mock_indent, mock_mylog):
|
||||
from models.notification_instance import finalize_html, XMLTokenError
|
||||
|
||||
mock_indent.side_effect = XMLTokenError("broken html")
|
||||
|
||||
# The mock forces the pretty-print failure so we can assert the raw
|
||||
# fallback HTML returned after PREHEADER replacement.
|
||||
template = "<html><body><span>PREHEADER</span>broken < content</body></html>"
|
||||
expected_html = template.replace(
|
||||
"PREHEADER",
|
||||
"Meta Quest <Pro" + (" ‌ " * 47),
|
||||
)
|
||||
final_html = finalize_html(template, ["Meta Quest <Pro"])
|
||||
|
||||
self.assertEqual(final_html, expected_html)
|
||||
mock_indent.assert_called_once()
|
||||
mock_mylog.assert_called_once_with(
|
||||
"none",
|
||||
"[Notification] Failed to pretty-print HTML report, sending unindented HTML instead: broken html",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user