Compare commits

..

14 Commits

Author SHA1 Message Date
Stefan Allius
d0563200ce update changelog 2025-05-01 19:28:21 +02:00
Stefan Allius
94f3b67cb2 set quart debug flag for debug versions 2025-05-01 17:55:22 +02:00
Stefan Allius
a2da9d255f add german translations 2025-05-01 17:54:28 +02:00
Stefan Allius
27ae3132c2 add sub_dir to test log path
- non files must be skipped. To test this we add
  a sub directory to the test log directory
2025-05-01 17:40:42 +02:00
Stefan Allius
e74f9e0848 add test log file to project 2025-05-01 17:29:40 +02:00
Stefan Allius
bb4640a623 initialize config structure for log-file tests 2025-05-01 17:22:51 +02:00
Stefan Allius
78a7a7577d improve dashboard unit tests
- add log file tests
 - check content-languages after language switch
2025-05-01 17:11:08 +02:00
Stefan Allius
d7c003cb15 initialize log-path in test config 2025-05-01 17:09:23 +02:00
Stefan Allius
8db0f18c05 rename Download page into Log files 2025-05-01 17:08:41 +02:00
Stefan Allius
f3c4fdb093 build and send list of log-files 2025-05-01 17:08:01 +02:00
Stefan Allius
59e8508f4c jump to referer page
- after changing the language, we jump to
  the referer page, if the attribute exists
2025-05-01 17:05:49 +02:00
Stefan Allius
24950244c6 rename page files 2025-05-01 17:05:03 +02:00
Stefan Allius
36dfdd3d41 rename template files 2025-05-01 17:02:38 +02:00
Stefan Allius
035f96461a store logging path in Config class 2025-05-01 16:58:48 +02:00
16 changed files with 53 additions and 343 deletions

View File

