From 06ea4aa496789f0cfd9d3d843a090178a666f3c8 Mon Sep 17 00:00:00 2001 From: Chuck Date: Mon, 29 Jun 2026 11:48:10 -0400 Subject: [PATCH] fix(tools-tab): resolve remaining PR review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - api_v3: use getattr(api_v3, 'plugin_manager', None) instead of the module-level plugin_manager (always None); app.py sets the blueprint attribute, not the module global, so the fallback to plugin-repos was always taken - pages_v3: replace broad except Exception in _load_tools_partial with specific TemplateNotFound / OSError handlers and add [Pages V3][Tools] context prefix to log messages and error responses for easier Pi debugging - base.html: add Tools tab branch to the HTMX-unavailable fallback block in loadTabContent so the tab loads gracefully via direct fetch if HTMX never initialises Skipped: auth on execute_system_action — pre-existing app-wide design; reboot/shutdown and all other system actions share the same exposure. An app-level auth layer is the correct fix and is out of scope here. Co-Authored-By: Claude Sonnet 4.6 --- web_interface/blueprints/api_v3.py | 3 ++- web_interface/blueprints/pages_v3.py | 10 +++++++--- web_interface/templates/v3/base.html | 17 ++++++++++++++++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/web_interface/blueprints/api_v3.py b/web_interface/blueprints/api_v3.py index 9c59399e..8b634f81 100644 --- a/web_interface/blueprints/api_v3.py +++ b/web_interface/blueprints/api_v3.py @@ -1666,7 +1666,8 @@ def execute_system_action(): 'output': _truncate_output(result.stdout, result.stderr) }) elif action == 'install_plugin_requirements': - plugins_dir = Path(plugin_manager.plugins_dir) if plugin_manager else PROJECT_ROOT / 'plugin-repos' + active_pm = getattr(api_v3, 'plugin_manager', None) + plugins_dir = Path(active_pm.plugins_dir) if active_pm else PROJECT_ROOT / 'plugin-repos' results = [] if plugins_dir.exists(): for p in sorted(plugins_dir.iterdir()): diff --git a/web_interface/blueprints/pages_v3.py b/web_interface/blueprints/pages_v3.py index 1086b5f9..b011480a 100644 --- a/web_interface/blueprints/pages_v3.py +++ b/web_interface/blueprints/pages_v3.py @@ -1,4 +1,5 @@ from flask import Blueprint, render_template, flash +from jinja2 import TemplateNotFound from markupsafe import escape import json import logging @@ -454,9 +455,12 @@ def _load_tools_partial(): """Load tools/utilities partial.""" try: return render_template('v3/partials/tools.html') - except Exception: - logger.error("Error loading partial", exc_info=True) - return "Error loading partial", 500 + except TemplateNotFound: + logger.error("[Pages V3][Tools] Template not found: v3/partials/tools.html", exc_info=True) + return "[Pages V3][Tools] Template is missing.", 500 + except OSError as exc: + logger.error("[Pages V3][Tools] I/O error loading tools partial: %s", exc, exc_info=True) + return "[Pages V3][Tools] Failed to load due to a file system error. Check logs.", 500 def _load_plugin_config_partial(plugin_id): diff --git a/web_interface/templates/v3/base.html b/web_interface/templates/v3/base.html index 1527fc55..5dea81d5 100644 --- a/web_interface/templates/v3/base.html +++ b/web_interface/templates/v3/base.html @@ -1922,7 +1922,22 @@ if (tab === 'overview' && typeof loadOverviewDirect === 'function') loadOverviewDirect(); else if (tab === 'wifi' && typeof loadWifiDirect === 'function') loadWifiDirect(); else if (tab === 'plugins' && typeof loadPluginsDirect === 'function') loadPluginsDirect(); - } + else if (tab === 'tools') { + fetch('/v3/partials/tools') + .then(r => { + if (!r.ok) throw new Error(r.status + ' ' + r.statusText); + return r.text(); + }) + .then(html => { + contentEl.innerHTML = html; + contentEl.setAttribute('data-loaded', 'true'); + if (window.Alpine) window.Alpine.initTree(contentEl); + }) + .catch(err => { + console.error('Failed to load tools content:', err); + contentEl.innerHTML = '

Failed to load Tools. Please refresh the page.

'; + }); + } }, 100); },