From 5f0aebd4f5105980d557e2c530dc801c325b7133 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 08:40:46 +0000 Subject: [PATCH 1/5] Initial plan From c662175e2d1ba39535deb308f039d054b2f129d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 08:44:56 +0000 Subject: [PATCH 2/5] Fix: Add menu bar and back button to metrics fallback page when prometheus_client is not installed Co-authored-by: springfall2008 <48591903+springfall2008@users.noreply.github.com> --- apps/predbat/web.py | 50 +++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/apps/predbat/web.py b/apps/predbat/web.py index ab080a2d2..a95e1a648 100644 --- a/apps/predbat/web.py +++ b/apps/predbat/web.py @@ -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" @@ -303,9 +303,7 @@ def get_power_flow_diagram(self): - """.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 @@ -326,9 +324,7 @@ def get_power_flow_diagram(self): - """.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 += """ @@ -336,9 +332,7 @@ def get_power_flow_diagram(self): {} W - """.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))) @@ -358,9 +352,7 @@ def get_power_flow_diagram(self): - """.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))) @@ -380,9 +372,7 @@ def get_power_flow_diagram(self): - """.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 @@ -403,9 +393,7 @@ def get_power_flow_diagram(self): - """.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))) @@ -425,9 +413,7 @@ def get_power_flow_diagram(self): - """.format( - dp0(grid_power), grid_speed, grid_speed, grid_speed - ) + """.format(dp0(grid_power), grid_speed, grid_speed, grid_speed) html += """ @@ -812,9 +798,7 @@ async def html_entity(self, request): Back - """.format( - self.default_page - ) + """.format(self.default_page) # Collect available attributes for all selected entities entity_attributes_map = {} @@ -862,9 +846,7 @@ async def html_entity(self, request): - """.format( - days - ) + """.format(days) # Add days selector text += """
@@ -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 += "\n" + text += """
+

Metrics Dashboard Unavailable

+

prometheus_client is not installed.

+

Install it with: pip install prometheus_client

+

← Back to Dashboard

+
+""" + text += "\n" + return web.Response(text=text, content_type="text/html") import json as _json From db5148eae22aceb094c708bc35c090835aacb72a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 08:55:21 +0000 Subject: [PATCH 3/5] Remove dead code: FALLBACK_HTML, metrics_dashboard_handler, and unused imports from web_metrics_dashboard.py Co-authored-by: springfall2008 <48591903+springfall2008@users.noreply.github.com> --- apps/predbat/web_metrics_dashboard.py | 31 --------------------------- 1 file changed, 31 deletions(-) diff --git a/apps/predbat/web_metrics_dashboard.py b/apps/predbat/web_metrics_dashboard.py index 82abd61de..c1111ef9a 100644 --- a/apps/predbat/web_metrics_dashboard.py +++ b/apps/predbat/web_metrics_dashboard.py @@ -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 """ -

Metrics Dashboard Unavailable

-

prometheus_client is not installed.

-

Install it with: pip install prometheus_client

""" - - -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 = ( - "" - "" - "PredBat Metrics" - + get_metrics_dashboard_css() - + "" - + get_metrics_dashboard_body(data_json) - + "" - ) - return web.Response(text=html, content_type="text/html") From 4dc734f650796f61824b4ebe28d6d94e431cedf9 Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:48:53 +0000 Subject: [PATCH 4/5] Add 'larr' to custom dictionary --- .cspell/custom-dictionary-workspace.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.cspell/custom-dictionary-workspace.txt b/.cspell/custom-dictionary-workspace.txt index 217577ac0..bb34a7c60 100644 --- a/.cspell/custom-dictionary-workspace.txt +++ b/.cspell/custom-dictionary-workspace.txt @@ -181,6 +181,7 @@ kwargs kwhb labelcolor labelnames +larr linebreak linestyle loadml From 47acba6fff4eb76e3ca1d92f36de01fe5fcce82e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:51:06 +0000 Subject: [PATCH 5/5] [pre-commit.ci lite] apply automatic fixes --- apps/predbat/web.py | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/apps/predbat/web.py b/apps/predbat/web.py index a95e1a648..f5ec59a37 100644 --- a/apps/predbat/web.py +++ b/apps/predbat/web.py @@ -303,7 +303,9 @@ def get_power_flow_diagram(self): - """.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 @@ -324,7 +326,9 @@ def get_power_flow_diagram(self): - """.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 += """ @@ -332,7 +336,9 @@ def get_power_flow_diagram(self): {} W - """.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))) @@ -352,7 +358,9 @@ def get_power_flow_diagram(self): - """.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))) @@ -372,7 +380,9 @@ def get_power_flow_diagram(self): - """.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 @@ -393,7 +403,9 @@ def get_power_flow_diagram(self): - """.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))) @@ -413,7 +425,9 @@ def get_power_flow_diagram(self): - """.format(dp0(grid_power), grid_speed, grid_speed, grid_speed) + """.format( + dp0(grid_power), grid_speed, grid_speed, grid_speed + ) html += """ @@ -798,7 +812,9 @@ async def html_entity(self, request): Back -
""".format(self.default_page) + """.format( + self.default_page + ) # Collect available attributes for all selected entities entity_attributes_map = {} @@ -846,7 +862,9 @@ async def html_entity(self, request): - """.format(days) + """.format( + days + ) # Add days selector text += """