@@ -7,18 +7,13 @@ from modbus import Modbus
from messages import Message
from cnf.config import Config
from singleton import Singleton
from datetime import datetime
logger_mqtt = logging.getLogger('mqtt')
class Mqtt(metaclass=Singleton):
__client: aiomqtt.Client = None
__client = None
__cb_mqtt_is_up = None
ctime = None
published: int = 0
received: int = 0
def __init__(self, cb_mqtt_is_up):
logger_mqtt.debug('MQTT: __init__')
@@ -57,7 +52,6 @@ class Mqtt(metaclass=Singleton):
| int | float | None = None) -> None:
if self.__client:
await self.__client.publish(topic, payload)
self.published += 1
async def __loop(self) -> None:
mqtt = Config.get('mqtt')
@@ -75,9 +69,6 @@ class Mqtt(metaclass=Singleton):
try:
async with self.__client:
logger_mqtt.info('MQTT broker connection established')
self.ctime = datetime.now()
self.published = 0
self.received = 0
if self.__cb_mqtt_is_up:
await self.__cb_mqtt_is_up()
@@ -93,8 +84,6 @@ class Mqtt(metaclass=Singleton):
await self.dispatch_msg(message)
except aiomqtt.MqttError:
self.ctime = None
if Config.is_default('mqtt'):
logger_mqtt.info(
"MQTT is unconfigured; Check your config.toml!")
@@ -112,14 +101,11 @@ class Mqtt(metaclass=Singleton):
return
except Exception:
# self.inc_counter('SW_Exception') # fixme
self.ctime = None
logger_mqtt.error(
f"Exception:\n"
f"{traceback.format_exc()}")
async def dispatch_msg(self, message):
self.received += 1
if message.topic.matches(self.ha_status_topic):
status = message.payload.decode("UTF-8")
logger_mqtt.info('Home-Assistant Status:'

View File

@@ -145,10 +145,6 @@ def main(): # pragma: no cover
serv_name = os.getenv('SERVICE_NAME', 'proxy')
version = os.getenv('VERSION', 'unknown')
@app.context_processor
def utility_processor():
return dict(version=version)
setattr(logging.handlers, "log_path", args.log_path)
setattr(logging.handlers, "log_backups", args.log_backups)
os.makedirs(args.log_path, exist_ok=True)

View File

@@ -48,7 +48,6 @@ def _get_row(inv: InverterBase):
def get_table_data():
'''build the connection table'''
table = {
"headline": _('Connections'),
"col_classes": [
"w3-hide-small w3-hide-medium", "w3-hide-large",
"",
@@ -76,7 +75,7 @@ async def data_fetch():
"proxy-cnt": f"<h3>{Infos.get_counter('ProxyMode_Cnt')}</h3>",
"emulation-cnt": f"<h3>{Infos.get_counter('EmuMode_Cnt')}</h3>",
}
data["conn-table"] = await render_template('templ_table.html.j2',
data["conn-table"] = await render_template('templ_conn_table.html.j2',
table=get_table_data())
data["notes-list"] = await render_template('templ_notes_list.html.j2')

View File

@@ -40,6 +40,7 @@ async def file_fetch():
data["file-list"] = await render_template('templ_log_files_list.html.j2',
dir_list=get_list_data())
data["notes-list"] = await render_template('templ_notes_list.html.j2')
return data
@@ -49,12 +50,3 @@ async def send(file):
directory=Config.get_log_path(),
file_name=secure_filename(file),
as_attachment=True)
@web.route('/del-file/<file>', methods=['DELETE'])
async def delete(file):
try:
os.remove(Config.get_log_path() + secure_filename(file))
except OSError:
return 'File not found', 404
return '', 204

View File

@@ -1,58 +0,0 @@
from inverter_base import InverterBase
from quart import render_template
from quart_babel import format_datetime, _
from mqtt import Mqtt
from . import web
def _get_row(inv: InverterBase):
'''build one row for the connection table'''
entity_prfx = inv.entity_prfx
inv_serial = inv.local.stream.inv_serial
node_id = inv.local.stream.node_id
sug_area = inv.local.stream.sug_area
row = []
row.append(inv_serial)
row.append(entity_prfx+node_id)
row.append(sug_area)
return row
def get_table_data():
'''build the connection table'''
table = {
"headline": _('MQTT devices'),
"col_classes": [
"",
"",
"",
],
"thead": [[
_("Serial-No"),
_('Node-ID'),
_('HA-Area'),
]],
"tbody": []
}
for inverter in InverterBase:
table['tbody'].append(_get_row(inverter))
return table
@web.route('/mqtt-fetch')
async def mqtt_fetch():
mqtt = Mqtt(None)
ctime = format_datetime(dt=mqtt.ctime, format='short')
data = {
"update-time": format_datetime(format="medium"),
"mqtt-ctime": f"<h3>{ctime}</h3>",
"mqtt-tx": f"<h3>{mqtt.published}</h3>",
"mqtt-rx": f"<h3>{mqtt.received}</h3>",
}
data["mqtt-table"] = await render_template('templ_table.html.j2',
table=get_table_data())
return data

View File

@@ -11,11 +11,9 @@ async def index():
fetch_url=url_for('.data_fetch'))
@web.route('/mqtt')
async def mqtt():
return await render_template(
'page_mqtt.html.j2',
fetch_url=url_for('.mqtt_fetch'))
@web.route('/page')
async def empty():
return await render_template('empty.html.j2')
@web.route('/logging')

View File

@@ -41,12 +41,12 @@
<!-- Sidebar/menu -->
<nav class="w3-sidebar w3-collapse w3-white" style="z-index:3;width:250px;" id="mySidebar"><br>
<div class="w3-container w3-cell-row">
<div class="w3-cell w3-cell-middle">
<div class="w3-container w3-row">
<div class="w3-col s4">
<img src="{{url_for('static', filename= 'images/favicon.svg') }}" alt="" class="w3-circle w3-margin-right" style="width:60px">
</div>
<div class="w3-cell">
<span><b class="w3-xlarge">TSUN-Proxy</b><br>{{_('Version:')}} {{version}}</span>
<div class="w3-col s8 w3-bar">
<h3>TSUN-Proxy</h3><br>
</div>
</div>
<hr>
@@ -56,7 +56,7 @@
<div class="w3-bar-block">
<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('.empty')}}" 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>
</div>
</nav>

