Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 21 additions & 29 deletions apps/predbat/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
from predbat import THIS_VERSION
from component_base import ComponentBase
from config import APPS_SCHEMA
from web_metrics_dashboard import get_metrics_dashboard_css, get_metrics_dashboard_body, FALLBACK_HTML
from web_metrics_dashboard import get_metrics_dashboard_css, get_metrics_dashboard_body
from predbat_metrics import metrics_handler, metrics_json_handler, metrics, PROMETHEUS_AVAILABLE

ROOT_YAML_KEY = "pred_bat"
Expand Down Expand Up @@ -303,9 +303,7 @@ def get_power_flow_diagram(self):
<!-- House to Grid path -->
<path id="house-grid-path" d="M340,230 L390,270" stroke="transparent" fill="none" />
</defs>
""".format(
dp0(load_power)
)
""".format(dp0(load_power))
# Draw arrows and labels
if pv_generating:
# Calculate animation speed based on power flow - faster for higher power
Expand All @@ -326,19 +324,15 @@ def get_power_flow_diagram(self):
<circle r="2" fill="#2196F3" opacity="0.4">
<animateMotion dur="{}s" repeatCount="indefinite" begin="1.0s" path="M200,100 L250,150" />
</circle>
""".format(
dp0(pv_power), pv_speed, pv_speed, pv_speed
)
""".format(dp0(pv_power), pv_speed, pv_speed, pv_speed)
else:
# Make the PV to House line dashed if not generating
html += """
<!-- PV to House Arrow (dashed) -->
<line x1="200" y1="100" x2="250" y2="150" stroke="#2196F3" stroke-width="2" stroke-dasharray="5,5" marker-end="url(#pv-arrow)" />
<text x="250" y="120" text-anchor="middle" fill="#2196F3">{} W</text>
<!-- No moving dot when PV is not generating -->
""".format(
dp0(pv_power)
)
""".format(dp0(pv_power))
if battery_charging:
# Calculate animation speed based on power flow - faster for higher power
battery_speed = max(0.5, min(3.0, 2.0 - (abs(battery_power) / 3000)))
Expand All @@ -358,9 +352,7 @@ def get_power_flow_diagram(self):
<circle r="2" fill="#FF9800" opacity="0.4">
<animateMotion dur="{}s" repeatCount="indefinite" begin="1.0s" path="M200,300 L250,250" />
</circle>
""".format(
dp0(battery_power), battery_speed, battery_speed, battery_speed
)
""".format(dp0(battery_power), battery_speed, battery_speed, battery_speed)
else:
# Calculate animation speed based on power flow - faster for higher power
battery_speed = max(0.5, min(3.0, 2.0 - (abs(battery_power) / 3000)))
Expand All @@ -380,9 +372,7 @@ def get_power_flow_diagram(self):
<circle r="2" fill="#FF9800" opacity="0.4">
<animateMotion dur="{}s" repeatCount="indefinite" begin="1.0s" path="M265,235 L215,275" />
</circle>
""".format(
dp0(battery_power), battery_speed, battery_speed, battery_speed
)
""".format(dp0(battery_power), battery_speed, battery_speed, battery_speed)

if grid_importing:
# Calculate animation speed based on power flow - faster for higher power
Expand All @@ -403,9 +393,7 @@ def get_power_flow_diagram(self):
<circle r="2" fill="#4CAF50" opacity="0.4">
<animateMotion dur="{}s" repeatCount="indefinite" begin="1.0s" path="M410,290 L355,240" />
</circle>
""".format(
dp0(grid_power), grid_speed, grid_speed, grid_speed
)
""".format(dp0(grid_power), grid_speed, grid_speed, grid_speed)
else:
# Calculate animation speed based on power flow - faster for higher power
grid_speed = max(0.5, min(3.0, 2.0 - (abs(grid_power) / 3000)))
Expand All @@ -425,9 +413,7 @@ def get_power_flow_diagram(self):
<circle r="2" fill="#4CAF50" opacity="0.4">
<animateMotion dur="{}s" repeatCount="indefinite" begin="1.0s" path="M340,230 L390,270" />
</circle>
""".format(
dp0(grid_power), grid_speed, grid_speed, grid_speed
)
""".format(dp0(grid_power), grid_speed, grid_speed, grid_speed)
html += """
<!-- Arrowhead Marker -->
<defs>
Expand Down Expand Up @@ -812,9 +798,7 @@ async def html_entity(self, request):
<a href="{}" class="button" style="display: inline-block; padding: 8px 15px; background-color: #4CAF50; color: white; text-decoration: none; border-radius: 4px; font-weight: bold;">
<span class="mdi mdi-arrow-left" style="margin-right: 5px;"></span>Back
</a>
</div>""".format(
self.default_page
)
</div>""".format(self.default_page)

