Skip to content

Commit 66dd7f3

Browse files
New logfile viewer in website (#2671)
* Version * New logfile viewer * [pre-commit.ci lite] apply automatic fixes * FIx path for log api * Update custom-dictionary-workspace.txt * [pre-commit.ci lite] apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 5ae1a4b commit 66dd7f3

File tree

4 files changed

+332
-36
lines changed

4 files changed

+332
-36
lines changed

.cspell/custom-dictionary-workspace.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Batpred
2222
battemperature
2323
batteri
2424
batteryb
25+
beforeunload
2526
bierner
2627
calib
2728
cexxxx
@@ -280,6 +281,7 @@ useid
280281
userinterface
281282
varh
282283
venv
284+
visibilitychange
283285
Wallbox
284286
weblink
285287
welink

apps/predbat/predbat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import requests
3737
import asyncio
3838

39-
THIS_VERSION = "v8.25.0"
39+
THIS_VERSION = "v8.25.1"
4040

4141
# fmt: off
4242
PREDBAT_FILES = ["predbat.py", "config.py", "prediction.py", "gecloud.py","utils.py", "inverter.py", "ha.py", "download.py", "unit_test.py", "web.py", "web_helper.py", "predheat.py", "futurerate.py", "octopus.py", "solcast.py","execute.py", "plan.py", "fetch.py", "output.py", "userinterface.py", "energydataservice.py", "alertfeed.py", "compare.py", "db_manager.py", "db_engine.py", "plugin_system.py", "ohme.py", "components.py", "fox.py"]

apps/predbat/web.py

Lines changed: 87 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import shutil
2020
import html as html_module
2121
import time
22-
from web_helper import get_header_html, get_plan_css, get_editor_js, get_editor_css, get_log_css, get_charts_css, get_apps_css, get_html_config_css, get_apps_js, get_components_css
22+
from web_helper import get_header_html, get_plan_css, get_editor_js, get_editor_css, get_log_css, get_charts_css, get_apps_css, get_html_config_css, get_apps_js, get_components_css, get_logfile_js
2323

2424
from utils import calc_percent_limit, str2time, dp0, dp2
2525
from config import TIME_FORMAT, TIME_FORMAT_DAILY
@@ -141,6 +141,7 @@ async def start(self):
141141
app.router.add_get("/api/ping", self.html_api_ping)
142142
app.router.add_post("/api/state", self.html_api_post_state)
143143
app.router.add_post("/api/service", self.html_api_post_service)
144+
app.router.add_get("/api/log", self.html_api_get_log)
144145
app.router.add_post("/api/login", self.html_api_login)
145146

146147
# Notify plugin system that web interface is ready
@@ -926,6 +927,73 @@ def render_chart(self, series_data, yaxis_name, chart_name, now_str, tagname="ch
926927
text += "</script>\n"
927928
return text
928929

930+
async def html_api_get_log(self, request):
931+
"""
932+
JSON API to get log data with filtering
933+
"""
934+
try:
935+
logfile = "predbat.log"
936+
logdata = ""
937+
938+
if os.path.exists(logfile):
939+
with open(logfile, "r") as f:
940+
logdata = f.read()
941+
942+
# Get query parameters
943+
args = request.query
944+
filter_type = args.get("filter", "warnings") # all, warnings, errors
945+
since_line = int(args.get("since", 0)) # Line number to start from
946+
max_lines = int(args.get("max_lines", 1024)) # Maximum lines to return
947+
948+
loglines = logdata.split("\n")
949+
total_lines = len(loglines)
950+
951+
# Process log lines with filtering
952+
result_lines = []
953+
count_lines = 0
954+
lineno = total_lines - 1
955+
956+
while count_lines < max_lines and lineno >= 0:
957+
line = loglines[lineno]
958+
line_lower = line.lower()
959+
960+
# Skip empty lines
961+
if not line.strip():
962+
lineno -= 1
963+
continue
964+
965+
# Apply filtering
966+
include_line = False
967+
line_type = "info"
968+
969+
if "error" in line_lower:
970+
line_type = "error"
971+
include_line = True
972+
elif "warn" in line_lower:
973+
line_type = "warning"
974+
include_line = filter_type in ["all", "warnings"]
975+
else:
976+
line_type = "info"
977+
include_line = filter_type == "all"
978+
979+
if include_line and (since_line == 0 or lineno > since_line):
980+
start_line = line[0:27] if len(line) >= 27 else line
981+
rest_line = line[27:] if len(line) >= 27 else ""
982+
983+
result_lines.append({"line_number": lineno, "timestamp": start_line, "message": rest_line, "type": line_type, "full_line": line})
984+
count_lines += 1
985+
986+
lineno -= 1
987+
988+
# Reverse to get reverse chronological order (newest first)
989+
result_lines.reverse()
990+
991+
return web.json_response({"status": "success", "total_lines": total_lines, "returned_lines": len(result_lines), "lines": result_lines, "filter": filter_type})
992+
993+
except Exception as e:
994+
self.log(f"Error in html_api_get_log: {e}")
995+
return web.json_response({"status": "error", "message": str(e)}, status=500)
996+
929997
async def html_api_post_state(self, request):
930998
"""
931999
JSON API
@@ -1186,16 +1254,11 @@ def add_button_to_load(match):
11861254

11871255
async def html_log(self, request):
11881256
"""
1189-
Return the Predbat log as an HTML page
1257+
Return the Predbat log as an HTML page with dynamic updates
11901258
"""
1191-
logfile = "predbat.log"
1192-
logdata = ""
11931259
self.default_page = "./log"
1194-
if os.path.exists(logfile):
1195-
with open(logfile, "r") as f:
1196-
logdata = f.read()
11971260

1198-
# Decode method get arguments
1261+
# Decode method get arguments to determine filter type
11991262
args = request.query
12001263
errors = False
12011264
warnings = False
@@ -1212,59 +1275,48 @@ async def html_log(self, request):
12121275
self.default_log = "warnings"
12131276
warnings = True
12141277

1215-
loglines = logdata.split("\n")
1216-
text = self.get_header("Predbat Log", refresh=10)
1278+
# Remove refresh from header since we'll update dynamically
1279+
text = self.get_header("Predbat Log", refresh=0)
12171280
text += """<body>"""
12181281
text += get_log_css()
12191282

12201283
if errors:
12211284
active_all = ""
12221285
active_warnings = ""
12231286
active_errors = "active"
1287+
filter_type = "errors"
12241288
elif warnings:
12251289
active_all = ""
12261290
active_warnings = "active"
12271291
active_errors = ""
1292+
filter_type = "warnings"
12281293
else:
12291294
active_all = "active"
12301295
active_warnings = ""
12311296
active_errors = ""
1297+
filter_type = "all"
12321298

12331299
text += '<div class="log-menu">'
12341300
text += "<h3>Logfile</h3> "
12351301
text += f'<a href="./log?all" class="{active_all}">All</a>'
12361302
text += f'<a href="./log?warnings" class="{active_warnings}">Warnings</a>'
12371303
text += f'<a href="./log?errors" class="{active_errors}">Errors</a>'
12381304
text += '<a href="./debug_log">Download</a>'
1305+
text += '<label class="auto-scroll-toggle"><input type="checkbox" id="autoScroll"> Auto-scroll to new</label>'
1306+
text += '<button class="scroll-to-bottom" onclick="scrollToBottom()">Scroll to Bottom</button>'
1307+
text += '<button id="pauseResumeBtn" onclick="toggleUpdates()" style="margin-left: 10px; padding: 4px 8px;">Pause</button>'
12391308
text += "</div>"
12401309

1241-
text += "<table width=100%>\n"
1242-
1243-
total_lines = len(loglines)
1244-
count_lines = 0
1245-
lineno = total_lines - 1
1246-
while count_lines < 1024 and lineno >= 0:
1247-
line = loglines[lineno]
1248-
line_lower = line.lower()
1249-
lineno -= 1
1250-
1251-
start_line = line[0:27]
1252-
rest_line = line[27:]
1253-
1254-
if "error" in line_lower:
1255-
text += "<tr><td>{}</td><td nowrap><font color=#ff3333>{}</font> {}</td></tr>\n".format(lineno, start_line, rest_line)
1256-
count_lines += 1
1257-
continue
1258-
elif (not errors) and ("warn" in line_lower):
1259-
text += "<tr><td>{}</td><td nowrap><font color=#ffA500>{}</font> {}</td></tr>\n".format(lineno, start_line, rest_line)
1260-
count_lines += 1
1261-
continue
1310+
text += '<div id="logStatus" class="log-status">Loading log data...</div>'
1311+
text += "<table width=100% id='logTable'>\n"
1312+
text += "<tbody id='logTableBody'>\n"
1313+
text += "<!-- Log entries will be loaded dynamically via JavaScript -->\n"
1314+
text += "</tbody>\n"
1315+
text += "</table>"
12621316

1263-
if line and (not errors) and (not warnings):
1264-
text += "<tr><td>{}</td><td nowrap><font color=#33cc33>{}</font> {}</td></tr>\n".format(lineno, start_line, rest_line)
1265-
count_lines += 1
1317+
# Add JavaScript for dynamic updates
1318+
text += get_logfile_js(filter_type)
12661319

1267-
text += "</table>"
12681320
text += "</body></html>\n"
12691321
return web.Response(content_type="text/html", text=text)
12701322

0 commit comments

Comments
 (0)