View File

@@ -0,0 +1,9 @@
{% extends 'base.html.j2' %}
{% block title %} TSUN Proxy - View {% endblock title%}
{% block menu2_class %}w3-blue{% endblock %}
{% block content %}
{% endblock content%}
{% block footer %}{% endblock footer %}

View File

@@ -7,7 +7,6 @@
{% block content %}
<div class="w3-row-padding w3-margin-bottom">
<div class="w3-quarter">
<div class="w3-card-4">
<div class="w3-container w3-indigo w3-padding-16">
<div class="w3-left"><i class="fa fa-upload w3-xxxlarge fa-rotate-180"></i></div>
<div id = "server-cnt" class="w3-right">
@@ -17,10 +16,8 @@
<h4>{{_('Server Mode')}}</h4>
<div class="w3-hide-small w3-hide-medium" style="min-height:50px">{{_('Established from device to proxy')}}</div>
</div>
</div>
</div>
<div class="w3-quarter">
<div class="w3-card-4">
<div class="w3-container w3-purple w3-padding-16">
<div class="w3-left"><i class="fa fa-download w3-xxxlarge fa-rotate-180"></i></div>
<div id = "client-cnt" class="w3-right">
@@ -30,10 +27,8 @@
<h4>{{_('Client Mode')}}</h4>
<div class="w3-hide-small w3-hide-medium" style="min-height:50px">{{_('Established from proxy to device')}}</div>
</div>
</div>
</div>
<div class="w3-quarter">
<div class="w3-card-4">
<div class="w3-container w3-orange w3-text-white w3-padding-16">
<div class="w3-left"><i class="fa fa-cloud w3-xxxlarge"></i></div>
<div id = "proxy-cnt" class="w3-right">
@@ -43,10 +38,8 @@
<h4>{{_('Proxy Mode')}}</h4>
<div class="w3-hide-small w3-hide-medium" style="min-height:50px">{{_('Forwarding data to cloud')}}</div>
</div>
</div>
</div>
<div class="w3-quarter">
<div class="w3-card-4">
<div class="w3-container w3-teal w3-padding-16">
<div class="w3-left"><i class="fa fa-cloud-arrow-up-alt w3-xxxlarge"></i></div>
<div id = "emulation-cnt" class="w3-right">
@@ -56,12 +49,9 @@
<h4>{{_('Emu Mode')}}</h4>
<div class="w3-hide-small w3-hide-medium" style="min-height:50px">{{_('Emulation sends data to cloud')}}</div>
</div>
</div>
</div>
</div>
<div id="notes-list"></div>
<div id="conn-table"></div>
<div class="w3-container" id="notes-list"></div>
<div class="w3-container" id="conn-table"></div>
{% endblock content%}
{% block footer %}{% endblock footer %}

View File

