diff --git a/CHANGELOG.md b/CHANGELOG.md
index eedb850..46be5df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased]
+- Dashboard: add Notes page and table for important messages
- Dashboard: add Log-File page
- Dashboard: add Connection page
- add web UI to add-on
diff --git a/Makefile b/Makefile
index b98463f..5964e6e 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ babel:
build:
$(MAKE) -C ha_addons $@
-clean build:
+clean:
$(MAKE) -C app $@
$(MAKE) -C ha_addons $@
diff --git a/app/src/web/__init__.py b/app/src/web/__init__.py
index 24b0e87..ef3728c 100644
--- a/app/src/web/__init__.py
+++ b/app/src/web/__init__.py
@@ -7,6 +7,8 @@ Usage:
from quart import Quart, Blueprint
from quart_babel import Babel
from utils import load_modules
+from .log_handler import LogHandler
+import logging
web = Blueprint('web', __name__)
@@ -30,3 +32,8 @@ class Web:
locale_selector=get_locale,
timezone_selector=get_tz,
default_translation_directories=translation_directories)
+
+ h = LogHandler()
+ logging.getLogger().addHandler(h)
+ for name in logging.root.manager.loggerDict:
+ logging.getLogger(name).addHandler(h)
diff --git a/app/src/web/conn_table.py b/app/src/web/conn_table.py
index ae6fe38..4b81868 100644
--- a/app/src/web/conn_table.py
+++ b/app/src/web/conn_table.py
@@ -4,6 +4,7 @@ from quart_babel import format_datetime, _
from infos import Infos
from . import web
+from .log_handler import LogHandler
def _get_device_icon(client_mode: bool):
@@ -79,5 +80,8 @@ async def data_fetch():
data["conn-table"] = await render_template('templ_table.html.j2',
table=get_table_data())
- data["notes-list"] = await render_template('templ_notes_list.html.j2')
+ data["notes-list"] = await render_template(
+ 'templ_notes_list.html.j2',
+ notes=LogHandler().get_buffer(3),
+ hide_if_empty=True)
return data
diff --git a/app/src/web/log_handler.py b/app/src/web/log_handler.py
new file mode 100644
index 0000000..7565649
--- /dev/null
+++ b/app/src/web/log_handler.py
@@ -0,0 +1,24 @@
+from logging import Handler
+from logging import LogRecord
+import logging
+from collections import deque
+
+from singleton import Singleton
+
+
+class LogHandler(Handler, metaclass=Singleton):
+ def __init__(self, capacity=64):
+ super().__init__(logging.WARNING)
+ self.capacity = capacity
+ self.buffer = deque(maxlen=capacity)
+
+ def emit(self, record: LogRecord):
+ self.buffer.append({
+ 'ctime': record.created,
+ 'level': record.levelno,
+ 'lname': record.levelname,
+ 'msg': record.getMessage()
+ })
+
+ def get_buffer(self, elms=0) -> list:
+ return list(self.buffer)[-elms:]
diff --git a/app/src/web/mqtt_table.py b/app/src/web/mqtt_table.py
index fcd0477..8370c17 100644
--- a/app/src/web/mqtt_table.py
+++ b/app/src/web/mqtt_table.py
@@ -4,6 +4,7 @@ from quart_babel import format_datetime, _
from mqtt import Mqtt
from . import web
+from .log_handler import LogHandler
def _get_row(inv: InverterBase):
@@ -55,4 +56,9 @@ async def mqtt_fetch():
data["mqtt-table"] = await render_template('templ_table.html.j2',
table=get_table_data())
+ data["notes-list"] = await render_template(
+ 'templ_notes_list.html.j2',
+ notes=LogHandler().get_buffer(3),
+ hide_if_empty=True)
+
return data
diff --git a/app/src/web/notes_list.py b/app/src/web/notes_list.py
new file mode 100644
index 0000000..e96c319
--- /dev/null
+++ b/app/src/web/notes_list.py
@@ -0,0 +1,19 @@
+from quart import render_template
+from quart_babel import format_datetime
+
+from . import web
+from .log_handler import LogHandler
+
+
+@web.route('/notes-fetch')
+async def notes_fetch():
+ data = {
+ "update-time": format_datetime(format="medium"),
+ }
+
+ data["notes-list"] = await render_template(
+ 'templ_notes_list.html.j2',
+ notes=LogHandler().get_buffer(),
+ hide_if_empty=False)
+
+ return data
diff --git a/app/src/web/pages.py b/app/src/web/pages.py
index f365239..49d720a 100644
--- a/app/src/web/pages.py
+++ b/app/src/web/pages.py
@@ -18,6 +18,13 @@ async def mqtt():
fetch_url=url_for('.mqtt_fetch'))
+@web.route('/notes')
+async def notes():
+ return await render_template(
+ 'page_notes.html.j2',
+ fetch_url=url_for('.notes_fetch'))
+
+
@web.route('/logging')
async def logging():
return await render_template(
diff --git a/app/src/web/templates/base.html.j2 b/app/src/web/templates/base.html.j2
index 34b6f57..0ef1f7e 100644
--- a/app/src/web/templates/base.html.j2
+++ b/app/src/web/templates/base.html.j2
@@ -57,7 +57,8 @@
-
+
+
diff --git a/app/src/web/templates/page_index.html.j2 b/app/src/web/templates/page_index.html.j2
index f8364c5..65cae0d 100644
--- a/app/src/web/templates/page_index.html.j2
+++ b/app/src/web/templates/page_index.html.j2
@@ -1,6 +1,6 @@
{% extends 'base.html.j2' %}
-{% block title %} TSUN Proxy - Connections {% endblock title%}
+{% block title %}{{_("TSUN Proxy - Connections")}}{% endblock title %}
{% block menu1_class %}w3-blue{% endblock %}
{% block headline %} {{_('Proxy Connection Overview')}}{% endblock headline %}
diff --git a/app/src/web/templates/page_logging.html.j2 b/app/src/web/templates/page_logging.html.j2
index 720a1e6..f80763c 100644
--- a/app/src/web/templates/page_logging.html.j2
+++ b/app/src/web/templates/page_logging.html.j2
@@ -1,7 +1,7 @@
{% extends 'base.html.j2' %}
-{% block title %} TSUN Proxy - Log Files {% endblock title%}
-{% block menu3_class %}w3-blue{% endblock %}
+{% block title %}{{_("TSUN Proxy - Log Files")}}{% endblock title %}
+{% block menu4_class %}w3-blue{% endblock %}
{% block headline %} {{_('Log Files')}}{% endblock headline %}
{% block content %}
diff --git a/app/src/web/templates/page_mqtt.html.j2 b/app/src/web/templates/page_mqtt.html.j2
index f1010de..0f23492 100644
--- a/app/src/web/templates/page_mqtt.html.j2
+++ b/app/src/web/templates/page_mqtt.html.j2
@@ -1,6 +1,6 @@
{% extends 'base.html.j2' %}
-{% block title %} TSUN Proxy - MQTT Status {% endblock title%}
+{% block title %}{{_("TSUN Proxy - MQTT Status")}}{% endblock title %}
{% block menu2_class %}w3-blue{% endblock %}
{% block headline %} {{_('MQTT Overview')}}{% endblock headline %}
{% block content %}
@@ -45,6 +45,7 @@
+
{% endblock content%}
diff --git a/app/src/web/templates/page_notes.html.j2 b/app/src/web/templates/page_notes.html.j2
new file mode 100644
index 0000000..495e5e2
--- /dev/null
+++ b/app/src/web/templates/page_notes.html.j2
@@ -0,0 +1,10 @@
+{% extends 'base.html.j2' %}
+
+{% block title %}{{_("TSUN Proxy - Important Messages")}}{% endblock title %}
+{% block menu3_class %}w3-blue{% endblock %}
+{% block headline %} {{_('Important Messages')}}{% endblock headline %}
+{% block content %}
+
+{% endblock content%}
+
+{% block footer %}{% endblock footer %}
diff --git a/app/src/web/templates/templ_notes_list.html.j2 b/app/src/web/templates/templ_notes_list.html.j2
index e69de29..6d5d98e 100644
--- a/app/src/web/templates/templ_notes_list.html.j2
+++ b/app/src/web/templates/templ_notes_list.html.j2
@@ -0,0 +1,23 @@
+{% if notes|length > 0 %}
+
+
{{_("Warnings and error messages")}}
+
+ {% for note in notes %}
+ -
+ {{note.ctime|datetimeformat(format='short')}}
+ {{note.lname|e}}
+ {{note.msg|e}}
+
+ {% endfor %}
+
+
+{% elif not hide_if_empty %}
+
+
+
+
{{_("Well done!")}}
+
{{_("No warnings or errors have been logged since the last proxy start.")}}
+
+
+
+{% endif %}
\ No newline at end of file
diff --git a/app/tests/test_web_route.py b/app/tests/test_web_route.py
index cf5f307..4ed9b36 100644
--- a/app/tests/test_web_route.py
+++ b/app/tests/test_web_route.py
@@ -68,6 +68,13 @@ async def test_rel_page(client):
assert response.mimetype == 'text/html'
web.build_relative_urls = False
+@pytest.mark.asyncio
+async def test_notes(client):
+ """Test the notes page route."""
+ response = await client.get('/notes')
+ assert response.status_code == 200
+ assert response.mimetype == 'text/html'
+
@pytest.mark.asyncio
async def test_logging(client):
"""Test the logging page route."""
@@ -185,6 +192,15 @@ async def test_mqtt_fetch(client, create_inverter):
assert response.status_code == 200
+@pytest.mark.asyncio
+async def test_notes_fetch(client, config_conn):
+ """Test the notes-fetch route."""
+ _ = create_inverter
+
+ response = await client.get('/notes-fetch')
+ assert response.status_code == 200
+
+
@pytest.mark.asyncio
async def test_file_fetch(client, config_conn):
"""Test the data-fetch route."""
diff --git a/app/translations/de/LC_MESSAGES/messages.po b/app/translations/de/LC_MESSAGES/messages.po
index f4284ab..8daf333 100644
--- a/app/translations/de/LC_MESSAGES/messages.po
+++ b/app/translations/de/LC_MESSAGES/messages.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: tsun-gen3-proxy 0.14.0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-05-03 21:59+0200\n"
+"POT-Creation-Date: 2025-05-04 18:16+0200\n"
"PO-Revision-Date: 2025-04-18 16:24+0200\n"
"Last-Translator: FULL NAME \n"
"Language: de\n"
@@ -19,39 +19,39 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
-#: src/web/conn_table.py:51 src/web/templates/base.html.j2:58
+#: src/web/conn_table.py:52 src/web/templates/base.html.j2:58
msgid "Connections"
msgstr "Verbindungen"
-#: src/web/conn_table.py:58
+#: src/web/conn_table.py:59
msgid "Device-IP:Port"
msgstr "Geräte-IP:Port"
-#: src/web/conn_table.py:58
+#: src/web/conn_table.py:59
msgid "Device-IP"
msgstr "Geräte-IP"
-#: src/web/conn_table.py:59 src/web/mqtt_table.py:33
+#: src/web/conn_table.py:60 src/web/mqtt_table.py:34
msgid "Serial-No"
msgstr "Seriennummer"
-#: src/web/conn_table.py:60
+#: src/web/conn_table.py:61
msgid "Cloud-IP:Port"
msgstr "Cloud-IP:Port"
-#: src/web/conn_table.py:60
+#: src/web/conn_table.py:61
msgid "Cloud-IP"
msgstr "Cloud-IP"
-#: src/web/mqtt_table.py:26
+#: src/web/mqtt_table.py:27
msgid "MQTT devices"
msgstr "MQTT Geräte"
-#: src/web/mqtt_table.py:34
+#: src/web/mqtt_table.py:35
msgid "Node-ID"
msgstr ""
-#: src/web/mqtt_table.py:35
+#: src/web/mqtt_table.py:36
msgid "HA-Area"
msgstr ""
@@ -63,10 +63,18 @@ msgstr "Aktualisiert:"
msgid "Version:"
msgstr ""
-#: src/web/templates/base.html.j2:60 src/web/templates/page_logging.html.j2:5
+#: src/web/templates/base.html.j2:60 src/web/templates/page_notes.html.j2:5
+msgid "Important Messages"
+msgstr "Wichtige Hinweise"
+
+#: src/web/templates/base.html.j2:61 src/web/templates/page_logging.html.j2:5
msgid "Log Files"
msgstr "Log Dateien"
+#: src/web/templates/page_index.html.j2:3
+msgid "TSUN Proxy - Connections"
+msgstr "TSUN Proxy - Verbindungen"
+
#: src/web/templates/page_index.html.j2:5
msgid "Proxy Connection Overview"
msgstr "Proxy Verbindungen"
@@ -103,6 +111,10 @@ msgstr "Emu Modus"
msgid "Emulation sends data to cloud"
msgstr "Emulation sendet in die Cloud"
+#: src/web/templates/page_logging.html.j2:3
+msgid "TSUN Proxy - Log Files"
+msgstr "TSUN Proxy - Log Dateien"
+
#: src/web/templates/page_logging.html.j2:10
msgid "Do you really want to delete the log file"
msgstr "Soll die Datei wirklich gelöscht werden"
@@ -115,6 +127,10 @@ msgstr "File löschen"
msgid "Abort"
msgstr "Abbruch"
+#: src/web/templates/page_mqtt.html.j2:3
+msgid "TSUN Proxy - MQTT Status"
+msgstr ""
+
#: src/web/templates/page_mqtt.html.j2:5
msgid "MQTT Overview"
msgstr "MQTT Überblick"
@@ -143,6 +159,10 @@ msgstr "Empfangene Topics"
msgid "Number of topics received"
msgstr "Anzahl der empfangenen Topics"
+#: src/web/templates/page_notes.html.j2:3
+msgid "TSUN Proxy - Important Messages"
+msgstr "TSUN Proxy - Wichtige Hinweise"
+
#: src/web/templates/templ_log_files_list.html.j2:11
msgid "Created"
msgstr "Erzeugt"
@@ -159,24 +179,17 @@ msgstr "Größe"
msgid "Download File"
msgstr "Datei Download"
-#~ msgid "MQTT Server"
-#~ msgstr ""
+#: src/web/templates/templ_notes_list.html.j2:3
+msgid "Warnings and error messages"
+msgstr "Warnungen und Fehlermeldungen"
-#~ msgid "MQTT User"
-#~ msgstr ""
+#: src/web/templates/templ_notes_list.html.j2:18
+msgid "Well done!"
+msgstr "Gut gemacht!"
-#~ msgid "MQTT Connected"
-#~ msgstr ""
-
-#~ msgid "Home Assistant Status"
-#~ msgstr ""
-
-#~ msgid "MQTT Publish Count"
-#~ msgstr ""
-
-#~ msgid "MQTT Reveiced Count"
-#~ msgstr ""
-
-#~ msgid "MQTT Connect Time"
-#~ msgstr "MQTT Verbindungszeit"
+#: src/web/templates/templ_notes_list.html.j2:19
+msgid "No warnings or errors have been logged since the last proxy start."
+msgstr ""
+"Seit dem letzten Proxystart wurden keine Warnungen oder Fehler "
+"protokolliert."