diff --git a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js index e16805fe6e5..a8b4aa0b8f9 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js @@ -113,6 +113,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", { __workbenchUI: null, __workbenchUIConnected: null, __iframePage: null, + __loggerPage: null, __loggerView: null, __currentNodeId: null, __startAppButton: null, @@ -440,9 +441,9 @@ qx.Class.define("osparc.desktop.WorkbenchView", { tabViewMain.add(iframePage); const loggerView = this.__loggerView = new osparc.widget.logger.LoggerView(); - const logsPage = this.__logsPage = this.__createTabPage("@FontAwesome5Solid/file-alt", this.tr("Logger"), loggerView); - osparc.utils.Utils.setIdToWidget(logsPage.getChildControl("button"), "loggerTabButton"); - tabViewMain.add(logsPage); + const loggerPage = this.__loggerPage = this.__createTabPage("@FontAwesome5Solid/file-alt", this.tr("Logger"), loggerView); + osparc.utils.Utils.setIdToWidget(loggerPage.getChildControl("button"), "loggerTabButton"); + tabViewMain.add(loggerPage); this.__addTopBarSpacer(topBar); @@ -590,6 +591,11 @@ qx.Class.define("osparc.desktop.WorkbenchView", { const edgeId = e.getData(); this.__removeEdge(edgeId); }, this); + workbenchUI.addListener("requestOpenLogger", e => { + const nodeId = e.getData(); + this.__loggerView.filterByNode(nodeId); + this.__openLoggerTab(); + }, this); } const workbench = this.getStudy().getWorkbench(); @@ -707,6 +713,11 @@ qx.Class.define("osparc.desktop.WorkbenchView", { } }, + __openLoggerTab: function() { + const tabViewMain = this.getChildControl("main-panel-tabs"); + tabViewMain.setSelection([this.__loggerPage]); + }, + __applyMaximized: function(maximized) { this.getBlocker().setStyles({ display: maximized ? "none" : "block" diff --git a/services/static-webserver/client/source/class/osparc/desktop/organizations/ServicesList.js b/services/static-webserver/client/source/class/osparc/desktop/organizations/ServicesList.js index b3cf8418e94..d6655889ed7 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/organizations/ServicesList.js +++ b/services/static-webserver/client/source/class/osparc/desktop/organizations/ServicesList.js @@ -88,7 +88,7 @@ qx.Class.define("osparc.desktop.organizations.ServicesList", { item.addListener("openMoreInfo", e => { const serviceKey = e.getData()["key"]; const serviceVersion = e.getData()["version"]; - osparc.store.Store.getInstance().getService(serviceKey, serviceVersion) + osparc.store.Store.getService(serviceKey, serviceVersion) .then(serviceData => { if (serviceData) { serviceData["resourceType"] = "service"; diff --git a/services/static-webserver/client/source/class/osparc/info/StudyMedium.js b/services/static-webserver/client/source/class/osparc/info/StudyMedium.js index df3dff04ec2..e8c77c184dc 100644 --- a/services/static-webserver/client/source/class/osparc/info/StudyMedium.js +++ b/services/static-webserver/client/source/class/osparc/info/StudyMedium.js @@ -94,7 +94,8 @@ qx.Class.define("osparc.info.StudyMedium", { width: 25, height: 25, icon: "@FontAwesome5Solid/ellipsis-v/14", - focusable: false + focusable: false, + allowGrowY: false }); const moreInfoButton = this.__getMoreInfoMenuButton(); diff --git a/services/static-webserver/client/source/class/osparc/ui/table/rowrenderer/ExtendSelection.js b/services/static-webserver/client/source/class/osparc/ui/table/rowrenderer/ExtendSelection.js new file mode 100644 index 00000000000..b6ee1a62c17 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/ui/table/rowrenderer/ExtendSelection.js @@ -0,0 +1,42 @@ +/* ************************************************************************ + + 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.ui.table.rowrenderer.ExtendSelection", { + extend: qx.ui.table.rowrenderer.Default, + + members: { + // overridden + updateDataRowElement : function(rowInfo, rowElem) { + this.base(arguments, rowInfo, rowElem); + + const messageCellPos = 2; + // extend collapse row + const style = rowElem.style; + if (rowInfo.selected) { + let messageDiv = rowElem.children.item(messageCellPos) + if (rowElem.children.item(messageCellPos).children.length) { + messageDiv = rowElem.children.item(messageCellPos).children.item(0); + } + const extendedHeight = messageDiv.getBoundingClientRect().height + "px"; + style.height = extendedHeight; + Array.from(rowElem.children).forEach(child => child.style.height = extendedHeight); + } else { + style.height = "19px"; + } + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/widget/logger/LoggerModel.js b/services/static-webserver/client/source/class/osparc/widget/logger/LoggerModel.js index a3679955822..ddadf44332d 100644 --- a/services/static-webserver/client/source/class/osparc/widget/logger/LoggerModel.js +++ b/services/static-webserver/client/source/class/osparc/widget/logger/LoggerModel.js @@ -106,6 +106,10 @@ qx.Class.define("osparc.widget.logger.LoggerModel", { return this.__rawData; }, + getFilteredRows: function() { + return this.__filteredData; + }, + addRows: function(newRows) { newRows.forEach(newRow => { const levelColor = this.self().getLevelColor(newRow.logLevel); diff --git a/services/static-webserver/client/source/class/osparc/widget/logger/LoggerView.js b/services/static-webserver/client/source/class/osparc/widget/logger/LoggerView.js index c15b995a113..b95a7434858 100644 --- a/services/static-webserver/client/source/class/osparc/widget/logger/LoggerView.js +++ b/services/static-webserver/client/source/class/osparc/widget/logger/LoggerView.js @@ -102,6 +102,10 @@ qx.Class.define("osparc.widget.logger.LoggerView", { 50: "ERROR" // CRITICAL }, + printRow: function(rowData) { + return `${rowData.timeStamp} ${this.self().logLevel2Str(rowData.logLevel)} ${rowData.nodeId} ${rowData.label}: ${rowData.msg}`; + }, + logLevel2Str: function(logLevel) { const pairFound = Object.entries(this.LOG_LEVELS).find(pair => pair[1] === logLevel); if (pairFound && pairFound.length) { @@ -177,7 +181,7 @@ qx.Class.define("osparc.widget.logger.LoggerView", { }); control.bind("value", this, "lockLogs"); control.bind("value", control, "icon", { - converter: val => val ? "@FontAwesome5Solid/lock/14" : "@FontAwesome5Solid/lock-open/14" + converter: val => val ? "@FontAwesome5Solid/lock-open/14" : "@FontAwesome5Solid/lock/14" }); const toolbar = this.getChildControl("toolbar"); toolbar.add(control); @@ -194,6 +198,16 @@ qx.Class.define("osparc.widget.logger.LoggerView", { toolbar.add(control); break; } + case "copy-selected-to-clipboard": { + const toolbar = this.getChildControl("toolbar"); + control = new qx.ui.form.Button().set({ + icon: "@FontAwesome5Solid/file/14", + toolTipText: this.tr("Copy Selected log to clipboard"), + appearance: "toolbar-button" + }); + toolbar.add(control); + break; + } case "download-logs-button": { const toolbar = this.getChildControl("toolbar"); control = new qx.ui.form.Button().set({ @@ -213,9 +227,7 @@ qx.Class.define("osparc.widget.logger.LoggerView", { const toolbar = this.getChildControl("toolbar"); const pinNode = this.getChildControl("pin-node"); - pinNode.addListener("changeValue", e => { - this.__currentNodeClicked(e.getData()); - }, this); + pinNode.addListener("changeValue", e => this.__pinChanged(e.getData()), this); const textFilterField = this.__textFilterField = this.getChildControl("filter-text"); textFilterField.addListener("changeValue", this.__applyFilters, this); @@ -233,6 +245,10 @@ qx.Class.define("osparc.widget.logger.LoggerView", { copyToClipboardButton.addListener("execute", () => this.__copyLogsToClipboard(), this); toolbar.add(copyToClipboardButton); + const copySelectedToClipboardButton = this.getChildControl("copy-selected-to-clipboard"); + copySelectedToClipboardButton.addListener("execute", () => this.__copySelectedLogToClipboard(), this); + toolbar.add(copySelectedToClipboardButton); + const downloadButton = this.getChildControl("download-logs-button"); downloadButton.addListener("execute", () => this.downloadLogs(), this); toolbar.add(downloadButton); @@ -256,6 +272,7 @@ qx.Class.define("osparc.widget.logger.LoggerView", { showCellFocusIndicator: false, forceLineHeight: false }); + // alwaysUpdateCells osparc.utils.Utils.setIdToWidget(table, "logsViewer"); const colModel = table.getTableColumnModel(); colModel.setDataCellRenderer(this.self().POS.TIMESTAMP, new osparc.ui.table.cellrenderer.Html().set({ @@ -263,22 +280,29 @@ qx.Class.define("osparc.widget.logger.LoggerView", { })); colModel.setDataCellRenderer(this.self().POS.ORIGIN, new qx.ui.table.cellrenderer.Html()); colModel.setDataCellRenderer(this.self().POS.MESSAGE, new osparc.ui.table.cellrenderer.Html().set({ - defaultCellStyle: "user-select: text" + defaultCellStyle: "user-select: text; text-wrap: wrap" })); let resizeBehavior = colModel.getBehavior(); resizeBehavior.setWidth(this.self().POS.TIMESTAMP, 80); resizeBehavior.setWidth(this.self().POS.ORIGIN, 100); + table.setDataRowRenderer(new osparc.ui.table.rowrenderer.ExtendSelection(table)); + this.__applyFilters(); return table; }, + filterByNode: function(nodeId) { + this.setCurrentNodeId(nodeId); + this.getChildControl("pin-node").setValue(true); + }, + __currentNodeIdChanged: function() { this.getChildControl("pin-node").setValue(false); }, - __currentNodeClicked: function(checked) { + __pinChanged: function(checked) { if (checked) { const currentNodeId = this.getCurrentNodeId(); this.__nodeSelected(currentNodeId); @@ -294,8 +318,8 @@ qx.Class.define("osparc.widget.logger.LoggerView", { __getLogsString: function() { let logs = ""; - this.__loggerModel.getRows().forEach(row => { - logs += `${row.timeStamp} ${this.self().logLevel2Str(row.logLevel)} ${row.nodeId} ${row.label}: ${row.msg} \n`; + this.__loggerModel.getFilteredRows().forEach(rowData => { + logs += this.self().printRow(rowData) + "\n"; }); return logs; }, @@ -304,6 +328,14 @@ qx.Class.define("osparc.widget.logger.LoggerView", { osparc.utils.Utils.copyTextToClipboard(this.__getLogsString()); }, + __copySelectedLogToClipboard: function() { + const sel = this.__loggerTable.getSelectionModel().getAnchorSelectionIndex(); + if (sel > -1) { + const rowData = this.__loggerModel.getRowData(sel); + osparc.utils.Utils.copyTextToClipboard(this.self().printRow(rowData)); + } + }, + downloadLogs: function() { const logs = this.__getLogsString(); osparc.utils.Utils.downloadContent("data:text/plain;charset=utf-8," + logs, "logs.log"); diff --git a/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js b/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js index fd49333a966..88dc0f97865 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js @@ -73,7 +73,8 @@ qx.Class.define("osparc.workbench.NodeUI", { }, events: { - "updateNodeDecorator": "qx.event.type.Event" + "updateNodeDecorator": "qx.event.type.Event", + "requestOpenLogger": "qx.event.type.Event", }, members: { @@ -146,6 +147,20 @@ qx.Class.define("osparc.workbench.NodeUI", { } const nodeStatus = new osparc.ui.basic.NodeStatusUI(this.getNode()); control.add(nodeStatus); + const statusLabel = nodeStatus.getChildControl("label"); + const requestOpenLogger = () => this.fireEvent("requestOpenLogger"); + const evaluateLabel = () => { + const failed = statusLabel.getValue() === "Failed"; + statusLabel.setCursor(failed ? "pointer" : "auto"); + if (nodeStatus.hasListener("tap")) { + nodeStatus.removeListener("tap", requestOpenLogger); + } + if (failed) { + nodeStatus.addListener("tap", requestOpenLogger); + } + }; + evaluateLabel(); + statusLabel.addListener("changeValue", evaluateLabel); this.add(control, { row: 0, column: 1 diff --git a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js index a8f03d648e8..4f2928f4cf9 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js @@ -91,7 +91,8 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { "removeNode": "qx.event.type.Data", "removeNodes": "qx.event.type.Data", "removeEdge": "qx.event.type.Data", - "changeSelectedNode": "qx.event.type.Data" + "changeSelectedNode": "qx.event.type.Data", + "requestOpenLogger": "qx.event.type.Data", }, properties: { @@ -492,6 +493,7 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { __addNodeListeners: function(nodeUI) { nodeUI.addListener("updateNodeDecorator", () => this.__updateNodeUIPos(nodeUI), this); + nodeUI.addListener("requestOpenLogger", () => this.fireDataEvent("requestOpenLogger", nodeUI.getNodeId()), this); nodeUI.addListener("nodeMovingStart", () => { this.__selectNode(nodeUI);