@@ -1,30 +1,11 @@
{% extends 'base.html.j2' %}
{% block title %} TSUN Proxy - Log Files {% endblock title%}
{% block title %} TSUN Proxy - Downloads {% endblock title%}
{% block menu3_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">
<div class="w3-modal-content" style="width:600px">
<div class="w3-container w3-padding-24">
<h2>{{_("Do you really want to delete the log file")}}:<br><b><span id="id03"></span></b> ?</h2>
<div class="w3-bar">
<button id="id02" class="w3-button w3-red" onclick="deleteFile(); document.getElementById('id01').style.display='none'">{{_('Delete File</button')}}>
<button class="w3-button w3-grey w3-right" onclick="document.getElementById('id01').style.display='none'">{{_('Abort')}}</button>
</div>
</div>
</div>
</div>
<div id="file-list"></div>
<script>
function deleteFile() {
fname = document.getElementById('id02').href;
fetch(fname, {method: 'DELETE'})
.then(fetch_data())
}
</script>
<div class="w3-container" id="file-list"></div>
{% endblock content%}
{% block footer %}{% endblock footer %}

View File

@@ -1,51 +0,0 @@
{% extends 'base.html.j2' %}
{% 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 %}
<div class="w3-row-padding w3-margin-bottom">
<div class="w3-third">
<div class="w3-card-4">
<div class="w3-container w3-indigo w3-padding-16">
<div class="w3-left"><i class="fa fa-link w3-xxxlarge"></i></div>
<div id = "mqtt-ctime" class="w3-right">
<h3>-</h3>
</div>
<div class="w3-clear"></div>
<h4>{{_('Connection Time')}}</h4>
<div class="w3-hide-small w3-hide-medium" style="min-height:50px">{{_('Time at which the connection was established')}}</div>
</div>
</div>
</div>
<div class="w3-third">
<div class="w3-card-4">
<div class="w3-container w3-purple w3-padding-16">
<div class="w3-left"><i class="fa fa-server w3-xxxlarge"></i></div>
<div id = "mqtt-tx" class="w3-right">
<h3>-</h3>
</div>
<div class="w3-clear"></div>
<h4>{{_('Published Topics')}}</h4>
<div class="w3-hide-small w3-hide-medium" style="min-height:50px">{{_('Number of published topics')}}</div>
</div>
</div>
</div>
<div class="w3-third">
<div class="w3-card-4">
<div class="w3-container w3-orange w3-text-white w3-padding-16">
<div class="w3-left"><i class="fa fa-user w3-xxxlarge"></i></div>
<div id = "mqtt-rx" class="w3-right">
<h3>-</h3>
</div>
<div class="w3-clear"></div>
<h4>{{_('Received Topics')}}</h4>
<div class="w3-hide-small w3-hide-medium" style="min-height:50px">{{_('Number of topics received')}}</div>
</div>
</div>
</div>
</div>
<div id="mqtt-table"></div>
{% endblock content%}
{% block footer %}{% endblock footer %}

View File

@@ -12,11 +12,8 @@
{% endif %}
{%- endmacro%}
<div class="w3-container w3-margin-bottom">
<h5>{{table.headline}}</h5>
<div class="w3-card-4">
<table class="w3-table w3-bordered w3-hoverable w3-white">
<h5>{{_('Connections')}}</h5>
<table class="w3-table w3-striped w3-bordered w3-border w3-hoverable w3-white">
{% if table.thead is defined%}
<thead>
{% for row in table.thead %}
@@ -38,5 +35,3 @@
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@@ -3,7 +3,7 @@
<div class="w3-quarter w3-margin-bottom">
<div class="w3-card-4">
<header class="w3-container w3-teal" style="min-height:80px">
<header class="w3-container w3-blue">
<h4>{{file.name}}</h4>
</header>
@@ -16,10 +16,8 @@
{% endfor %}
</table>
<footer class="w3-teal">
<a href="{{ url_for('.send',file=file.name)}}" class="w3-button w3-hover-teal w3-hover-text-black"><i class="fa fa-file-download"></i>  {{_('Download File')}}</a>
<a class="w3-button w3-right w3-hover-teal w3-hover-text-black"
onclick="document.getElementById('id03').innerHTML='{{file.name}}'; document.getElementById('id02').href='{{ url_for('.delete',file=file.name)}}'; document.getElementById('id01').style.display='block';"><i class="fa fa-trash"></i></a>
<footer class="w3-blue">
<a href="{{ url_for('web.send',file=file.name)}}" class="w3-button w3-hover-blue w3-hover-text-black"><i class="fa fa-file-export"></i>  {{_('Download File')}}</a>
</footer>
</div>

View File

@@ -140,7 +140,6 @@ async def test_ha_reconnect(config_mqtt_conn):
assert on_connect.is_set()
finally:
assert m.received == 2
await m.close()
@pytest.mark.asyncio

View File

@@ -6,9 +6,6 @@ from async_stream import AsyncStreamClient
from gen3plus.inverter_g3p import InverterG3P
from test_inverter_g3p import FakeReader, FakeWriter, config_conn
from cnf.config import Config
from mock import patch
from proxy import Proxy
import os, errno
pytest_plugins = ('pytest_asyncio',)
@@ -54,16 +51,16 @@ async def test_home(client):
@pytest.mark.asyncio
async def test_page(client):
"""Test the mqtt page route."""
response = await client.get('/mqtt')
"""Test the empty page route."""
response = await client.get('/page')
assert response.status_code == 200
assert response.mimetype == 'text/html'
@pytest.mark.asyncio
async def test_rel_page(client):
"""Test the mqtt route."""
"""Test the empty page route."""
web.build_relative_urls = True
response = await client.get('/mqtt')
response = await client.get('/page')
assert response.status_code == 200
assert response.mimetype == 'text/html'
web.build_relative_urls = False
@@ -153,7 +150,7 @@ async def test_language_en(client):
assert response.mimetype == 'text/html'
client.set_cookie('test', key='language', value='de')
response = await client.get('/mqtt')
response = await client.get('/page')
assert response.status_code == 200
assert response.mimetype == 'text/html'
@@ -175,16 +172,6 @@ async def test_language_unknown(client):
assert response.mimetype == 'text/html'
@pytest.mark.asyncio
async def test_mqtt_fetch(client, create_inverter):
"""Test the mqtt-fetch route."""
_ = create_inverter
Proxy.class_init()
response = await client.get('/mqtt-fetch')
assert response.status_code == 200
@pytest.mark.asyncio
async def test_file_fetch(client, config_conn):
"""Test the data-fetch route."""
@@ -219,39 +206,3 @@ async def test_invalid_send_file(client, config_conn):
assert Config.log_path == 'app/tests/log/'
response = await client.get('/send-file/../test_web_route.py')
assert response.status_code == 404
@pytest.fixture
def patch_os_remove_err():
def new_remove(file_path: str):
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT), file_path)
with patch.object(os, 'remove', new_remove) as wrapped_os:
yield wrapped_os
@pytest.fixture
def patch_os_remove_ok():
def new_remove(file_path: str):
return
with patch.object(os, 'remove', new_remove) as wrapped_os:
yield wrapped_os
@pytest.mark.asyncio
async def test_del_file_ok(client, config_conn, patch_os_remove_ok):
"""Test the del-file route with no error."""
_ = config_conn
_ = patch_os_remove_ok
assert Config.log_path == 'app/tests/log/'
response = await client.delete ('/del-file/test.txt')
assert response.status_code == 204
@pytest.mark.asyncio
async def test_del_file_err(client, config_conn, patch_os_remove_err):
"""Test the send-file route with OSError."""
_ = config_conn
_ = patch_os_remove_err
assert Config.log_path == 'app/tests/log/'
response = await client.delete ('/del-file/test.txt')
assert response.status_code == 404