# Collect available attributes for all selected entities
entity_attributes_map = {}
Expand Down Expand Up @@ -862,9 +846,7 @@ async def html_entity(self, request):
<input type="hidden" name="days" value="{}" />
<button type="submit" style="margin-top: 10px; padding: 8px 16px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">Update Chart</button>
</form>
</div>""".format(
days
)
</div>""".format(days)

# Add days selector
text += """<div style="margin-bottom: 20px;">
Expand Down Expand Up @@ -4448,7 +4430,17 @@ async def html_metrics_dashboard(self, request):
self.default_page = "./metrics_dashboard"

if not PROMETHEUS_AVAILABLE:
return web.Response(text=FALLBACK_HTML, content_type="text/html")
text = self.get_header("Predbat Metrics", refresh=0)
text += "<body>\n"
text += """<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;text-align:center;padding:2rem;">
<h1>Metrics Dashboard Unavailable</h1>
<p><code>prometheus_client</code> is not installed.</p>
<p>Install it with: <code>pip install prometheus_client</code></p>
<p style="margin-top:1.5rem;"><a href="./dash" style="padding:10px 20px;background:#4CAF50;color:white;text-decoration:none;border-radius:4px;font-size:16px;">&larr; Back to Dashboard</a></p>
</div>
"""
text += "</body></html>\n"
return web.Response(text=text, content_type="text/html")

import json as _json

Expand Down
31 changes: 0 additions & 31 deletions apps/predbat/web_metrics_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
Dark/light mode is derived automatically from the ``body.dark-mode`` class used
by the rest of the PredBat web UI - no separate theme toggle is needed.
"""

from predbat_metrics import PROMETHEUS_AVAILABLE, metrics


def get_metrics_dashboard_css():
"""Return scoped CSS for the metrics dashboard component."""
return """<style>
Expand Down Expand Up @@ -363,30 +359,3 @@ def get_metrics_dashboard_body(data_json):
</script>
"""
)
FALLBACK_HTML = """<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>PredBat Metrics</title>
<style>body{font-family:sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;background:#0f172a;color:#e2e8f0;text-align:center;}</style>
</head><body><div><h1>Metrics Dashboard Unavailable</h1>
<p><code>prometheus_client</code> is not installed.</p>
<p>Install it with: <code>pip install prometheus_client</code></p></div></body></html>"""


async def metrics_dashboard_handler(request):
"""Serve the self-contained metrics dashboard at ``/metrics_dashboard``."""
import json
from aiohttp import web

if not PROMETHEUS_AVAILABLE:
return web.Response(text=FALLBACK_HTML, content_type="text/html")

data_json = json.dumps(metrics().to_dict())
html = (
"<!DOCTYPE html><html lang='en'><head>"
"<meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1'>"
"<title>PredBat Metrics</title>"
+ get_metrics_dashboard_css()
+ "</head><body style='background:var(--md-bg)'>"
+ get_metrics_dashboard_body(data_json)
+ "</body></html>"
)
return web.Response(text=html, content_type="text/html")
Loading