S allius/issue397 (#405)
* add Dashboards log handler to all known loggers * add list of last 3 warnings/errors to page * add note list to page * create LogHandler for the dashboard - simple memory log handler which stores the last 64 warnings/errors for the dashboard * render warnings/errors as note list * add page for warnings and errors * fix double defined build target * add well done message if no errors in the logs * translate page titles * more translations * add Notes page and table for important messages * add unit tests
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
24
app/src/web/log_handler.py
Normal file
24
app/src/web/log_handler.py
Normal file
@@ -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:]
|
||||
@@ -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
|
||||
|
||||
19
app/src/web/notes_list.py
Normal file
19
app/src/web/notes_list.py
Normal file
@@ -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
|
||||
@@ -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(
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
<button href="#" class="w3-bar-item w3-button w3-padding-16 w3-hide-large w3-dark-grey w3-hover-black" onclick="w3_close()" title="close menu"><i class="fa fa-remove fa-fw"></i> Close Menu</button>
|
||||
<a href="{{ url_for('.index')}}" class="w3-bar-item w3-button w3-padding {% block menu1_class %}{% endblock %}"><i class="fa fa-network-wired fa-fw"></i> {{_('Connections')}}</a>
|
||||
<a href="{{ url_for('.mqtt')}}" class="w3-bar-item w3-button w3-padding {% block menu2_class %}{% endblock %}"><i class="fa fa-database fa-fw"></i> MQTT</a>
|
||||
<a href="{{ url_for('.logging')}}" class="w3-bar-item w3-button w3-padding {% block menu3_class %}{% endblock %}"><i class="fa fa-file-export fa-fw"></i> {{_('Log Files')}}</a>
|
||||
<a href="{{ url_for('.notes')}}" class="w3-bar-item w3-button w3-padding {% block menu3_class %}{% endblock %}"><i class="fa fa-exclamation-triangle fa-fw"></i> {{_('Important Messages')}}</a>
|
||||
<a href="{{ url_for('.logging')}}" class="w3-bar-item w3-button w3-padding {% block menu4_class %}{% endblock %}"><i class="fa fa-file-export fa-fw"></i> {{_('Log Files')}}</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
@@ -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 %}<i class="fa fa-network-wired"></i> {{_('Proxy Connection Overview')}}{% endblock headline %}
|
||||
|
||||
|
||||
@@ -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 %}<i class="fa fa-file-export fa-fw"></i> {{_('Log Files')}}{% endblock headline %}
|
||||
{% block content %}
|
||||
<div id="id01" class="w3-modal">
|
||||
|
||||
@@ -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 %}<i class="fa fa-database"></i> {{_('MQTT Overview')}}{% endblock headline %}
|
||||
{% block content %}
|
||||
@@ -45,6 +45,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="notes-list"></div>
|
||||
<div id="mqtt-table"></div>
|
||||
{% endblock content%}
|
||||
|
||||
|
||||
10
app/src/web/templates/page_notes.html.j2
Normal file
10
app/src/web/templates/page_notes.html.j2
Normal file
@@ -0,0 +1,10 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block title %}{{_("TSUN Proxy - Important Messages")}}{% endblock title %}
|
||||
{% block menu3_class %}w3-blue{% endblock %}
|
||||
{% block headline %}<i class="fa fa-exclamation-triangle fa-fw"></i> {{_('Important Messages')}}{% endblock headline %}
|
||||
{% block content %}
|
||||
<div id="notes-list"></div>
|
||||
{% endblock content%}
|
||||
|
||||
{% block footer %}{% endblock footer %}
|
||||
@@ -0,0 +1,23 @@
|
||||
{% if notes|length > 0 %}
|
||||
<div class="w3-container w3-margin-bottom">
|
||||
<h5>{{_("Warnings and error messages")}} </h5>
|
||||
<ul class="w3-ul w3-card-4">
|
||||
{% for note in notes %}
|
||||
<li class="{% if note.level is le(30) %}w3-leftbar w3-rightbar w3-pale-blue w3-border-blue{% else %}w3-leftbar w3-rightbar w3-pale-red w3-border-red{% endif %}">
|
||||
<span class="w3-col" style="width:150px">{{note.ctime|datetimeformat(format='short')}}</span>
|
||||
<span class="w3-col w3-hide-small" style="width:100px">{{note.lname|e}}</span>
|
||||
<span class="w3-rest">{{note.msg|e}}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% elif not hide_if_empty %}
|
||||
<div class="w3-container w3-margin-bottom">
|
||||
<div class="w3-leftbar w3-rightbar w3-pale-green w3-border-green">
|
||||
<div class="w3-container">
|
||||
<h2>{{_("Well done!")}}</h2>
|
||||
<p>{{_("No warnings or errors have been logged since the last proxy start.")}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -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."""
|
||||
|
||||
@@ -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 <EMAIL@ADDRESS>\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."
|
||||
|
||||
|
||||
Reference in New Issue
Block a user