View File

@@ -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-01 17:48+0200\n"
"PO-Revision-Date: 2025-04-18 16:24+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@@ -19,49 +19,34 @@ 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
msgid "Connections"
msgstr "Verbindungen"
#: src/web/conn_table.py:58
#: src/web/conn_table.py:57
msgid "Device-IP:Port"
msgstr "Geräte-IP:Port"
#: src/web/conn_table.py:58
#: src/web/conn_table.py:57
msgid "Device-IP"
msgstr "Geräte-IP"
#: src/web/conn_table.py:59 src/web/mqtt_table.py:33
#: src/web/conn_table.py:58
msgid "Serial-No"
msgstr "Seriennummer"
#: src/web/conn_table.py:60
#: src/web/conn_table.py:59
msgid "Cloud-IP:Port"
msgstr "Cloud-IP:Port"
#: src/web/conn_table.py:60
#: src/web/conn_table.py:59
msgid "Cloud-IP"
msgstr "Cloud-IP"
#: src/web/mqtt_table.py:26
msgid "MQTT devices"
msgstr "MQTT Geräte"
#: src/web/mqtt_table.py:34
msgid "Node-ID"
msgstr ""
#: src/web/mqtt_table.py:35
msgid "HA-Area"
msgstr ""
#: src/web/templates/base.html.j2:37
msgid "Updated:"
msgstr "Aktualisiert:"
#: src/web/templates/base.html.j2:49
msgid "Version:"
msgstr ""
#: src/web/templates/base.html.j2:58
#: src/web/templates/templ_conn_table.html.j2:15
msgid "Connections"
msgstr "Verbindungen"
#: src/web/templates/base.html.j2:60 src/web/templates/page_logging.html.j2:5
msgid "Log Files"
@@ -71,78 +56,38 @@ msgstr "Log Dateien"
msgid "Proxy Connection Overview"
msgstr "Proxy Verbindungen"
#: src/web/templates/page_index.html.j2:17
#: src/web/templates/page_index.html.j2:16
msgid "Server Mode"
msgstr "Server Modus"
#: src/web/templates/page_index.html.j2:18
#: src/web/templates/page_index.html.j2:17
msgid "Established from device to proxy"
msgstr "Vom Gerät zum Proxy aufgebaut"
#: src/web/templates/page_index.html.j2:30
#: src/web/templates/page_index.html.j2:27
msgid "Client Mode"
msgstr "Client Modus"
#: src/web/templates/page_index.html.j2:31
#: src/web/templates/page_index.html.j2:28
msgid "Established from proxy to device"
msgstr "Vom Proxy zum Gerät aufgebaut"
#: src/web/templates/page_index.html.j2:43
#: src/web/templates/page_index.html.j2:38
msgid "Proxy Mode"
msgstr "Proxy Modus"
#: src/web/templates/page_index.html.j2:44
#: src/web/templates/page_index.html.j2:39
msgid "Forwarding data to cloud"
msgstr "Weiterleitung in die Cloud"
#: src/web/templates/page_index.html.j2:56
#: src/web/templates/page_index.html.j2:49
msgid "Emu Mode"
msgstr "Emu Modus"
#: src/web/templates/page_index.html.j2:57
#: src/web/templates/page_index.html.j2:50
msgid "Emulation sends data to cloud"
msgstr "Emulation sendet in die Cloud"
#: 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"
#: src/web/templates/page_logging.html.j2:12
msgid "Delete File</button"
msgstr "File löschen"
#: src/web/templates/page_logging.html.j2:13
msgid "Abort"
msgstr "Abbruch"
#: src/web/templates/page_mqtt.html.j2:5
msgid "MQTT Overview"
msgstr "MQTT Überblick"
#: src/web/templates/page_mqtt.html.j2:16
msgid "Connection Time"
msgstr "Verbindungszeit"
#: src/web/templates/page_mqtt.html.j2:17
msgid "Time at which the connection was established"
msgstr "Zeitpunkt des Verbindungsaufbaus"
#: src/web/templates/page_mqtt.html.j2:29
msgid "Published Topics"
msgstr "Gesendete Topics"
#: src/web/templates/page_mqtt.html.j2:30
msgid "Number of published topics"
msgstr "Anzahl der veröffentlichten Topics"
#: src/web/templates/page_mqtt.html.j2:42
msgid "Received Topics"
msgstr "Empfangene Topics"
#: src/web/templates/page_mqtt.html.j2:43
msgid "Number of topics received"
msgstr "Anzahl der empfangenen Topics"
#: src/web/templates/templ_log_files_list.html.j2:11
msgid "Created"
msgstr "Erzeugt"
@@ -159,24 +104,4 @@ msgstr "Größe"
msgid "Download File"
msgstr "Datei Download"
#~ msgid "MQTT Server"
#~ msgstr ""
#~ msgid "MQTT User"
#~ msgstr ""
#~ 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"