Compare commits

...

9 Commits

Author SHA1 Message Date
Stefan Allius
ab1b245e2a update favicon.svg 2025-04-20 01:28:04 +02:00
Stefan Allius
2430aef541 remove unusage statements 2025-04-20 00:33:28 +02:00
Stefan Allius
509d26471f increase test coverage 2025-04-20 00:30:59 +02:00
Stefan Allius
e5a08c3150 fix route for fetch data
- for running in iframes (e.g. HA ingress) we must
  use relative path in the URLs
2025-04-20 00:07:34 +02:00
Stefan Allius
d386456a88 change file extension to html.j2 for templates 2025-04-20 00:06:34 +02:00
Stefan Allius
a527926e57 change file extension to html.j2 for templates 2025-04-19 21:26:15 +02:00
Stefan Allius
fe915f524d change file extension to html.j2 for templates 2025-04-19 21:23:34 +02:00
Stefan Allius
55972b2240 add optional java scriot to fetch data regullary 2025-04-19 21:12:11 +02:00
Stefan Allius
c270edff15 S allius/issue385 (#386)
* ignore translation and log files

* add quart-babel

* build and install translation files

* don't export the web-ui port 8127

- for security reason the user should use the
  HA ingress and not the direkt access to the web
  dashboard

* set 'lang' in html tag
2025-04-19 16:51:06 +02:00
17 changed files with 175 additions and 26 deletions

4
.gitignore vendored
View File

@@ -13,3 +13,7 @@ Doku/**
.env .env
.venv .venv
coverage.xml coverage.xml
*.pot
*.mo
*.log
*.log.*

View File

@@ -1,12 +1,13 @@
.PHONY: build clean addon-dev addon-debug addon-rc addon-rel debug dev preview rc rel check-docker-compose install .PHONY: build babel clean addon-dev addon-debug addon-rc addon-rel debug dev preview rc rel check-docker-compose install
debug dev preview rc rel: babel debug dev preview rc rel:
$(MAKE) -C app $@ $(MAKE) -C app $@
clean build: clean build:
$(MAKE) -C ha_addons $@ $(MAKE) -C ha_addons $@
addon-dev addon-debug addon-rc addon-rel: addon-dev addon-debug addon-rc addon-rel:
$(MAKE) -C app babel
$(MAKE) -C ha_addons $(patsubst addon-%,%,$@) $(MAKE) -C ha_addons $(patsubst addon-%,%,$@)
check-docker-compose: check-docker-compose:

View File

@@ -3,3 +3,5 @@ tests/
*.pyc *.pyc
.DS_Store .DS_Store
build.sh build.sh
*.pot
*.po

View File

@@ -6,10 +6,16 @@ IMAGE = tsun-gen3-proxy
# Folders # Folders
SRC=. APP=.
SRC=$(APP)/src
# Folders for Babel translation
BABEL_INPUT_JINJA=$(SRC)/web/templates
BABEL_INPUT= $(foreach dir,$(BABEL_INPUT_JINJA),$(wildcard $(dir)/*.html.j2)) \
BABEL_TRANSLATIONS=$(APP)/translations
export BUILD_DATE := ${shell date -Iminutes} export BUILD_DATE := ${shell date -Iminutes}
VERSION := $(shell cat $(SRC)/.version) VERSION := $(shell cat $(APP)/.version)
export MAJOR := $(shell echo $(VERSION) | cut -f1 -d.) export MAJOR := $(shell echo $(VERSION) | cut -f1 -d.)
PUBLIC_URL := $(shell echo $(PUBLIC_CONTAINER_REGISTRY) | cut -f1 -d/) PUBLIC_URL := $(shell echo $(PUBLIC_CONTAINER_REGISTRY) | cut -f1 -d/)
@@ -39,5 +45,17 @@ preview rel:
export IMAGE=$(PUBLIC_CONTAINER_REGISTRY)$(IMAGE) && \ export IMAGE=$(PUBLIC_CONTAINER_REGISTRY)$(IMAGE) && \
docker buildx bake -f docker-bake.hcl $@ docker buildx bake -f docker-bake.hcl $@
babel: $(BABEL_TRANSLATIONS)/de/LC_MESSAGES/messages.mo $(BABEL_TRANSLATIONS)/de/LC_MESSAGES/messages.po $(BABEL_TRANSLATIONS)/messages.pot
.PHONY: debug dev preview rc rel $(BABEL_TRANSLATIONS)/%.pot : $(SRC)/.babel.cfg $(BABEL_INPUT)
@mkdir -p $(@D)
@pybabel extract -F $< --project=$(IMAGE) --version=$(VERSION) -o $@ $(SRC)
$(BABEL_TRANSLATIONS)/%/LC_MESSAGES/messages.po : $(BABEL_TRANSLATIONS)/messages.pot
@mkdir -p $(@D)
@pybabel update --init-missing -i $< -d $(BABEL_TRANSLATIONS) -l $*
$(BABEL_TRANSLATIONS)/%/LC_MESSAGES/messages.mo : $(BABEL_TRANSLATIONS)/%/LC_MESSAGES/messages.po
@pybabel compile -d $(BABEL_TRANSLATIONS) -l $*
.PHONY: babel debug dev preview rc rel

View File

@@ -2,3 +2,4 @@
schema==0.7.7 schema==0.7.7
aiocron==2.1 aiocron==2.1
quart==0.20 quart==0.20
quart-babel==1.0.7

3
app/src/.babel.cfg Normal file
View File

@@ -0,0 +1,3 @@
[python: **.py]
[jinja2: web/templates/**.html]
[jinja2: web/templates/**.html.j2]

View File

@@ -4,7 +4,9 @@ import logging.handlers
import os import os
import argparse import argparse
from asyncio import StreamReader, StreamWriter from asyncio import StreamReader, StreamWriter
from quart import Quart, Response from quart import Quart, Response, request
from quart_babel import Babel
from quart_babel.locale import get_locale
from logging import config # noqa F401 from logging import config # noqa F401
from proxy import Proxy from proxy import Proxy
from inverter_ifc import InverterIfc from inverter_ifc import InverterIfc
@@ -31,12 +33,28 @@ class ProxyState:
ProxyState._is_up = value ProxyState._is_up = value
def my_get_locale():
# check how to get the locale form for the add-on - hass.selectedLanguage
# logging.info("get_locale(%s)", request.accept_languages)
return request.accept_languages.best_match(
['de', 'en']
)
app = Quart(__name__, app = Quart(__name__,
template_folder='web/templates', template_folder='web/templates',
static_folder='web/static') static_folder='web/static')
babel = Babel(app,
locale_selector=my_get_locale,
default_translation_directories='../translations')
app.register_blueprint(web_routes) app.register_blueprint(web_routes)
@app.context_processor
def utility_processor():
return dict(lang=get_locale())
@app.route('/-/ready') @app.route('/-/ready')
async def ready(): async def ready():
if ProxyState.is_up(): if ProxyState.is_up():

View File

@@ -1,5 +1,5 @@
from quart import Blueprint from quart import Blueprint
from quart import render_template from quart import render_template, url_for
from quart import send_from_directory from quart import send_from_directory
import os import os
@@ -13,14 +13,44 @@ async def get_icon(file: str, mime: str = 'image/png'):
mimetype=mime) mimetype=mime)
def get_inv_count():
return 1234
TsunCnt = 0
def get_tsun_count():
global TsunCnt
TsunCnt += 1
return TsunCnt
@web_routes.context_processor
def utility_processor():
return dict(inv_count=get_inv_count(),
tsun_count=get_tsun_count())
@web_routes.route('/') @web_routes.route('/')
async def index(): async def index():
return await render_template('index.html') return await render_template(
'index.html.j2',
fetch_url='.'+url_for('web_routes.data_fetch'))
@web_routes.route('/page') @web_routes.route('/page')
async def empty(): async def empty():
return await render_template('empty.html') return await render_template('empty.html.j2')
@web_routes.route('/data-fetch')
async def data_fetch():
global TsunCnt
TsunCnt += 1
return {
"geology-fact": f"<h3>{TsunCnt}</h3>",
}
@web_routes.route('/favicon-96x96.png') @web_routes.route('/favicon-96x96.png')

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 729 KiB

View File

@@ -1,6 +1,6 @@
{ {
"name": "TSUN-Proxy", "name": "TSUN-Proxy",
"short_name": "TsunProxy", "short_name": "Proxy",
"icons": [ "icons": [
{ {
"src": "/web-app-manifest-192x192.png", "src": "/web-app-manifest-192x192.png",

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="{{lang}}" >
<head> <head>
<title>{% block title %}{% endblock title %}</title> <title>{% block title %}{% endblock title %}</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
@@ -106,6 +106,36 @@
mySidebar.style.display = "none"; mySidebar.style.display = "none";
overlayBg.style.display = "none"; overlayBg.style.display = "none";
} }
{% if fetch_url is defined %}
function fetch_data() {
fetch("{{fetch_url}}")
.then(response => response.json())
.then(function (data) {
Object.keys(data).forEach(key => {
//console.log(`${key}: ${data[key]}`);
try {
elm = document.getElementById(key)
elm.innerHTML = data[key]
}
catch(err) {
console.log('error: ' + err + ' (for key: ' + key + ')');
}
});
})
.catch(function (err) {
console.log('error: ' + err);
});
}
window.addEventListener('load', function () {
// Your document is loaded.
var fetchInterval = 5000; // 5 seconds.
// Invoke the request every 5 seconds.
setInterval(fetch_data, fetchInterval);
});
{% endif %}
</script> </script>
{% endblock trailer %} {% endblock trailer %}

View File

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

View File

@@ -1,8 +1,8 @@
{% extends 'base.html' %} {% extends 'base.html.j2' %}
{% block title %} TSUN Proxy - Dashboard {% endblock title%} {% block title %} TSUN Proxy - Dashboard {% endblock title%}
{% block menu1_class %}w3-blue{% endblock %} {% block menu1_class %}w3-blue{% endblock %}
{% block headline %}<i class="fa fa-dashboard"></i> My Dashboard{% endblock headline %} {% block headline %}<i class="fa fa-dashboard"></i> {{_('My Dashboard')}}{% endblock headline %}
{% block content %} {% block content %}
<div class="w3-row-padding w3-margin-bottom"> <div class="w3-row-padding w3-margin-bottom">
@@ -10,7 +10,7 @@
<div class="w3-container w3-red w3-padding-16"> <div class="w3-container w3-red w3-padding-16">
<div class="w3-left"><i class="fa fa-comment w3-xxxlarge"></i></div> <div class="w3-left"><i class="fa fa-comment w3-xxxlarge"></i></div>
<div class="w3-right"> <div class="w3-right">
<h3>52</h3> <h3>{{inv_count}}</h3>
</div> </div>
<div class="w3-clear"></div> <div class="w3-clear"></div>
<h4>Messages</h4> <h4>Messages</h4>
@@ -20,7 +20,7 @@
<div class="w3-container w3-blue w3-padding-16"> <div class="w3-container w3-blue w3-padding-16">
<div class="w3-left"><i class="fa fa-eye w3-xxxlarge"></i></div> <div class="w3-left"><i class="fa fa-eye w3-xxxlarge"></i></div>
<div class="w3-right"> <div class="w3-right">
<h3>99</h3> <h3>{{tsun_count}}</h3>
</div> </div>
<div class="w3-clear"></div> <div class="w3-clear"></div>
<h4>Views</h4> <h4>Views</h4>
@@ -29,7 +29,7 @@
<div class="w3-quarter"> <div class="w3-quarter">
<div class="w3-container w3-teal w3-padding-16"> <div class="w3-container w3-teal w3-padding-16">
<div class="w3-left"><i class="fa fa-share-alt w3-xxxlarge"></i></div> <div class="w3-left"><i class="fa fa-share-alt w3-xxxlarge"></i></div>
<div class="w3-right"> <div id = "geology-fact" class="w3-right">
<h3>23</h3> <h3>23</h3>
</div> </div>
<div class="w3-clear"></div> <div class="w3-clear"></div>
@@ -52,7 +52,6 @@
<div class="w3-row-padding" style="margin:0 -16px"> <div class="w3-row-padding" style="margin:0 -16px">
<div class="w3-third"> <div class="w3-third">
<h5>Regions</h5> <h5>Regions</h5>
<img src="/w3images/region.jpg" style="width:100%" alt="Google Regional Map">
</div> </div>
<div class="w3-twothird"> <div class="w3-twothird">
<h5>Feeds</h5> <h5>Feeds</h5>
@@ -178,5 +177,3 @@
</div> </div>
</div> </div>
{% endblock content%} {% endblock content%}

View File

@@ -61,3 +61,13 @@ async def test_manifest():
response = await client.get('/site.webmanifest') response = await client.get('/site.webmanifest')
assert response.status_code == 200 assert response.status_code == 200
assert response.mimetype == 'application/manifest+json' assert response.mimetype == 'application/manifest+json'
@pytest.mark.asyncio
async def test_data_fetch():
"""Test the healthy route."""
client = app.test_client()
response = await client.get('/data-fetch')
assert response.status_code == 200
response = await client.get('/data-fetch')
assert response.status_code == 200

View File

@@ -0,0 +1,25 @@
# German translations for tsun-gen3-proxy.
# Copyright (C) 2025 ORGANIZATION
# This file is distributed under the same license as the tsun-gen3-proxy
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
#
msgid ""
msgstr ""
"Project-Id-Version: tsun-gen3-proxy 0.14.0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-04-20 00:01+0200\n"
"PO-Revision-Date: 2025-04-18 16:24+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
"Language-Team: de <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
#: src/web/templates/index.html.j2:5
msgid "My Dashboard"
msgstr "Mein Dashboard"

View File

@@ -12,11 +12,14 @@ IMAGE = tsun-gen3-addon
SRC=../app SRC=../app
SRC_PROXY=$(SRC)/src SRC_PROXY=$(SRC)/src
CNF_PROXY=$(SRC)/config CNF_PROXY=$(SRC)/config
TRANSLATION_PROXY=$(SRC)/translations
# Target folders for building the local add-on and the docker container # Target folders for building the local add-on and the docker container
ADDON_PATH = ha_addon ADDON_PATH = ha_addon
DST=$(ADDON_PATH)/rootfs DST=$(ADDON_PATH)/rootfs
DST_PROXY=$(DST)/home/proxy DST_PROXY=$(DST)/home/proxy
DST_TRANSLATION=$(DST)/home/translations
# base director of the add-on repro for installing the add-on git repros # base director of the add-on repro for installing the add-on git repros
INST_BASE=../../ha-addons INST_BASE=../../ha-addons
@@ -85,19 +88,21 @@ SRC_FILES := $(wildcard $(SRC_PROXY)/*.py)\
$(wildcard $(SRC_PROXY)/gen3/*.py)\ $(wildcard $(SRC_PROXY)/gen3/*.py)\
$(wildcard $(SRC_PROXY)/gen3plus/*.py)\ $(wildcard $(SRC_PROXY)/gen3plus/*.py)\
$(wildcard $(SRC_PROXY)/web/*.py)\ $(wildcard $(SRC_PROXY)/web/*.py)\
$(wildcard $(SRC_PROXY)/web/templates/*.html)\ $(wildcard $(SRC_PROXY)/web/templates/*.html.j2)\
$(wildcard $(SRC_PROXY)/web/static/css/*.css)\ $(wildcard $(SRC_PROXY)/web/static/css/*.css)\
$(wildcard $(SRC_PROXY)/web/static/font/*)\ $(wildcard $(SRC_PROXY)/web/static/font/*)\
$(wildcard $(SRC_PROXY)/web/static/font-awesome/*/*)\ $(wildcard $(SRC_PROXY)/web/static/font-awesome/*/*)\
$(wildcard $(SRC_PROXY)/web/static/images/*) $(wildcard $(SRC_PROXY)/web/static/images/*)
CNF_FILES := $(wildcard $(CNF_PROXY)/*.toml) CNF_FILES := $(wildcard $(CNF_PROXY)/*.toml)
MO_FILES := $(wildcard $(TRANSLATION_PROXY)/de/LC_MESSAGES/*.mo)
# determine destination files # determine destination files
TARGET_FILES = $(SRC_FILES:$(SRC_PROXY)/%=$(DST_PROXY)/%) TARGET_FILES = $(SRC_FILES:$(SRC_PROXY)/%=$(DST_PROXY)/%)
CONFIG_FILES = $(CNF_FILES:$(CNF_PROXY)/%=$(DST_PROXY)/%) CONFIG_FILES = $(CNF_FILES:$(CNF_PROXY)/%=$(DST_PROXY)/%)
TRANSLATION_FILES = $(MO_FILES:$(TRANSLATION_PROXY)/%=$(DST_TRANSLATION)/%)
rootfs: $(TARGET_FILES) $(CONFIG_FILES) $(DST)/requirements.txt rootfs: $(TARGET_FILES) $(CONFIG_FILES) $(TRANSLATION_FILES) $(DST)/requirements.txt
$(CONFIG_FILES): $(DST_PROXY)/% : $(CNF_PROXY)/% $(CONFIG_FILES): $(DST_PROXY)/% : $(CNF_PROXY)/%
@echo Copy $< to $@ @echo Copy $< to $@
@@ -109,6 +114,11 @@ $(TARGET_FILES): $(DST_PROXY)/% : $(SRC_PROXY)/%
@mkdir -p $(@D) @mkdir -p $(@D)
@cp $< $@ @cp $< $@
$(TRANSLATION_FILES): $(DST_TRANSLATION)/% : $(TRANSLATION_PROXY)/%
@echo Copy $< to $@
@mkdir -p $(@D)
@cp $< $@
$(DST)/requirements.txt : $(SRC)/requirements.txt $(DST)/requirements.txt : $(SRC)/requirements.txt
@echo Copy $< to $@ @echo Copy $< to $@
@cp $< $@ @cp $< $@

View File

@@ -23,11 +23,11 @@ services:
ports: ports:
5005/tcp: 5005 5005/tcp: 5005
10000/tcp: 10000 10000/tcp: 10000
8127/tcp: 8127
webui: "http://[HOST]:[PORT:8127]/" webui: "http://[HOST]:[PORT:8127]/"
watchdog: "http://[HOST]:[PORT:8127]/-/healthy" watchdog: "http://[HOST]:[PORT:8127]/-/healthy"
ingress: true ingress: true
ingress_port: 8127 ingress_port: 8127
panel_icon: "mdi:application-cog-outline"
# Definition of parameters in the configuration tab of the addon # Definition of parameters in the configuration tab of the addon
# parameters are available within the container as /data/options.json # parameters are available within the container as /data/options.json