fix(web): seven web UI bugs, and remove dead plugins_manager.js helpers (#647)

- Operation History: the plugin filter lists the installed plugin ids
  instead of one option, "plugins" (Object.keys of {plugins: [...]}).
- Ctrl/Cmd+S submits the active tab's first visible form with
  requestSubmit() (validation and onsubmit guards run) instead of a bare
  Event on the first form in the document; skipped inside a modal dialog
  and on tabs without a form.
- Overview "Check Updates" confirms like "Update Code", takes its button
  explicitly (no implicit global event) and shows the server's message.
  Both, and the Tools tab git pull, raise the restart-pending banner on
  restart_required.
- Tools: toolsAction and diagnostics show the server's error message;
  only a non-JSON body falls back to HTTP <status>.
- Installed list after uninstall: PluginAPI writes clear the throttler's
  GET cache, a forced loadInstalledPlugins clears it too, and the
  post-uninstall reload goes through refreshInstalledPlugins().
- Plugin widgets load from /static/plugin-widgets/ only (the other two
  paths have no route).
- Raw JSON editor escapes the parse error; slider escapes value/min/max/step.
- Removed the unreferenced array-of-objects and key-value helpers from
  plugins_manager.js, the textarea auto-resize and Ctrl+R handlers in
  app.js, and a redundant ?v= on the plugins_manager.js script tag.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-28 08:26:27 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 7eb7a58d0c
commit 76f5d8a336
11 changed files with 103 additions and 697 deletions
+22 -21
View File
@@ -229,31 +229,32 @@ document.addEventListener('DOMContentLoaded', function() {
// SSE streams (and window.reconnectSSE) are owned by window.LEDStreams in
// js/app-shell.js — do not open EventSources for stats/display here.
// Auto-resize textareas
document.addEventListener('DOMContentLoaded', function() {
const textareas = document.querySelectorAll('textarea');
textareas.forEach(textarea => {
textarea.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = this.scrollHeight + 'px';
});
});
});
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
// Ctrl/Cmd + R to refresh
if ((e.ctrlKey || e.metaKey) && e.key === 'r') {
e.preventDefault();
location.reload();
}
// Ctrl/Cmd + S to save current form
// Ctrl/Cmd + S to save the form on the active tab: the first visible form
// in that tab's panel (#<tab>-content, or #plugin-config-<id> for a plugin
// tab). The panel is looked up by activeTab rather than by visibility
// alone because during the x-transition between tabs both are visible.
// requestSubmit() runs validation and onsubmit guards like a real submit;
// with no visible form, do nothing. Inside a modal dialog the shortcut is
// the dialog's (json-file-manager saves its file on Ctrl+S), so the tab's
// form is left alone.
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
const form = document.querySelector('form');
if (form) {
form.dispatchEvent(new Event('submit'));
const active = document.activeElement;
if (active && active.closest && active.closest('[aria-modal="true"]')) {
return;
}
const app = typeof window.getApp === 'function' ? window.getApp() : null;
const tab = app && app.activeTab;
const panel = (tab && (document.getElementById(tab + '-content') ||
document.getElementById('plugin-config-' + tab))) ||
document.getElementById('tab-content');
if (!panel) return;
const form = Array.from(panel.querySelectorAll('form'))
.find(f => f.getClientRects().length > 0);
if (form && typeof form.requestSubmit === 'function') {
form.requestSubmit();
}
}
});