diff --git a/public/css/style.css b/public/css/style.css index 13246a3..4b60119 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1487,8 +1487,17 @@ html, body { .plugins-panel__status { min-width: 3.5em; + max-width: 200px; color: #888; font-size: 11px; + /* Overrides the parent's nowrap: "Running…"/"Done" never wrap either + way, but a real RunError message (e.g. "plugin exited with status 1") + needs to, or it just overflows the flyout unreadably. */ + white-space: normal; +} + +.plugins-panel__status--error { + color: #c62828; } .plugins-panel__empty { diff --git a/public/js/plugins.js b/public/js/plugins.js index a43ab74..2c6a983 100644 --- a/public/js/plugins.js +++ b/public/js/plugins.js @@ -83,12 +83,23 @@ export class PluginsControl { east: bounds.getEast(), }); const response = await fetch(`/api/plugins/${encodeURIComponent(plugin.name)}/run`, { method: "POST", body }); - if (!response.ok) throw new Error(`run failed: ${response.status}`); - const { path } = await response.json(); - notifyGpxStoreChanged(path); + // Every app.rb response on this route is JSON, success or error — + // the catch below is only for an infra-level failure in front of it + // (a proxy's own HTML error page, a dropped connection) that never + // reached app.rb at all. + const data = await response.json().catch(() => ({})); + if (!response.ok) throw new Error(data.error || `run failed: ${response.status}`); + notifyGpxStoreChanged(data.path); status.textContent = "Done"; + status.classList.remove("plugins-panel__status--error"); } catch (err) { - status.textContent = "Failed — see server log"; + // Plugins::RunError's message (e.g. "plugin exited with status 1") + // reaches here as `data.error` above — nothing about a plugin run + // failure gets logged server-side (app.rb turns it into a plain 502 + // JSON response, not an exception), so showing that message directly + // is the only way to see why, short of SSHing in to reproduce it. + status.textContent = err.message; + status.classList.add("plugins-panel__status--error"); } finally { button.disabled = false; }