Compare commits
14 Commits
renovate/p
...
s-allius/i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0563200ce | ||
|
|
94f3b67cb2 | ||
|
|
a2da9d255f | ||
|
|
27ae3132c2 | ||
|
|
e74f9e0848 | ||
|
|
bb4640a623 | ||
|
|
78a7a7577d | ||
|
|
d7c003cb15 | ||
|
|
8db0f18c05 | ||
|
|
f3c4fdb093 | ||
|
|
59e8508f4c | ||
|
|
24950244c6 | ||
|
|
36dfdd3d41 | ||
|
|
035f96461a |
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [unreleased]
|
## [unreleased]
|
||||||
|
|
||||||
|
- Dashboard: add Log-File page
|
||||||
|
- Dashboard: add Connection page
|
||||||
- add web UI to add-on
|
- add web UI to add-on
|
||||||
- allow `Y00` serial numbers for GEN3PLUS devices
|
- allow `Y00` serial numbers for GEN3PLUS devices
|
||||||
|
|
||||||
|
|||||||
@@ -162,12 +162,13 @@ class Config():
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def init(cls, def_reader: ConfigIfc) -> None | str:
|
def init(cls, def_reader: ConfigIfc, log_path: str = '') -> None | str:
|
||||||
'''Initialise the Proxy-Config
|
'''Initialise the Proxy-Config
|
||||||
|
|
||||||
Copy the internal default config file into the config directory
|
Copy the internal default config file into the config directory
|
||||||
and initialise the Config with the default configuration '''
|
and initialise the Config with the default configuration '''
|
||||||
cls.err = None
|
cls.err = None
|
||||||
|
cls.log_path = log_path
|
||||||
cls.def_config = {}
|
cls.def_config = {}
|
||||||
try:
|
try:
|
||||||
# make the default config transparaent by copying it
|
# make the default config transparaent by copying it
|
||||||
@@ -247,3 +248,7 @@ here. The default config reader is handled in the Config.init method'''
|
|||||||
'''Check if the member is the default value'''
|
'''Check if the member is the default value'''
|
||||||
|
|
||||||
return cls.act_config.get(member) == cls.def_config.get(member)
|
return cls.act_config.get(member) == cls.def_config.get(member)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_log_path(cls) -> str:
|
||||||
|
return cls.log_path
|
||||||
|
|||||||
@@ -179,7 +179,8 @@ def main(): # pragma: no cover
|
|||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
# read config file
|
# read config file
|
||||||
Config.init(ConfigReadToml(src_dir + "cnf/default_config.toml"))
|
Config.init(ConfigReadToml(src_dir + "cnf/default_config.toml"),
|
||||||
|
log_path=args.log_path)
|
||||||
ConfigReadEnv()
|
ConfigReadEnv()
|
||||||
ConfigReadJson(args.config_path + "config.json")
|
ConfigReadJson(args.config_path + "config.json")
|
||||||
ConfigReadToml(args.config_path + "config.toml")
|
ConfigReadToml(args.config_path + "config.toml")
|
||||||
@@ -214,7 +215,7 @@ def main(): # pragma: no cover
|
|||||||
ProxyState.set_up(True)
|
ProxyState.set_up(True)
|
||||||
logging.info("Start Quart")
|
logging.info("Start Quart")
|
||||||
app.run(host='0.0.0.0', port=8127, use_reloader=False, loop=loop,
|
app.run(host='0.0.0.0', port=8127, use_reloader=False, loop=loop,
|
||||||
debug=True,)
|
debug=log_level == logging.DEBUG)
|
||||||
logging.info("Quart stopped")
|
logging.info("Quart stopped")
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ async def data_fetch():
|
|||||||
"proxy-cnt": f"<h3>{Infos.get_counter('ProxyMode_Cnt')}</h3>",
|
"proxy-cnt": f"<h3>{Infos.get_counter('ProxyMode_Cnt')}</h3>",
|
||||||
"emulation-cnt": f"<h3>{Infos.get_counter('EmuMode_Cnt')}</h3>",
|
"emulation-cnt": f"<h3>{Infos.get_counter('EmuMode_Cnt')}</h3>",
|
||||||
}
|
}
|
||||||
data["conn-table"] = await render_template('conn_table.html.j2',
|
data["conn-table"] = await render_template('templ_conn_table.html.j2',
|
||||||
table=get_table_data())
|
table=get_table_data())
|
||||||
|
|
||||||
data["notes-list"] = await render_template('notes_list.html.j2')
|
data["notes-list"] = await render_template('templ_notes_list.html.j2')
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -38,5 +38,8 @@ def utility_processor():
|
|||||||
async def set_language(language=None):
|
async def set_language(language=None):
|
||||||
if language in LANGUAGES:
|
if language in LANGUAGES:
|
||||||
session['language'] = language
|
session['language'] = language
|
||||||
return redirect('../#')
|
|
||||||
|
rsp = redirect(request.referrer if request.referrer else '../#')
|
||||||
|
rsp.content_language = language
|
||||||
|
return rsp
|
||||||
return abort(404)
|
return abort(404)
|
||||||
|
|||||||
52
app/src/web/log_files.py
Normal file
52
app/src/web/log_files.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from quart import render_template
|
||||||
|
from quart_babel import format_datetime, format_decimal
|
||||||
|
from quart.helpers import send_from_directory
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
from cnf.config import Config
|
||||||
|
import os
|
||||||
|
|
||||||
|
from . import web
|
||||||
|
|
||||||
|
|
||||||
|
def _get_file(file):
|
||||||
|
'''build one row for the connection table'''
|
||||||
|
entry = {}
|
||||||
|
entry['name'] = file.name
|
||||||
|
stat = file.stat()
|
||||||
|
entry['size'] = format_decimal(stat.st_size)
|
||||||
|
entry['date'] = stat.st_mtime
|
||||||
|
entry['created'] = format_datetime(stat.st_ctime, format="short")
|
||||||
|
entry['modified'] = format_datetime(stat.st_mtime, format="short")
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
def get_list_data():
|
||||||
|
'''build the connection table'''
|
||||||
|
file_list = []
|
||||||
|
with os.scandir(Config.get_log_path()) as it:
|
||||||
|
for entry in it:
|
||||||
|
if entry.is_file():
|
||||||
|
file_list.append(_get_file(entry))
|
||||||
|
|
||||||
|
file_list.sort(key=lambda x: x['date'], reverse=True)
|
||||||
|
return file_list
|
||||||
|
|
||||||
|
|
||||||
|
@web.route('/file-fetch')
|
||||||
|
async def file_fetch():
|
||||||
|
data = {
|
||||||
|
"update-time": format_datetime(format="medium"),
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@web.route('/send-file/<file>')
|
||||||
|
async def send(file):
|
||||||
|
return await send_from_directory(
|
||||||
|
directory=Config.get_log_path(),
|
||||||
|
file_name=secure_filename(file),
|
||||||
|
as_attachment=True)
|
||||||
@@ -7,10 +7,17 @@ from . import web
|
|||||||
@web.route('/')
|
@web.route('/')
|
||||||
async def index():
|
async def index():
|
||||||
return await render_template(
|
return await render_template(
|
||||||
'index.html.j2',
|
'page_index.html.j2',
|
||||||
fetch_url=url_for('web.data_fetch'))
|
fetch_url=url_for('.data_fetch'))
|
||||||
|
|
||||||
|
|
||||||
@web.route('/page')
|
@web.route('/page')
|
||||||
async def empty():
|
async def empty():
|
||||||
return await render_template('empty.html.j2')
|
return await render_template('empty.html.j2')
|
||||||
|
|
||||||
|
|
||||||
|
@web.route('/logging')
|
||||||
|
async def logging():
|
||||||
|
return await render_template(
|
||||||
|
'page_logging.html.j2',
|
||||||
|
fetch_url=url_for('.file_fetch'))
|
||||||
|
|||||||
@@ -55,16 +55,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w3-bar-block">
|
<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>
|
<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('web.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('.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('web.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('.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('web.empty')}}" class="w3-bar-item w3-button w3-padding"><i class="fa fa-file-export fa-fw {% block menu3_class %}{% endblock %}"></i> Downloads</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>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
<!-- Overlay effect when opening sidebar on small screens -->
|
<!-- Overlay effect when opening sidebar on small screens -->
|
||||||
<button class="w3-overlay w3-hide-large w3-animate-opacity" onclick="w3_close()" style="cursor:pointer" title="close side menu" id="myOverlay"></button>
|
<button class="w3-overlay w3-hide-large w3-animate-opacity" onclick="w3_close()" style="cursor:pointer" title="close side menu" id="myOverlay"></button>
|
||||||
|
|
||||||
<!-- !PAGE CONTENT! -->
|
<!-- !PAGE CONTENT! -->
|
||||||
<div class="w3-main" style="margin-left:250px;margin-top:43px;">
|
<div class="w3-main" style="margin-left:250px;margin-top:43px;">
|
||||||
|
|
||||||
|
|||||||
11
app/src/web/templates/page_logging.html.j2
Normal file
11
app/src/web/templates/page_logging.html.j2
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
{% 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 class="w3-container" id="file-list"></div>
|
||||||
|
{% endblock content%}
|
||||||
|
|
||||||
|
{% block footer %}{% endblock footer %}
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{%- endmacro%}
|
{%- endmacro%}
|
||||||
|
|
||||||
<h5>Connections</h5>
|
<h5>{{_('Connections')}}</h5>
|
||||||
<table class="w3-table w3-striped w3-bordered w3-border w3-hoverable w3-white">
|
<table class="w3-table w3-striped w3-bordered w3-border w3-hoverable w3-white">
|
||||||
{% if table.thead is defined%}
|
{% if table.thead is defined%}
|
||||||
<thead>
|
<thead>
|
||||||
31
app/src/web/templates/templ_log_files_list.html.j2
Normal file
31
app/src/web/templates/templ_log_files_list.html.j2
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<div class="w3-row-padding w3-margin-bottom">
|
||||||
|
{% for file in dir_list %}
|
||||||
|
<div class="w3-quarter w3-margin-bottom">
|
||||||
|
|
||||||
|
<div class="w3-card-4">
|
||||||
|
<header class="w3-container w3-blue">
|
||||||
|
<h4>{{file.name}}</h4>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<table class="w3-table">
|
||||||
|
{% for idx, name in [('created',_('Created')), ('modified', _('Modified')), ('size', _('Size'))]%}
|
||||||
|
<tr>
|
||||||
|
<td>{{_(name)}}:</td>
|
||||||
|
<td>{{file[idx]}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% if 0 == (loop.index%4) and not last %}
|
||||||
|
</div>
|
||||||
|
<div class="w3-row-padding w3-margin-bottom">
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
0
app/tests/log/sub_dir/not_reachable.txt
Normal file
0
app/tests/log/sub_dir/not_reachable.txt
Normal file
19
app/tests/log/test.txt
Normal file
19
app/tests/log/test.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
2025-04-30 00:01:23 INFO | root | Server "proxy - unknown" will be started
|
||||||
|
2025-04-30 00:01:23 INFO | root | current dir: /Users/sallius/tsun/tsun-gen3-proxy
|
||||||
|
2025-04-30 00:01:23 INFO | root | config_path: ./config/
|
||||||
|
2025-04-30 00:01:23 INFO | root | json_config: None
|
||||||
|
2025-04-30 00:01:23 INFO | root | toml_config: None
|
||||||
|
2025-04-30 00:01:23 INFO | root | trans_path: ../translations/
|
||||||
|
2025-04-30 00:01:23 INFO | root | rel_urls: False
|
||||||
|
2025-04-30 00:01:23 INFO | root | log_path: ./log/
|
||||||
|
2025-04-30 00:01:23 INFO | root | log_backups: unlimited
|
||||||
|
2025-04-30 00:01:23 INFO | root | LOG_LVL : None
|
||||||
|
2025-04-30 00:01:23 INFO | root | ******
|
||||||
|
2025-04-30 00:01:23 INFO | root | Read from /Users/sallius/tsun/tsun-gen3-proxy/app/src/cnf/default_config.toml => ok
|
||||||
|
2025-04-30 00:01:23 INFO | root | Read from environment => ok
|
||||||
|
2025-04-30 00:01:23 INFO | root | Read from ./config/config.json => n/a
|
||||||
|
2025-04-30 00:01:23 INFO | root | Read from ./config/config.toml => n/a
|
||||||
|
2025-04-30 00:01:23 INFO | root | ******
|
||||||
|
2025-04-30 00:01:23 INFO | root | listen on port: 5005 for inverters
|
||||||
|
2025-04-30 00:01:23 INFO | root | listen on port: 10000 for inverters
|
||||||
|
2025-04-30 00:01:23 INFO | root | Start Quart
|
||||||
@@ -37,6 +37,7 @@ def config_conn():
|
|||||||
},
|
},
|
||||||
'solarman':{'enabled': True, 'host': 'test_cloud.local', 'port': 1234}, 'inverters':{'allow_all':True}
|
'solarman':{'enabled': True, 'host': 'test_cloud.local', 'port': 1234}, 'inverters':{'allow_all':True}
|
||||||
}
|
}
|
||||||
|
Config.log_path='app/tests/log/'
|
||||||
|
|
||||||
@pytest.fixture(scope="module", autouse=True)
|
@pytest.fixture(scope="module", autouse=True)
|
||||||
def module_init():
|
def module_init():
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from web import Web, web
|
|||||||
from async_stream import AsyncStreamClient
|
from async_stream import AsyncStreamClient
|
||||||
from gen3plus.inverter_g3p import InverterG3P
|
from gen3plus.inverter_g3p import InverterG3P
|
||||||
from test_inverter_g3p import FakeReader, FakeWriter, config_conn
|
from test_inverter_g3p import FakeReader, FakeWriter, config_conn
|
||||||
|
from cnf.config import Config
|
||||||
|
|
||||||
pytest_plugins = ('pytest_asyncio',)
|
pytest_plugins = ('pytest_asyncio',)
|
||||||
|
|
||||||
@@ -64,6 +65,13 @@ async def test_rel_page(client):
|
|||||||
assert response.mimetype == 'text/html'
|
assert response.mimetype == 'text/html'
|
||||||
web.build_relative_urls = False
|
web.build_relative_urls = False
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_logging(client):
|
||||||
|
"""Test the logging page route."""
|
||||||
|
response = await client.get('/logging')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.mimetype == 'text/html'
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_favicon96(client):
|
async def test_favicon96(client):
|
||||||
"""Test the favicon-96x96.png route."""
|
"""Test the favicon-96x96.png route."""
|
||||||
@@ -101,7 +109,7 @@ async def test_manifest(client):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_data_fetch(create_inverter):
|
async def test_data_fetch(create_inverter):
|
||||||
"""Test the healthy route."""
|
"""Test the data-fetch route."""
|
||||||
_ = create_inverter
|
_ = create_inverter
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
response = await client.get('/data-fetch')
|
response = await client.get('/data-fetch')
|
||||||
@@ -112,7 +120,7 @@ async def test_data_fetch(create_inverter):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_data_fetch1(create_inverter_server):
|
async def test_data_fetch1(create_inverter_server):
|
||||||
"""Test the healthy route."""
|
"""Test the data-fetch route with server connection."""
|
||||||
_ = create_inverter_server
|
_ = create_inverter_server
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
response = await client.get('/data-fetch')
|
response = await client.get('/data-fetch')
|
||||||
@@ -123,7 +131,7 @@ async def test_data_fetch1(create_inverter_server):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_data_fetch2(create_inverter_client):
|
async def test_data_fetch2(create_inverter_client):
|
||||||
"""Test the healthy route."""
|
"""Test the data-fetch route with client connection."""
|
||||||
_ = create_inverter_client
|
_ = create_inverter_client
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
response = await client.get('/data-fetch')
|
response = await client.get('/data-fetch')
|
||||||
@@ -134,9 +142,11 @@ async def test_data_fetch2(create_inverter_client):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_language_en(client):
|
async def test_language_en(client):
|
||||||
"""Test the language/en route."""
|
"""Test the language/en route and cookie."""
|
||||||
response = await client.get('/language/en')
|
response = await client.get('/language/en', headers={'referer': '/index'})
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
|
assert response.content_language.pop() == 'en'
|
||||||
|
assert response.location == '/index'
|
||||||
assert response.mimetype == 'text/html'
|
assert response.mimetype == 'text/html'
|
||||||
|
|
||||||
client.set_cookie('test', key='language', value='de')
|
client.set_cookie('test', key='language', value='de')
|
||||||
@@ -146,14 +156,53 @@ async def test_language_en(client):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_language_de(client):
|
async def test_language_de(client):
|
||||||
"""Test the language/en route."""
|
"""Test the language/de route."""
|
||||||
response = await client.get('/language/de')
|
response = await client.get('/language/de', headers={'referer': '/'})
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
|
assert response.content_language.pop() == 'de'
|
||||||
|
assert response.location == '/'
|
||||||
assert response.mimetype == 'text/html'
|
assert response.mimetype == 'text/html'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_language_unknown(client):
|
async def test_language_unknown(client):
|
||||||
"""Test the language/en route."""
|
"""Test the language/unknown route."""
|
||||||
response = await client.get('/language/unknonw')
|
response = await client.get('/language/unknown')
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
assert response.mimetype == 'text/html'
|
assert response.mimetype == 'text/html'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_file_fetch(client, config_conn):
|
||||||
|
"""Test the data-fetch route."""
|
||||||
|
_ = config_conn
|
||||||
|
assert Config.log_path == 'app/tests/log/'
|
||||||
|
response = await client.get('/file-fetch')
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_send_file(client, config_conn):
|
||||||
|
"""Test the send-file route."""
|
||||||
|
_ = config_conn
|
||||||
|
assert Config.log_path == 'app/tests/log/'
|
||||||
|
response = await client.get('/send-file/test.txt')
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_missing_send_file(client, config_conn):
|
||||||
|
"""Test the send-file route (file not found)."""
|
||||||
|
_ = config_conn
|
||||||
|
assert Config.log_path == 'app/tests/log/'
|
||||||
|
response = await client.get('/send-file/no_file.log')
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_invalid_send_file(client, config_conn):
|
||||||
|
"""Test the send-file route (invalid filename)."""
|
||||||
|
_ = config_conn
|
||||||
|
assert Config.log_path == 'app/tests/log/'
|
||||||
|
response = await client.get('/send-file/../test_web_route.py')
|
||||||
|
assert response.status_code == 404
|
||||||
|
|||||||
@@ -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-04-28 21:00+0200\n"
|
"POT-Creation-Date: 2025-05-01 17:48+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"
|
||||||
@@ -44,42 +44,64 @@ msgid "Updated:"
|
|||||||
msgstr "Aktualisiert:"
|
msgstr "Aktualisiert:"
|
||||||
|
|
||||||
#: src/web/templates/base.html.j2:58
|
#: src/web/templates/base.html.j2:58
|
||||||
|
#: src/web/templates/templ_conn_table.html.j2:15
|
||||||
msgid "Connections"
|
msgid "Connections"
|
||||||
msgstr "Verbindungen"
|
msgstr "Verbindungen"
|
||||||
|
|
||||||
#: src/web/templates/index.html.j2:5
|
#: src/web/templates/base.html.j2:60 src/web/templates/page_logging.html.j2:5
|
||||||
|
msgid "Log Files"
|
||||||
|
msgstr "Log Dateien"
|
||||||
|
|
||||||
|
#: src/web/templates/page_index.html.j2:5
|
||||||
msgid "Proxy Connection Overview"
|
msgid "Proxy Connection Overview"
|
||||||
msgstr "Proxy Verbindungen"
|
msgstr "Proxy Verbindungen"
|
||||||
|
|
||||||
#: src/web/templates/index.html.j2:16
|
#: src/web/templates/page_index.html.j2:16
|
||||||
msgid "Server Mode"
|
msgid "Server Mode"
|
||||||
msgstr "Server Modus"
|
msgstr "Server Modus"
|
||||||
|
|
||||||
#: src/web/templates/index.html.j2:17
|
#: src/web/templates/page_index.html.j2:17
|
||||||
msgid "Established from device to proxy"
|
msgid "Established from device to proxy"
|
||||||
msgstr "Vom Gerät zum Proxy aufgebaut"
|
msgstr "Vom Gerät zum Proxy aufgebaut"
|
||||||
|
|
||||||
#: src/web/templates/index.html.j2:27
|
#: src/web/templates/page_index.html.j2:27
|
||||||
msgid "Client Mode"
|
msgid "Client Mode"
|
||||||
msgstr "Client Modus"
|
msgstr "Client Modus"
|
||||||
|
|
||||||
#: src/web/templates/index.html.j2:28
|
#: src/web/templates/page_index.html.j2:28
|
||||||
msgid "Established from proxy to device"
|
msgid "Established from proxy to device"
|
||||||
msgstr "Vom Proxy zum Gerät aufgebaut"
|
msgstr "Vom Proxy zum Gerät aufgebaut"
|
||||||
|
|
||||||
#: src/web/templates/index.html.j2:38
|
#: src/web/templates/page_index.html.j2:38
|
||||||
msgid "Proxy Mode"
|
msgid "Proxy Mode"
|
||||||
msgstr "Proxy Modus"
|
msgstr "Proxy Modus"
|
||||||
|
|
||||||
#: src/web/templates/index.html.j2:39
|
#: src/web/templates/page_index.html.j2:39
|
||||||
msgid "Forwarding data to cloud"
|
msgid "Forwarding data to cloud"
|
||||||
msgstr "Weiterleitung in die Cloud"
|
msgstr "Weiterleitung in die Cloud"
|
||||||
|
|
||||||
#: src/web/templates/index.html.j2:49
|
#: src/web/templates/page_index.html.j2:49
|
||||||
msgid "Emu Mode"
|
msgid "Emu Mode"
|
||||||
msgstr "Emu Modus"
|
msgstr "Emu Modus"
|
||||||
|
|
||||||
#: src/web/templates/index.html.j2:50
|
#: src/web/templates/page_index.html.j2:50
|
||||||
msgid "Emulation sends data to cloud"
|
msgid "Emulation sends data to cloud"
|
||||||
msgstr "Emulation sendet in die Cloud"
|
msgstr "Emulation sendet in die Cloud"
|
||||||
|
|
||||||
|
#: src/web/templates/templ_log_files_list.html.j2:11
|
||||||
|
msgid "Created"
|
||||||
|
msgstr "Erzeugt"
|
||||||
|
|
||||||
|
#: src/web/templates/templ_log_files_list.html.j2:11
|
||||||
|
msgid "Modified"
|
||||||
|
msgstr "Modifiziert"
|
||||||
|
|
||||||
|
#: src/web/templates/templ_log_files_list.html.j2:11
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "Größe"
|
||||||
|
|
||||||
|
#: src/web/templates/templ_log_files_list.html.j2:20
|
||||||
|
msgid "Download File"
|
||||||
|
msgstr "Datei Download"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user