diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js index 369c1de7fa4d..1719c8187dd4 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -1209,6 +1209,10 @@ qx.Class.define("osparc.data.Resources", { method: "GET", url: statics.API + "/storage/locations/{locationId}/paths?file_filter={path}&cursor={cursor}&size=1000" }, + multiDownload: { + method: "POST", + url: statics.API + "/storage/locations/{locationId}/export-data" + }, batchDelete: { method: "POST", url: statics.API + "/storage/locations/{locationId}/-/paths:batchDelete" diff --git a/services/static-webserver/client/source/class/osparc/file/FileLabelWithActions.js b/services/static-webserver/client/source/class/osparc/file/FileLabelWithActions.js index 86bd7eced16d..21fb0d39cad4 100644 --- a/services/static-webserver/client/source/class/osparc/file/FileLabelWithActions.js +++ b/services/static-webserver/client/source/class/osparc/file/FileLabelWithActions.js @@ -110,7 +110,8 @@ qx.Class.define("osparc.file.FileLabelWithActions", { if (selectedItem) { this.__selection = [selectedItem]; const isFile = osparc.file.FilesTree.isFile(selectedItem); - this.getChildControl("download-button").setEnabled(isFile); + const isMultiDownloadEnabled = osparc.utils.DisabledPlugins.isMultiDownloadEnabled(); + this.getChildControl("download-button").setEnabled(isFile || isMultiDownloadEnabled); // folders can also be downloaded this.getChildControl("delete-button").setEnabled(true); // folders can also be deleted this.getChildControl("selected-label").setValue(selectedItem.getLabel()); } else { @@ -142,16 +143,23 @@ qx.Class.define("osparc.file.FileLabelWithActions", { }, __retrieveURLAndDownloadSelected: function() { + const isMultiDownloadEnabled = osparc.utils.DisabledPlugins.isMultiDownloadEnabled(); if (this.isMultiSelect()) { - this.__selection.forEach(selection => { - if (selection && osparc.file.FilesTree.isFile(selection)) { - this.__retrieveURLAndDownloadFile(selection); - } - }); + if (this.__selection.length === 1 && osparc.file.FilesTree.isFile(this.__selection[0])) { + this.__retrieveURLAndDownloadFile(this.__selection[0]); + } else if (this.__selection.length > 1 && isMultiDownloadEnabled) { + const paths = this.__selection.map(item => item.getPath()); + this.__retrieveURLAndExportData(paths); + } } else if (this.__selection.length) { const selection = this.__selection[0]; - if (selection && osparc.file.FilesTree.isFile(selection)) { - this.__retrieveURLAndDownloadFile(selection); + if (selection) { + if (osparc.file.FilesTree.isFile(selection)) { + this.__retrieveURLAndDownloadFile(selection); + } else if (isMultiDownloadEnabled) { + const paths = [selection.getPath()]; + this.__retrieveURLAndExportData(paths); + } } } }, @@ -167,6 +175,15 @@ qx.Class.define("osparc.file.FileLabelWithActions", { }); }, + __retrieveURLAndExportData: function(paths) { + const dataStore = osparc.store.Data.getInstance(); + const fetchPromise = dataStore.exportData(paths); + const pollTasks = osparc.store.PollTasks.getInstance(); + pollTasks.createPollingTask(fetchPromise) + .then(task => this.__exportDataTaskReceived(task)) + .catch(err => osparc.FlashMessenger.logError(err, this.tr("Unsuccessful files download"))); + }, + __deleteSelected: function() { const toBeDeleted = []; let isFolderSelected = false; @@ -223,63 +240,127 @@ qx.Class.define("osparc.file.FileLabelWithActions", { const fetchPromise = dataStore.deleteFiles(paths); const pollTasks = osparc.store.PollTasks.getInstance(); pollTasks.createPollingTask(fetchPromise) - .then(task => { - const taskUI = new osparc.task.TaskUI(); - taskUI.setIcon("@FontAwesome5Solid/trash/14"); - taskUI.setTitle(this.tr("Deleting files")); - taskUI.setTask(task); - osparc.task.TasksContainer.getInstance().addTaskUI(taskUI); - - const progressWindow = new osparc.ui.window.Progress( - this.tr("Delete files"), - "@FontAwesome5Solid/trash/14", - this.tr("Deleting files..."), - ); - if (task.getAbortHref()) { - const cancelButton = progressWindow.addCancelButton(); - cancelButton.setLabel(this.tr("Ignore")); - const abortButton = new qx.ui.form.Button().set({ - label: this.tr("Cancel"), - center: true, - minWidth: 100, - }); - abortButton.addListener("execute", () => task.abortRequested()); - progressWindow.addButton(abortButton); - abortButton.set({ - appearance: "danger-button", - }); + .then(task => this.__deleteTaskReceived(task, paths)) + .catch(err => osparc.FlashMessenger.logError(err, this.tr("Unsuccessful files deletion"))); + } + }, + + __exportDataTaskReceived: function(task) { + const exportDataTaskUI = new osparc.task.ExportData(); + exportDataTaskUI.setTask(task); + osparc.task.TasksContainer.getInstance().addTaskUI(exportDataTaskUI); + + const progressWindow = new osparc.ui.window.Progress( + this.tr("Downloading files"), + "@FontAwesome5Solid/download/14", + this.tr("Downloading files..."), + ); + if (task.getAbortHref()) { + const cancelButton = progressWindow.addCancelButton(); + cancelButton.setLabel(this.tr("Ignore")); + const abortButton = new qx.ui.form.Button().set({ + label: this.tr("Cancel"), + center: true, + minWidth: 100, + }); + abortButton.addListener("execute", () => task.abortRequested()); + progressWindow.addButton(abortButton); + abortButton.set({ + appearance: "danger-button", + }); + } + progressWindow.open(); + + task.addListener("updateReceived", e => { + const data = e.getData(); + if (data["task_progress"]) { + if ("message" in data["task_progress"] && data["task_progress"]["message"]) { + progressWindow.setMessage(data["task_progress"]["message"]); + } + progressWindow.setProgress(osparc.data.PollTask.extractProgress(data) * 100); + } + }, this); + task.addListener("resultReceived", e => { + const taskData = e.getData(); + if (taskData["result"]) { + const params = { + url: { + locationId: 0, + fileUuid: encodeURIComponent(taskData["result"]), } - progressWindow.open(); - - const finished = () => { - progressWindow.close(); - }; - - task.addListener("updateReceived", e => { - const data = e.getData(); - if (data["task_progress"]) { - if ("message" in data["task_progress"] && data["task_progress"]["message"]) { - progressWindow.setMessage(data["task_progress"]["message"]); - } - progressWindow.setProgress(osparc.data.PollTask.extractProgress(data) * 100); + }; + osparc.data.Resources.fetch("storageLink", "getOne", params) + .then(data => { + if (data && data.link) { + const fileName = taskData["result"].split("/").pop(); + osparc.utils.Utils.downloadLink(data.link, "GET", fileName); } - }, this); - task.addListener("resultReceived", e => { - finished(); - osparc.FlashMessenger.logAs(this.tr("Items successfully deleted"), "INFO"); - this.fireDataEvent("pathsDeleted", paths); - }); - task.addListener("taskAborted", () => { - finished(); - osparc.FlashMessenger.logAs(this.tr("Deletion aborted"), "WARNING"); - }); - task.addListener("pollingError", e => { - const err = e.getData(); - osparc.FlashMessenger.logError(err); - }); - }) - .catch(err => osparc.FlashMessenger.logError(err, this.tr("Unsuccessful files deletion"))); + }) + } + progressWindow.close(); + }); + task.addListener("taskAborted", () => { + osparc.FlashMessenger.logAs(this.tr("Download aborted"), "WARNING"); + progressWindow.close(); + }); + task.addListener("pollingError", e => { + const err = e.getData(); + osparc.FlashMessenger.logError(err); + progressWindow.close(); + }); + }, + + __deleteTaskReceived: function(task, paths) { + const taskUI = new osparc.task.TaskUI(); + taskUI.setIcon("@FontAwesome5Solid/trash/14"); + taskUI.setTitle(this.tr("Deleting files")); + taskUI.setTask(task); + osparc.task.TasksContainer.getInstance().addTaskUI(taskUI); + + const progressWindow = new osparc.ui.window.Progress( + this.tr("Delete files"), + "@FontAwesome5Solid/trash/14", + this.tr("Deleting files..."), + ); + if (task.getAbortHref()) { + const cancelButton = progressWindow.addCancelButton(); + cancelButton.setLabel(this.tr("Ignore")); + const abortButton = new qx.ui.form.Button().set({ + label: this.tr("Cancel"), + center: true, + minWidth: 100, + }); + abortButton.addListener("execute", () => task.abortRequested()); + progressWindow.addButton(abortButton); + abortButton.set({ + appearance: "danger-button", + }); } + progressWindow.open(); + + task.addListener("updateReceived", e => { + const data = e.getData(); + if (data["task_progress"]) { + if ("message" in data["task_progress"] && data["task_progress"]["message"]) { + progressWindow.setMessage(data["task_progress"]["message"]); + } + progressWindow.setProgress(osparc.data.PollTask.extractProgress(data) * 100); + } + }, this); + task.addListener("resultReceived", e => { + osparc.FlashMessenger.logAs(this.tr("Items successfully deleted"), "INFO"); + this.fireDataEvent("pathsDeleted", paths); + progressWindow.close(); + }); + task.addListener("taskAborted", () => { + osparc.FlashMessenger.logAs(this.tr("Deletion aborted"), "WARNING"); + progressWindow.close(); + }); + task.addListener("pollingError", e => { + const err = e.getData(); + osparc.FlashMessenger.logError(err); + progressWindow.close(); + }); }, } }); diff --git a/services/static-webserver/client/source/class/osparc/store/Data.js b/services/static-webserver/client/source/class/osparc/store/Data.js index 8353c24ab028..a1fd4f47add1 100644 --- a/services/static-webserver/client/source/class/osparc/store/Data.js +++ b/services/static-webserver/client/source/class/osparc/store/Data.js @@ -234,6 +234,22 @@ qx.Class.define("osparc.store.Data", { return true; }, + exportData: function(paths) { + if (!osparc.data.Permissions.getInstance().canDo("study.node.data.delete", true)) { + return null; + } + + const params = { + url: { + locationId: 0, + }, + data: { + paths, + } + }; + return osparc.data.Resources.fetch("storagePaths", "multiDownload", params); + }, + deleteFiles: function(paths) { if (!osparc.data.Permissions.getInstance().canDo("study.node.data.delete", true)) { return null; diff --git a/services/static-webserver/client/source/class/osparc/store/StaticInfo.js b/services/static-webserver/client/source/class/osparc/store/StaticInfo.js index 341827779eb6..92c4b42bbd04 100644 --- a/services/static-webserver/client/source/class/osparc/store/StaticInfo.js +++ b/services/static-webserver/client/source/class/osparc/store/StaticInfo.js @@ -121,6 +121,10 @@ qx.Class.define("osparc.store.StaticInfo", { return wsStaticData[key]; } return null; - } + }, + + isDevFeaturesEnabled: function() { + return this.getValue("webserverDevFeaturesEnabled"); + }, } }); diff --git a/services/static-webserver/client/source/class/osparc/task/ExportData.js b/services/static-webserver/client/source/class/osparc/task/ExportData.js new file mode 100644 index 000000000000..a8c46a34f0ed --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/task/ExportData.js @@ -0,0 +1,31 @@ +/* ************************************************************************ + + 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.task.ExportData", { + extend: osparc.task.TaskUI, + + construct: function() { + this.base(arguments); + + this.setIcon(this.self().ICON+"/14"); + this.setTitle(this.tr("Downloading files:")); + }, + + statics: { + ICON: "@FontAwesome5Solid/download" + }, +}); diff --git a/services/static-webserver/client/source/class/osparc/task/TaskUI.js b/services/static-webserver/client/source/class/osparc/task/TaskUI.js index bbfbc5c9b0d4..f19322c17864 100644 --- a/services/static-webserver/client/source/class/osparc/task/TaskUI.js +++ b/services/static-webserver/client/source/class/osparc/task/TaskUI.js @@ -121,35 +121,38 @@ qx.Class.define("osparc.task.TaskUI", { }, __applyTask: function(task) { - task.addListener("updateReceived", e => { - const data = e.getData(); - if (data["task_progress"]) { - if ("message" in data["task_progress"] && !this.getChildControl("subtitle").getValue()) { - this.getChildControl("subtitle").setValue(data["task_progress"]["message"]); - } - this.getChildControl("progress").setValue((osparc.data.PollTask.extractProgress(data) * 100) + "%"); - } - }, this); + task.addListener("updateReceived", e => this._updateHandler(e.getData()), this); const stopButton = this.getChildControl("stop"); task.bind("abortHref", stopButton, "visibility", { converter: abortHref => abortHref ? "visible" : "excluded" }); - stopButton.addListener("tap", () => { - const msg = this.tr("Are you sure you want to cancel the task?"); - const win = new osparc.ui.window.Confirmation(msg).set({ - caption: this.tr("Cancel Task"), - confirmText: this.tr("Cancel"), - confirmAction: "delete" - }); - win.getCancelButton().setLabel(this.tr("Ignore")); - win.center(); - win.open(); - win.addListener("close", () => { - if (win.getConfirmed()) { - task.abortRequested(); - } - }, this); + stopButton.addListener("tap", () => this._abortHandler(), this); + }, + + _updateHandler: function(data) { + if (data["task_progress"]) { + if ("message" in data["task_progress"] && !this.getChildControl("subtitle").getValue()) { + this.getChildControl("subtitle").setValue(data["task_progress"]["message"]); + } + this.getChildControl("progress").setValue((osparc.data.PollTask.extractProgress(data) * 100) + "%"); + } + }, + + _abortHandler: function() { + const msg = this.tr("Are you sure you want to cancel the task?"); + const win = new osparc.ui.window.Confirmation(msg).set({ + caption: this.tr("Cancel Task"), + confirmText: this.tr("Cancel"), + confirmAction: "delete" + }); + win.getCancelButton().setLabel(this.tr("Ignore")); + win.center(); + win.open(); + win.addListener("close", () => { + if (win.getConfirmed()) { + this.getTask().abortRequested(); + } }, this); }, diff --git a/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js b/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js index 233ba211e05d..37fcce0187bb 100644 --- a/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js +++ b/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js @@ -29,7 +29,6 @@ qx.Class.define("osparc.utils.DisabledPlugins", { VERSION_CONTROL: "WEBSERVER_VERSION_CONTROL", META_MODELING: "WEBSERVER_META_MODELING", LICENSES: "WEBSERVER_LICENSES", - SHARE_WITH_EMAIL: "WEBSERVER_SHARE_WITH_EMAIL", isExportDisabled: function() { return this.__isPluginDisabled(this.EXPORT); @@ -54,8 +53,11 @@ qx.Class.define("osparc.utils.DisabledPlugins", { }, isShareWithEmailEnabled: function() { - // return !this.__isPluginDisabled(this.SHARE_WITH_EMAIL); - return new Promise(resolve => resolve(osparc.utils.Utils.isDevelopmentPlatform())); + return osparc.store.StaticInfo.getInstance().isDevFeaturesEnabled(); + }, + + isMultiDownloadEnabled: function() { + return osparc.store.StaticInfo.getInstance().isDevFeaturesEnabled(); }, isJobsEnabled: function() {