build a web module for the dashboard

- load all python module from local dir
- initialize Blueprint and Babel
This commit is contained in:
Stefan Allius
2025-04-27 12:19:33 +02:00
parent 5e0aea3364
commit da04e700c7
6 changed files with 85 additions and 43 deletions

View File

@@ -5,8 +5,6 @@ import os
import argparse
from asyncio import StreamReader, StreamWriter
from quart import Quart, Response
from quart_babel import Babel
from quart_babel.locale import get_locale
from logging import config # noqa F401
from proxy import Proxy
from inverter_ifc import InverterIfc
@@ -17,8 +15,7 @@ from cnf.config import Config
from cnf.config_read_env import ConfigReadEnv
from cnf.config_read_toml import ConfigReadToml
from cnf.config_read_json import ConfigReadJson
from web.routes import web_routes
from web.i18n import i18n_routes, my_get_locale, LANGUAGES
from web import Web
from modbus_tcp import ModbusTcp
@@ -34,27 +31,11 @@ class ProxyState:
ProxyState._is_up = value
def my_get_tz():
return 'CET'
app = Quart(__name__,
template_folder='web/templates',
static_folder='web/static')
babel = Babel(app,
locale_selector=my_get_locale,
timezone_selector=my_get_tz,
default_translation_directories='../translations')
app.register_blueprint(web_routes)
app.register_blueprint(i18n_routes)
app.secret_key = 'super secret key'
@app.context_processor
def utility_processor():
return dict(lang=get_locale(),
lang_str=LANGUAGES.get(str(get_locale()), "English"),
languages=LANGUAGES)
Web(app, '../translations')
app.secret_key = 'super secret key' # fixme define a secret
@app.route('/-/ready')

25
app/src/utils/__init__.py Normal file
View File

@@ -0,0 +1,25 @@
import mimetypes
from importlib import import_module
from pathlib import Path
from collections.abc import Callable
class SourceFileLoader:
""" Represents a SouceFileLoader (__loader__)"""
name: str
get_resource_reader: Callable
def load_modules(loader: SourceFileLoader):
"""Load the entire modules from a SourceFileLoader (__loader__)"""
pkg = loader.name
for load in loader.get_resource_reader().contents():
if "python" not in str(mimetypes.guess_type(load)[0]):
continue
mod = Path(load).stem
if mod == "__init__":
continue
import_module(pkg + "." + mod, pkg)

28
app/src/web/__init__.py Normal file
View File

@@ -0,0 +1,28 @@
'''Quart blueprint for the proxy webserver with the dashboard
Usage:
app = Quart(__name__, ...)
Web(app)
'''
from quart import Quart, Blueprint
from quart_babel import Babel
from utils import load_modules
web = Blueprint('web', __name__)
load_modules(__loader__)
class Web:
'''Helper Class to register the Blueprint at Quart and
initializing Babel'''
def __init__(self, app: Quart, translation_directories: str | list[str]):
app.register_blueprint(web)
from .i18n import get_locale, get_tz
global babel
babel = Babel(
app,
locale_selector=get_locale,
timezone_selector=get_tz,
default_translation_directories=translation_directories)

View File

@@ -1,7 +1,8 @@
from quart import Blueprint
from quart import request, session, redirect
from quart_babel import _
from quart_babel.locale import get_locale as babel_get_locale
from . import web
LANGUAGES = {
'en': _('English'),
@@ -9,10 +10,8 @@ LANGUAGES = {
'fr': _('French')
}
i18n_routes = Blueprint('i18n_routes', __name__)
def my_get_locale():
def get_locale():
try:
language = session['language']
except KeyError:
@@ -25,7 +24,18 @@ def my_get_locale():
return request.accept_languages.best_match(LANGUAGES.keys())
@i18n_routes.route('/language/<language>')
def get_tz():
return 'CET'
@web.context_processor
def utility_processor():
return dict(lang=babel_get_locale(),
lang_str=LANGUAGES.get(str(babel_get_locale()), "English"),
languages=LANGUAGES)
@web.route('/language/<language>')
def set_language(language=None):
if language in LANGUAGES:
session['language'] = language

View File

@@ -1,34 +1,32 @@
from quart import Blueprint
from quart import render_template, url_for
from quart import send_from_directory
from quart_babel import format_datetime
from infos import Infos
from web.conn_table import get_table_data
from . import web
import os
web_routes = Blueprint('web_routes', __name__)
async def get_icon(file: str, mime: str = 'image/png'):
return await send_from_directory(
os.path.join(web_routes.root_path, 'static/images'),
os.path.join(web.root_path, 'static/images'),
file,
mimetype=mime)
@web_routes.route('/')
@web.route('/')
async def index():
return await render_template(
'index.html.j2',
fetch_url='.'+url_for('web_routes.data_fetch'))
fetch_url='.'+url_for('web.data_fetch'))
@web_routes.route('/page')
@web.route('/page')
async def empty():
return await render_template('empty.html.j2')
@web_routes.route('/data-fetch')
@web.route('/data-fetch')
async def data_fetch():
data = {
"update-time": format_datetime(format="medium"),
@@ -44,26 +42,26 @@ async def data_fetch():
return data
@web_routes.route('/favicon-96x96.png')
@web.route('/favicon-96x96.png')
async def favicon():
return await get_icon('favicon-96x96.png')
@web_routes.route('/favicon.ico')
@web.route('/favicon.ico')
async def favicon_ico():
return await get_icon('favicon.ico', 'image/x-icon')
@web_routes.route('/favicon.svg')
@web.route('/favicon.svg')
async def favicon_svg():
return await get_icon('favicon.svg', 'image/svg+xml')
@web_routes.route('/apple-touch-icon.png')
@web.route('/apple-touch-icon.png')
async def apple_touch():
return await get_icon('apple-touch-icon.png')
@web_routes.route('/site.webmanifest')
@web.route('/site.webmanifest')
async def webmanifest():
return await get_icon('site.webmanifest', 'application/manifest+json')

View File

@@ -55,9 +55,9 @@
</div>
<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('web_routes.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_routes.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_routes.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('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('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('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>
</div>
</nav>