fix(tools-tab): resolve remaining PR review comments

- 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 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-06-29 11:48:10 -04:00
co-authored by Claude Sonnet 4.6
parent 344f64ebba
commit 06ea4aa496
3 changed files with 25 additions and 5 deletions
+2 -1
View File
@@ -1666,7 +1666,8 @@ def execute_system_action():
'output': _truncate_output(result.stdout, result.stderr) 'output': _truncate_output(result.stdout, result.stderr)
}) })
elif action == 'install_plugin_requirements': 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 = [] results = []
if plugins_dir.exists(): if plugins_dir.exists():
for p in sorted(plugins_dir.iterdir()): for p in sorted(plugins_dir.iterdir()):
+7 -3
View File
@@ -1,4 +1,5 @@
from flask import Blueprint, render_template, flash from flask import Blueprint, render_template, flash
from jinja2 import TemplateNotFound
from markupsafe import escape from markupsafe import escape
import json import json
import logging import logging
@@ -454,9 +455,12 @@ def _load_tools_partial():
"""Load tools/utilities partial.""" """Load tools/utilities partial."""
try: try:
return render_template('v3/partials/tools.html') return render_template('v3/partials/tools.html')
except Exception: except TemplateNotFound:
logger.error("Error loading partial", exc_info=True) logger.error("[Pages V3][Tools] Template not found: v3/partials/tools.html", exc_info=True)
return "Error loading partial", 500 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): def _load_plugin_config_partial(plugin_id):
+16 -1
View File
@@ -1922,7 +1922,22 @@
if (tab === 'overview' && typeof loadOverviewDirect === 'function') loadOverviewDirect(); if (tab === 'overview' && typeof loadOverviewDirect === 'function') loadOverviewDirect();
else if (tab === 'wifi' && typeof loadWifiDirect === 'function') loadWifiDirect(); else if (tab === 'wifi' && typeof loadWifiDirect === 'function') loadWifiDirect();
else if (tab === 'plugins' && typeof loadPluginsDirect === 'function') loadPluginsDirect(); 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 = '<div class="bg-red-50 border border-red-200 rounded-lg p-4"><p class="text-red-800">Failed to load Tools. Please refresh the page.</p></div>';
});
}
}, 100); }, 100);
}, },