From c31f22f23ff40e408d43e5dc3d2a99ba9cf55765 Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Thu, 12 Sep 2024 12:02:59 +0200 Subject: [PATCH] system: render header for failed active widgets to allow removal (#7858) * system: render header for failed active widgets to allow removal If there is a syntax error in the widget JS code we will not be able to remove it from the dashboard. This is only a POC as it doesn't fully work for varying reasons, but details where the problem lies. * dashboard: account for failed module imports or class instantiations --------- Co-authored-by: Stephan de Wit --- .../www/js/opnsense_widget_manager.js | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/src/opnsense/www/js/opnsense_widget_manager.js b/src/opnsense/www/js/opnsense_widget_manager.js index c4cbaf28c..166047a4a 100644 --- a/src/opnsense/www/js/opnsense_widget_manager.js +++ b/src/opnsense/www/js/opnsense_widget_manager.js @@ -138,15 +138,18 @@ class WidgetManager { } const promises = data.modules.map(async (item) => { - const mod = await import('/ui/js/widgets/' + item.module + '?t='+Date.now()); - this.loadedModules[item.id] = mod.default; - this.widgetTranslations[item.id] = item.translations; + try { + const mod = await import('/ui/js/widgets/' + item.module + '?t='+Date.now()); + this.loadedModules[item.id] = mod.default; + } catch (error) { + console.error('Could not import module', item.module, error); + } finally { + this.widgetTranslations[item.id] = item.translations; + } }); // Load all modules simultaneously - this shouldn't take long - const results = await Promise.all(promises.map(p => p.catch(e => e))); - const errors = results.filter(result => (result instanceof Error)); - if (errors.length > 0) console.error('Failed to load one or more widgets:', errors); + await Promise.all(promises); }); } @@ -156,12 +159,23 @@ class WidgetManager { } for (const [id, configuration] of Object.entries(this.widgetConfigurations)) { - if (id in this.loadedModules) { - try { - this._createGridStackWidget(id, this.loadedModules[id], configuration); - } catch (error) { - console.error('Failed to create widget', id, error); - } + try { + this._createGridStackWidget(id, this.loadedModules[id], configuration); + } catch (error) { + console.error(error); + + let $panel = this._makeWidget(id, this.widgetTranslations[id].title, ` +
+ +
+ ${this.gettext.failed} +
+ `); + this.widgetConfigurations[id] = { + id: id, + content: $panel.prop('outerHTML'), + ...configuration + }; } } @@ -169,6 +183,10 @@ class WidgetManager { } _createGridStackWidget(id, widgetClass, persistedConfig = {}) { + if (!(id in this.loadedModules)) { + throw new Error('Widget not loaded'); + } + // merge persisted config with defaults let config = { callbacks: { @@ -246,6 +264,13 @@ class WidgetManager { // force the cell height of each widget to the lowest value. The grid will adjust the height // according to the content of the widget. this.grid.cellHeight(1); + + // click handlers for widget removal. + for (const id of Object.keys(this.widgetConfigurations)) { + $(`#close-handle-${id}`).click((event) => { + this._onWidgetClose(id); + }); + } } _renderHeader() { @@ -425,7 +450,7 @@ class WidgetManager { // Executed for each widget; starts the widget-specific tick routine. async _onMarkupRendered(widget) { - // click handler for widget removal. + // click handler for widget removal $(`#close-handle-${widget.id}`).click((event) => { this._onWidgetClose(widget.id); }); @@ -750,8 +775,8 @@ class WidgetManager { _onWidgetClose(id) { clearInterval(this.widgetTickRoutines[id]); - this.widgetClasses[id].onWidgetClose(); + if (id in this.widgetClasses) this.widgetClasses[id].onWidgetClose(); this.grid.removeWidget(this.widgetHTMLElements[id]); - this.moduleDiff.push(id); + if (id in this.loadedModules) this.moduleDiff.push(id); } }