diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js index 63de0b65a95b..53d3fe4eeef5 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js @@ -141,13 +141,29 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { __classifiersPage: null, __qualityPage: null, - __addOpenButton: function(page) { + __addToolbarButtons: function(page) { const resourceData = this.__resourceData; const toolbar = this.self().createToolbar(); page.addToHeader(toolbar); - if (["study", "template"].includes(this.__resourceData["resourceType"])) { + if (["study", "template", "tutorial"].includes(resourceData["resourceType"])) { + const cantReadServices = osparc.study.Utils.getCantReadServices(resourceData["services"]); + if (cantReadServices.length) { + const requestAccessButton = new qx.ui.form.Button(this.tr("Request Apps Access")); + osparc.dashboard.resources.pages.BasePage.decorateHeaderButton(requestAccessButton); + requestAccessButton.set({ + minWidth: 170, + maxWidth: 170, + }); + requestAccessButton.addListener("execute", () => { + osparc.share.RequestServiceAccess.openRequestAccess(cantReadServices); + }); + toolbar.add(requestAccessButton); + } + } + + if (this.__resourceData["resourceType"] === "study") { const payDebtButton = new qx.ui.form.Button(this.tr("Credits required")); page.payDebtButton = payDebtButton; osparc.dashboard.resources.pages.BasePage.decorateHeaderButton(payDebtButton); @@ -405,7 +421,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const title = this.tr("Overview"); const iconSrc = "@FontAwesome5Solid/info/22"; const page = this.__infoPage = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id); - this.__addOpenButton(page); + this.__addToolbarButtons(page); const lazyLoadContent = () => { const resourceData = this.__resourceData; @@ -446,7 +462,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const title = this.tr("Billing Settings"); const iconSrc = "@FontAwesome5Solid/cogs/22"; const page = this.__billingSettings = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id); - this.__addOpenButton(page); + this.__addToolbarButtons(page); if (resourceData["resourceType"] === "study") { const canBeOpened = osparc.study.Utils.canShowBillingOptions(resourceData); @@ -473,7 +489,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const title = this.tr("Tiers"); const iconSrc = "@FontAwesome5Solid/server/22"; const page = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id); - this.__addOpenButton(page); + this.__addToolbarButtons(page); const lazyLoadContent = () => { const pricingUnitsList = new osparc.service.PricingUnitsList(resourceData); @@ -502,7 +518,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const title = this.tr("Pipeline View"); const iconSrc = "@FontAwesome5Solid/eye/22"; const page = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id); - this.__addOpenButton(page); + this.__addToolbarButtons(page); const studyData = this.__resourceData; const enabled = osparc.study.Utils.canShowPreview(studyData); @@ -528,7 +544,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const title = this.tr("Conversations"); const iconSrc = "@FontAwesome5Solid/comments/22"; const page = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id); - this.__addOpenButton(page); + this.__addToolbarButtons(page); const lazyLoadContent = () => { const conversations = new osparc.study.Conversations(resourceData); @@ -544,7 +560,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const title = this.tr("Sharing"); const iconSrc = "@FontAwesome5Solid/share-alt/22"; const page = this.__permissionsPage = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id); - this.__addOpenButton(page); + this.__addToolbarButtons(page); const lazyLoadContent = () => { const resourceData = this.__resourceData; @@ -587,7 +603,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const title = this.tr("Classifiers"); const iconSrc = "@FontAwesome5Solid/search/22"; const page = this.__classifiersPage = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id); - this.__addOpenButton(page); + this.__addToolbarButtons(page); const lazyLoadContent = () => { const resourceData = this.__resourceData; @@ -625,7 +641,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const title = this.tr("Quality"); const iconSrc = "@FontAwesome5Solid/star-half/22"; const page = this.__qualityPage = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id); - this.__addOpenButton(page); + this.__addToolbarButtons(page); const lazyLoadContent = () => { const qualityEditor = new osparc.metadata.QualityEditor(resourceData); @@ -655,7 +671,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const title = this.tr("Tags"); const iconSrc = "@FontAwesome5Solid/tags/22"; const page = this.__tagsPage = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id); - this.__addOpenButton(page); + this.__addToolbarButtons(page); const lazyLoadContent = () => { const tagManager = new osparc.form.tag.TagManager(resourceData); @@ -681,7 +697,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const title = this.tr("Services Updates"); const iconSrc = "@MaterialIcons/update/24"; const page = this.__servicesUpdatePage = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id); - this.__addOpenButton(page); + this.__addToolbarButtons(page); const studyData = this.__resourceData; const enabled = osparc.study.Utils.canShowServiceUpdates(studyData); @@ -713,7 +729,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const title = this.tr("Boot Options"); const iconSrc = "@FontAwesome5Solid/play-circle/22"; const page = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id); - this.__addOpenButton(page); + this.__addToolbarButtons(page); const studyData = this.__resourceData; const enabled = osparc.study.Utils.canShowServiceBootOptions(studyData); diff --git a/services/static-webserver/client/source/class/osparc/share/CollaboratorsStudy.js b/services/static-webserver/client/source/class/osparc/share/CollaboratorsStudy.js index a595b47320df..124d275ae76c 100644 --- a/services/static-webserver/client/source/class/osparc/share/CollaboratorsStudy.js +++ b/services/static-webserver/client/source/class/osparc/share/CollaboratorsStudy.js @@ -235,32 +235,7 @@ qx.Class.define("osparc.share.CollaboratorsStudy", { if (gids.length === 0) { return; } - - const promises = []; - gids.forEach(gid => { - const params = { - url: { - "studyId": this._serializedDataCopy["uuid"], - "gid": gid - } - }; - promises.push(osparc.data.Resources.fetch("studies", "checkShareePermissions", params)); - }); - Promise.all(promises) - .then(values => { - const noAccessible = values.filter(value => value["accessible"] === false); - if (noAccessible.length) { - const shareePermissions = new osparc.share.ShareePermissions(noAccessible); - const win = osparc.ui.window.Window.popUpInWindow(shareePermissions, this.tr("Sharee permissions"), 500, 500, "@FontAwesome5Solid/exclamation-triangle/14").set({ - clickAwayClose: false, - resizable: true, - showClose: true - }); - win.getChildControl("icon").set({ - textColor: "warning-yellow" - }); - } - }); + osparc.share.ShareePermissions.checkShareePermissions(this._serializedDataCopy["uuid"], gids); } } }); diff --git a/services/static-webserver/client/source/class/osparc/share/RequestServiceAccess.js b/services/static-webserver/client/source/class/osparc/share/RequestServiceAccess.js new file mode 100644 index 000000000000..7aa5718d6636 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/share/RequestServiceAccess.js @@ -0,0 +1,92 @@ +/* + * oSPARC - The SIMCORE frontend - https://osparc.io + * Copyright: 2025 IT'IS Foundation - https://itis.swiss + * License: MIT - https://opensource.org/licenses/MIT + * Authors: Odei Maiz (odeimaiz) + */ + +qx.Class.define("osparc.share.RequestServiceAccess", { + extend: qx.ui.core.Widget, + + construct: function(cantReadServicesData) { + this.base(arguments); + + this._setLayout(new qx.ui.layout.VBox(25)); + + this.__populateLayout(cantReadServicesData); + }, + + statics: { + openRequestAccess: function(cantReadServicesData) { + const requestServiceAccess = new osparc.share.RequestServiceAccess(cantReadServicesData); + const caption = qx.locale.Manager.tr("Request Apps Access"); + osparc.ui.window.Window.popUpInWindow(requestServiceAccess, caption, 600, 400).set({ + clickAwayClose: false, + resizable: true, + showClose: true + }); + } + }, + + members: { + __populateLayout: function(cantReadServicesData) { + const text = this.tr("In order to open the project, the following users/groups need to give you access to some apps. Please contact the app owner:"); + this._add(new qx.ui.basic.Label().set({ + value: text, + font: "text-14", + rich: true, + wrap: true + })); + + const grid = new qx.ui.layout.Grid(20, 10); + const layout = new qx.ui.container.Composite(grid); + this._add(layout); + + // Header + layout.add(new qx.ui.basic.Label(this.tr("Owner")), { + row: 0, + column: 0 + }); + layout.add(new qx.ui.basic.Label(this.tr("Email")), { + row: 0, + column: 1 + }); + layout.add(new qx.ui.basic.Label(this.tr("App")), { + row: 0, + column: 2 + }); + + // Populate the grid with the cantReadServicesData + cantReadServicesData.forEach((cantReadServiceData, idx) => { + const group = osparc.store.Groups.getInstance().getGroup(cantReadServiceData["owner"]); + if (group) { + const username = new qx.ui.basic.Label(group.getLabel()).set({ + rich: true, + selectable: true, + }); + layout.add(username, { + row: idx+1, + column: 0 + }); + const email = new qx.ui.basic.Label(group.getEmail()).set({ + rich: true, + selectable: true, + }); + layout.add(email, { + row: idx+1, + column: 1 + }); + const appLabel = new qx.ui.basic.Label().set({ + value: `${cantReadServiceData["key"]}:${osparc.service.Utils.extractVersionDisplay(cantReadServiceData["release"])}`, + rich: true, + selectable: true, + }); + layout.add(appLabel, { + row: idx+1, + column: 2 + }); + } + }); + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/share/ShareePermissions.js b/services/static-webserver/client/source/class/osparc/share/ShareePermissions.js index bc8c92cbe991..2a2a2bf9909a 100644 --- a/services/static-webserver/client/source/class/osparc/share/ShareePermissions.js +++ b/services/static-webserver/client/source/class/osparc/share/ShareePermissions.js @@ -5,6 +5,12 @@ * Authors: Odei Maiz (odeimaiz) */ +/** + * Data structure for showing sharee permissions. Array of objects with the following keys + * - accessible: boolean + * - gid: string // sharee group id + * - inaccessible_services: Array of objects with keys "key" and "version" + */ qx.Class.define("osparc.share.ShareePermissions", { extend: qx.ui.core.Widget, @@ -16,6 +22,37 @@ qx.Class.define("osparc.share.ShareePermissions", { this.__populateLayout(shareesData); }, + statics: { + checkShareePermissions: function(studyId, gids) { + const promises = []; + gids.forEach(gid => { + const params = { + url: { + studyId, + gid, + } + }; + promises.push(osparc.data.Resources.fetch("studies", "checkShareePermissions", params)); + }); + Promise.all(promises) + .then(shareesData => { + const inaccessibleShareesData = shareesData.filter(value => value["accessible"] === false); + if (inaccessibleShareesData.length) { + const shareePermissions = new osparc.share.ShareePermissions(inaccessibleShareesData); + const caption = qx.locale.Manager.tr("Sharee permissions"); + const win = osparc.ui.window.Window.popUpInWindow(shareePermissions, caption, 500, 500, "@FontAwesome5Solid/exclamation-triangle/14").set({ + clickAwayClose: false, + resizable: true, + showClose: true + }); + win.getChildControl("icon").set({ + textColor: "warning-yellow" + }); + } + }); + }, + }, + members: { __populateLayout: function(shareesData) { const text = this.tr("The following users/groups will not be able to open the shared study, because they don't have access to some services. Please contact the service owner(s) to give permission."); @@ -33,7 +70,7 @@ qx.Class.define("osparc.share.ShareePermissions", { this._add(layout); for (let i=0; i resolve(productIcon)); } else { resolve(osparc.data.model.StudyUI.PIPELINE_ICON); }