diff --git a/services/static-webserver/client/source/class/osparc/Application.js b/services/static-webserver/client/source/class/osparc/Application.js index 0b18c01bd22..251b74d42aa 100644 --- a/services/static-webserver/client/source/class/osparc/Application.js +++ b/services/static-webserver/client/source/class/osparc/Application.js @@ -70,6 +70,7 @@ qx.Class.define("osparc.Application", { // trackers osparc.announcement.Tracker.getInstance().startTracker(); osparc.WindowSizeTracker.getInstance().startTracker(); + osparc.ConsoleErrorTracker.getInstance().startTracker(); const webSocket = osparc.wrapper.WebSocket.getInstance(); webSocket.addListener("connect", () => osparc.WatchDog.getInstance().setOnline(true)); diff --git a/services/static-webserver/client/source/class/osparc/ConsoleErrorTracker.js b/services/static-webserver/client/source/class/osparc/ConsoleErrorTracker.js new file mode 100644 index 00000000000..6da6cdefc4e --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/ConsoleErrorTracker.js @@ -0,0 +1,53 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2023 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.ConsoleErrorTracker", { + extend: qx.core.Object, + type: "singleton", + + construct: function() { + this.base(arguments); + + this.__errors = []; + }, + + members: { + __errors: null, + + startTracker: function() { + const originalConsoleError = console.error; + + // Override console.error + console.error = (...args) => { + this.__errors.unshift({ + date: new Date(), + error: args + }); + if (this.__errors.length > 20) { + this.__errors.length = 20; + } + + // Call the original console.error so the error still appears in the console + originalConsoleError.apply(console, args); + }; + }, + + getErrors: function() { + return this.__errors; + }, + } +}); diff --git a/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js b/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js index 213bc8bf0bd..e4798ed1464 100644 --- a/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js +++ b/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js @@ -213,6 +213,9 @@ qx.Class.define("osparc.navigation.UserMenu", { if (osparc.data.Permissions.getInstance().isProductOwner()) { this.getChildControl("po-center"); } + if (osparc.data.Permissions.getInstance().isTester()) { + this.getChildControl("tester-center"); + } if (osparc.desktop.credits.Utils.areWalletsEnabled()) { this.getChildControl("billing-center"); } diff --git a/services/static-webserver/client/source/class/osparc/tester/ConsoleErrors.js b/services/static-webserver/client/source/class/osparc/tester/ConsoleErrors.js new file mode 100644 index 00000000000..074acc0315f --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/tester/ConsoleErrors.js @@ -0,0 +1,130 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.tester.ConsoleErrors", { + extend: osparc.po.BaseView, + construct: function() { + this.base(arguments); + }, + + members: { + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "filter-message": { + control = new qx.ui.form.TextField().set({ + liveUpdate : true, + placeholder: this.tr("Search in Message"), + }); + this._add(control); + break; + } + case "messages-table": { + const tableModel = new qx.ui.table.model.Filtered(); + tableModel.setColumns([ + this.tr("Date"), + this.tr("Message"), + ]); + const custom = { + tableColumnModel: function(obj) { + return new qx.ui.table.columnmodel.Resize(obj); + } + }; + control = new qx.ui.table.Table(tableModel, custom).set({ + selectable: true, + statusBarVisible: false, + showCellFocusIndicator: false, + forceLineHeight: false + }); + control.getTableColumnModel().setDataCellRenderer( + 0, + new qx.ui.table.cellrenderer.String().set({ + defaultCellStyle: "user-select: text" + }) + ); + control.getTableColumnModel().setDataCellRenderer( + 1, + new osparc.ui.table.cellrenderer.Html().set({ + defaultCellStyle: "user-select: text; text-wrap: wrap" + }) + ); + control.setColumnWidth(0, 80); + + // control.setDataRowRenderer(new osparc.ui.table.rowrenderer.ExpandSelection(control)); + this._add(control, { + flex: 1 + }); + break; + } + case "error-viewer": + control = new qx.ui.form.TextArea().set({ + autoSize: true, + }); + this._add(control, { + flex: 1 + }); + break; + } + return control || this.base(arguments, id); + }, + + _buildLayout: function() { + const filterMessage = this.getChildControl("filter-message"); + const table = this.getChildControl("messages-table"); + const errorViewer = this.getChildControl("error-viewer"); + + const model = table.getTableModel(); + filterMessage.addListener("changeValue", e => { + const value = e.getData(); + model.resetHiddenRows(); + model.addNotRegex(value, "Message", true); + model.applyFilters(); + }); + table.addListener("cellTap", e => { + const selectedRow = e.getRow(); + const rowData = table.getTableModel().getRowData(selectedRow); + errorViewer.setValue(JSON.stringify(rowData[1])); + }, this); + + this.__populateTable(); + }, + + __populateTable: function() { + const consoleErrorTracker = osparc.ConsoleErrorTracker.getInstance(); + const errors = consoleErrorTracker.getErrors(); + const errorsArray = []; + errors.forEach(msg => { + errorsArray.push({ + date: msg.date, + message: msg.error, + }); + }); + errorsArray.sort((a, b) => { + return new Date(b.date) - new Date(a.date); // newest first + }); + const datas = []; + errorsArray.forEach(entry => { + const data = [ + new Date(entry.date).toLocaleTimeString(), + entry.message, + ]; + datas.push(data); + }); + this.getChildControl("messages-table").getTableModel().setData(datas); + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/tester/TesterCenter.js b/services/static-webserver/client/source/class/osparc/tester/TesterCenter.js index b456afebb32..023fafb4dc5 100644 --- a/services/static-webserver/client/source/class/osparc/tester/TesterCenter.js +++ b/services/static-webserver/client/source/class/osparc/tester/TesterCenter.js @@ -27,6 +27,7 @@ qx.Class.define("osparc.tester.TesterCenter", { this.addWidgetOnTopOfTheTabs(miniProfile); this.__addSocketMessagesPage(); + this.__addConsoleErrorsPage(); this.__addStaticsPage(); }, @@ -34,8 +35,15 @@ qx.Class.define("osparc.tester.TesterCenter", { __addSocketMessagesPage: function() { const title = this.tr("Socket Messages"); const iconSrc = "@FontAwesome5Solid/exchange-alt/22"; - const maintenance = new osparc.tester.WebSocketMessages(); - this.addTab(title, iconSrc, maintenance); + const webSocketMessages = new osparc.tester.WebSocketMessages(); + this.addTab(title, iconSrc, webSocketMessages); + }, + + __addConsoleErrorsPage: function() { + const title = this.tr("Console Errors"); + const iconSrc = "@FontAwesome5Solid/times/22"; + const consoleErrors = new osparc.tester.ConsoleErrors(); + this.addTab(title, iconSrc, consoleErrors); }, __addStaticsPage: function() {