Compare commits
8 Commits
s-allius/i
...
s-allius/i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c6ebcd486 | ||
|
|
2a96fd2d3c | ||
|
|
4371f3dadb | ||
|
|
caf88e2849 | ||
|
|
b7f7dd0441 | ||
|
|
207bc51c42 | ||
|
|
a0afe71654 | ||
|
|
907dcb1623 |
@@ -10,39 +10,40 @@ from .log_handler import LogHandler
|
|||||||
def _get_device_icon(client_mode: bool):
|
def _get_device_icon(client_mode: bool):
|
||||||
'''returns the icon for the device conntection'''
|
'''returns the icon for the device conntection'''
|
||||||
if client_mode:
|
if client_mode:
|
||||||
return 'fa-download fa-rotate-180'
|
return 'fa-download fa-rotate-180', 'Server Mode'
|
||||||
|
|
||||||
return 'fa-upload fa-rotate-180'
|
return 'fa-upload fa-rotate-180', 'Client Mode'
|
||||||
|
|
||||||
|
|
||||||
def _get_cloud_icon(emu_mode: bool):
|
def _get_cloud_icon(emu_mode: bool):
|
||||||
'''returns the icon for the cloud conntection'''
|
'''returns the icon for the cloud conntection'''
|
||||||
if emu_mode:
|
if emu_mode:
|
||||||
return 'fa-cloud-arrow-up-alt'
|
return 'fa-cloud-arrow-up-alt', 'Emu Mode'
|
||||||
|
|
||||||
return 'fa-cloud'
|
return 'fa-cloud', 'Proxy Mode'
|
||||||
|
|
||||||
|
|
||||||
def _get_row(inv: InverterBase):
|
def _get_row(inv: InverterBase):
|
||||||
'''build one row for the connection table'''
|
'''build one row for the connection table'''
|
||||||
client_mode = inv.client_mode
|
client_mode = inv.client_mode
|
||||||
inv_serial = inv.local.stream.inv_serial
|
inv_serial = inv.local.stream.inv_serial
|
||||||
icon1 = _get_device_icon(client_mode)
|
icon1, descr1 = _get_device_icon(client_mode)
|
||||||
ip1, port1 = inv.addr
|
ip1, port1 = inv.addr
|
||||||
icon2 = ''
|
icon2 = ''
|
||||||
|
descr2 = ''
|
||||||
ip2 = '--'
|
ip2 = '--'
|
||||||
port2 = '--'
|
port2 = '--'
|
||||||
|
|
||||||
if inv.remote.ifc:
|
if inv.remote.ifc:
|
||||||
ip2, port2 = inv.remote.ifc.r_addr
|
ip2, port2 = inv.remote.ifc.r_addr
|
||||||
icon2 = _get_cloud_icon(client_mode)
|
icon2, descr2 = _get_cloud_icon(client_mode)
|
||||||
|
|
||||||
row = []
|
row = []
|
||||||
row.append(f'<i class="fa {icon1}"></i> {ip1}:{port1}')
|
row.append(f'<i class="fa {icon1}" title="{_(descr1)}"></i> {ip1}:{port1}')
|
||||||
row.append(f'<i class="fa {icon1}"></i> {ip1}')
|
row.append(f'<i class="fa {icon1}" title="{_(descr1)}"></i> {ip1}')
|
||||||
row.append(inv_serial)
|
row.append(inv_serial)
|
||||||
row.append(f'<i class="fa {icon2}"></i> {ip2}:{port2}')
|
row.append(f'<i class="fa {icon2}" title="{_(descr2)}"></i> {ip2}:{port2}')
|
||||||
row.append(f'<i class="fa {icon2}"></i> {ip2}')
|
row.append(f'<i class="fa {icon2}" title="{_(descr2)}"></i> {ip2}')
|
||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,58 @@
|
|||||||
from quart import render_template
|
from quart import render_template
|
||||||
from quart_babel import format_datetime, format_decimal
|
from quart_babel import format_datetime, format_decimal, _
|
||||||
from quart.helpers import send_from_directory
|
from quart.helpers import send_from_directory
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from cnf.config import Config
|
from cnf.config import Config
|
||||||
|
from datetime import datetime
|
||||||
|
from os import DirEntry
|
||||||
import os
|
import os
|
||||||
|
from dateutil import tz
|
||||||
|
|
||||||
from . import web
|
from . import web
|
||||||
|
|
||||||
|
|
||||||
def _get_file(file):
|
def _get_birth_from_log(path: str) -> None | datetime:
|
||||||
|
'''read timestamp from the first line of a log file'''
|
||||||
|
dt = None
|
||||||
|
try:
|
||||||
|
with open(path) as f:
|
||||||
|
first_line = f.readline()
|
||||||
|
first_line = first_line.lstrip("'")
|
||||||
|
fmt = "%Y-%m-%d %H:%M:%S" if first_line[4] == '-' \
|
||||||
|
else "%d-%m-%Y %H:%M:%S"
|
||||||
|
dt = datetime.strptime(first_line[0:19], fmt). \
|
||||||
|
replace(tzinfo=tz.tzlocal())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return dt
|
||||||
|
|
||||||
|
|
||||||
|
def _get_file(file: DirEntry) -> dict:
|
||||||
'''build one row for the connection table'''
|
'''build one row for the connection table'''
|
||||||
entry = {}
|
entry = {}
|
||||||
entry['name'] = file.name
|
entry['name'] = file.name
|
||||||
stat = file.stat()
|
stat = file.stat()
|
||||||
entry['size'] = format_decimal(stat.st_size)
|
entry['size'] = format_decimal(stat.st_size)
|
||||||
entry['date'] = stat.st_mtime
|
try:
|
||||||
entry['created'] = format_datetime(stat.st_ctime, format="short")
|
dt = stat.st_birthtime
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
dt = _get_birth_from_log(file.path)
|
||||||
|
|
||||||
|
if dt:
|
||||||
|
entry['created'] = format_datetime(dt, format="short")
|
||||||
|
|
||||||
|
# sort by creating date, if available
|
||||||
|
entry['date'] = dt if isinstance(dt, float) else dt.timestamp()
|
||||||
|
else:
|
||||||
|
entry['created'] = _('n/a')
|
||||||
|
entry['date'] = stat.st_mtime
|
||||||
|
|
||||||
entry['modified'] = format_datetime(stat.st_mtime, format="short")
|
entry['modified'] = format_datetime(stat.st_mtime, format="short")
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
def get_list_data():
|
def get_list_data() -> list:
|
||||||
'''build the connection table'''
|
'''build the connection table'''
|
||||||
file_list = []
|
file_list = []
|
||||||
with os.scandir(Config.get_log_path()) as it:
|
with os.scandir(Config.get_log_path()) as it:
|
||||||
|
|||||||
@@ -46,10 +46,13 @@ def get_table_data():
|
|||||||
@web.route('/mqtt-fetch')
|
@web.route('/mqtt-fetch')
|
||||||
async def mqtt_fetch():
|
async def mqtt_fetch():
|
||||||
mqtt = Mqtt(None)
|
mqtt = Mqtt(None)
|
||||||
ctime = format_datetime(dt=mqtt.ctime, format='short')
|
cdatetime = format_datetime(dt=mqtt.ctime, format='d.MM. HH:mm')
|
||||||
data = {
|
data = {
|
||||||
"update-time": format_datetime(format="medium"),
|
"update-time": format_datetime(format="medium"),
|
||||||
"mqtt-ctime": f"<h3>{ctime}</h3>",
|
"mqtt-ctime": f"""
|
||||||
|
<h3 class="w3-hide-small w3-hide-medium">{cdatetime}</h3>
|
||||||
|
<h4 class="w3-hide-large">{cdatetime}</h4>
|
||||||
|
""",
|
||||||
"mqtt-tx": f"<h3>{mqtt.published}</h3>",
|
"mqtt-tx": f"<h3>{mqtt.published}</h3>",
|
||||||
"mqtt-rx": f"<h3>{mqtt.received}</h3>",
|
"mqtt-rx": f"<h3>{mqtt.received}</h3>",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
<div id="id01" class="w3-modal">
|
<div id="id01" class="w3-modal">
|
||||||
<div class="w3-modal-content" style="width:600px">
|
<div class="w3-modal-content" style="width:600px">
|
||||||
<div class="w3-container w3-padding-24">
|
<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>
|
<h2>{{_('Do you really want to delete the log file: <br>%(file)s ?', file='<b><span id="id03"></span></b>')}}</h2>
|
||||||
<div class="w3-bar">
|
<div class="w3-bar">
|
||||||
<button id="id02" class="w3-button w3-red" onclick="deleteFile(); document.getElementById('id01').style.display='none'">{{_('Delete File</button')}}>
|
<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>
|
<button class="w3-button w3-grey w3-right" onclick="document.getElementById('id01').style.display='none'">{{_('Abort')}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="w3-third">
|
<div class="w3-third">
|
||||||
<div class="w3-card-4">
|
<div class="w3-card-4">
|
||||||
<div class="w3-container w3-indigo w3-padding-16">
|
<div class="w3-container w3-indigo w3-padding-16">
|
||||||
<div class="w3-left"><i class="fa fa-link w3-xxxlarge"></i></div>
|
<div class="w3-left"><i class="fa fa-business-time w3-xxxlarge"></i></div>
|
||||||
<div id = "mqtt-ctime" class="w3-right">
|
<div id = "mqtt-ctime" class="w3-right">
|
||||||
<h3>-</h3>
|
<h3>-</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<div class="w3-third">
|
<div class="w3-third">
|
||||||
<div class="w3-card-4">
|
<div class="w3-card-4">
|
||||||
<div class="w3-container w3-purple w3-padding-16">
|
<div class="w3-container w3-purple w3-padding-16">
|
||||||
<div class="w3-left"><i class="fa fa-server w3-xxxlarge"></i></div>
|
<div class="w3-left"><i class="fa fa-angle-double-right w3-xxxlarge"></i></div>
|
||||||
<div id = "mqtt-tx" class="w3-right">
|
<div id = "mqtt-tx" class="w3-right">
|
||||||
<h3>-</h3>
|
<h3>-</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<div class="w3-third">
|
<div class="w3-third">
|
||||||
<div class="w3-card-4">
|
<div class="w3-card-4">
|
||||||
<div class="w3-container w3-orange w3-text-white w3-padding-16">
|
<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 class="w3-left"><i class="fa fa-angle-double-left w3-xxxlarge"></i></div>
|
||||||
<div id = "mqtt-rx" class="w3-right">
|
<div id = "mqtt-rx" class="w3-right">
|
||||||
<h3>-</h3>
|
<h3>-</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
0
app/tests/log/empty.txt
Normal file
0
app/tests/log/empty.txt
Normal file
@@ -9,6 +9,8 @@ from cnf.config import Config
|
|||||||
from mock import patch
|
from mock import patch
|
||||||
from proxy import Proxy
|
from proxy import Proxy
|
||||||
import os, errno
|
import os, errno
|
||||||
|
from os import DirEntry, stat_result
|
||||||
|
import datetime
|
||||||
|
|
||||||
pytest_plugins = ('pytest_asyncio',)
|
pytest_plugins = ('pytest_asyncio',)
|
||||||
|
|
||||||
@@ -201,14 +203,33 @@ async def test_notes_fetch(client, config_conn):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_file_fetch(client, config_conn):
|
async def test_file_fetch(client, config_conn, monkeypatch):
|
||||||
"""Test the data-fetch route."""
|
"""Test the data-fetch route."""
|
||||||
_ = config_conn
|
_ = config_conn
|
||||||
assert Config.log_path == 'app/tests/log/'
|
assert Config.log_path == 'app/tests/log/'
|
||||||
|
def my_stat1(*arg):
|
||||||
|
stat = stat_result
|
||||||
|
stat.st_size = 20
|
||||||
|
stat.st_birthtime = datetime.datetime(2024, 1, 31, 10, 30, 15)
|
||||||
|
stat.st_mtime = datetime.datetime(2024, 1, 1, 1, 30, 15).timestamp()
|
||||||
|
return stat
|
||||||
|
|
||||||
|
monkeypatch.setattr(DirEntry, "stat", my_stat1)
|
||||||
response = await client.get('/file-fetch')
|
response = await client.get('/file-fetch')
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def my_stat2(*arg):
|
||||||
|
stat = stat_result
|
||||||
|
stat.st_size = 20
|
||||||
|
stat.st_mtime = datetime.datetime(2024, 1, 1, 1, 30, 15).timestamp()
|
||||||
|
return stat
|
||||||
|
|
||||||
|
monkeypatch.setattr(DirEntry, "stat", my_stat2)
|
||||||
|
monkeypatch.delattr(stat_result, "st_birthtime")
|
||||||
|
response = await client.get('/file-fetch')
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_send_file(client, config_conn):
|
async def test_send_file(client, config_conn):
|
||||||
"""Test the send-file route."""
|
"""Test the send-file route."""
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: tsun-gen3-proxy 0.14.0\n"
|
"Project-Id-Version: tsun-gen3-proxy 0.14.0\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2025-05-04 18:16+0200\n"
|
"POT-Creation-Date: 2025-05-13 22:34+0200\n"
|
||||||
"PO-Revision-Date: 2025-04-18 16:24+0200\n"
|
"PO-Revision-Date: 2025-04-18 16:24+0200\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
@@ -19,30 +19,34 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: Babel 2.17.0\n"
|
"Generated-By: Babel 2.17.0\n"
|
||||||
|
|
||||||
#: src/web/conn_table.py:52 src/web/templates/base.html.j2:58
|
#: src/web/conn_table.py:53 src/web/templates/base.html.j2:58
|
||||||
msgid "Connections"
|
msgid "Connections"
|
||||||
msgstr "Verbindungen"
|
msgstr "Verbindungen"
|
||||||
|
|
||||||
#: src/web/conn_table.py:59
|
#: src/web/conn_table.py:60
|
||||||
msgid "Device-IP:Port"
|
msgid "Device-IP:Port"
|
||||||
msgstr "Geräte-IP:Port"
|
msgstr "Geräte-IP:Port"
|
||||||
|
|
||||||
#: src/web/conn_table.py:59
|
#: src/web/conn_table.py:60
|
||||||
msgid "Device-IP"
|
msgid "Device-IP"
|
||||||
msgstr "Geräte-IP"
|
msgstr "Geräte-IP"
|
||||||
|
|
||||||
#: src/web/conn_table.py:60 src/web/mqtt_table.py:34
|
#: src/web/conn_table.py:61 src/web/mqtt_table.py:34
|
||||||
msgid "Serial-No"
|
msgid "Serial-No"
|
||||||
msgstr "Seriennummer"
|
msgstr "Seriennummer"
|
||||||
|
|
||||||
#: src/web/conn_table.py:61
|
#: src/web/conn_table.py:62
|
||||||
msgid "Cloud-IP:Port"
|
msgid "Cloud-IP:Port"
|
||||||
msgstr "Cloud-IP:Port"
|
msgstr "Cloud-IP:Port"
|
||||||
|
|
||||||
#: src/web/conn_table.py:61
|
#: src/web/conn_table.py:62
|
||||||
msgid "Cloud-IP"
|
msgid "Cloud-IP"
|
||||||
msgstr "Cloud-IP"
|
msgstr "Cloud-IP"
|
||||||
|
|
||||||
|
#: src/web/log_files.py:48
|
||||||
|
msgid "n/a"
|
||||||
|
msgstr "keine Angabe"
|
||||||
|
|
||||||
#: src/web/mqtt_table.py:27
|
#: src/web/mqtt_table.py:27
|
||||||
msgid "MQTT devices"
|
msgid "MQTT devices"
|
||||||
msgstr "MQTT Geräte"
|
msgstr "MQTT Geräte"
|
||||||
@@ -116,12 +120,12 @@ msgid "TSUN Proxy - Log Files"
|
|||||||
msgstr "TSUN Proxy - Log Dateien"
|
msgstr "TSUN Proxy - Log Dateien"
|
||||||
|
|
||||||
#: src/web/templates/page_logging.html.j2:10
|
#: src/web/templates/page_logging.html.j2:10
|
||||||
msgid "Do you really want to delete the log file"
|
msgid "Do you really want to delete the log file: <br>%(file)s ?"
|
||||||
msgstr "Soll die Datei wirklich gelöscht werden"
|
msgstr "Soll die Datei: <br>%(file)s<br>wirklich gelöscht werden?"
|
||||||
|
|
||||||
#: src/web/templates/page_logging.html.j2:12
|
#: src/web/templates/page_logging.html.j2:12
|
||||||
msgid "Delete File</button"
|
msgid "Delete File"
|
||||||
msgstr "File löschen"
|
msgstr "Datei löschen"
|
||||||
|
|
||||||
#: src/web/templates/page_logging.html.j2:13
|
#: src/web/templates/page_logging.html.j2:13
|
||||||
msgid "Abort"
|
msgid "Abort"
|
||||||
|
|||||||
Reference in New Issue
Block a user