diff --git a/services/director-v2/src/simcore_service_director_v2/cli/__init__.py b/services/director-v2/src/simcore_service_director_v2/cli/__init__.py index eb4d050bd80b..f33d59722605 100644 --- a/services/director-v2/src/simcore_service_director_v2/cli/__init__.py +++ b/services/director-v2/src/simcore_service_director_v2/cli/__init__.py @@ -26,7 +26,11 @@ DEFAULT_OUTPUTS_PUSH_ATTEMPTS: Final[int] = 3 DEFAULT_TASK_UPDATE_INTERVAL_S: Final[int] = 1 -main = typer.Typer(name=PROJECT_NAME) +main = typer.Typer( + name=PROJECT_NAME, + pretty_exceptions_enable=False, + pretty_exceptions_show_locals=False, +) _logger = logging.getLogger(__name__) diff --git a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/api/rest/containers_long_running_tasks.py b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/api/rest/containers_long_running_tasks.py index 981edd42f7ad..af857013a821 100644 --- a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/api/rest/containers_long_running_tasks.py +++ b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/api/rest/containers_long_running_tasks.py @@ -124,6 +124,7 @@ async def runs_docker_compose_down_task( settings: Annotated[ApplicationSettings, Depends(get_settings)], shared_store: Annotated[SharedStore, Depends(get_shared_store)], app: Annotated[FastAPI, Depends(get_application)], + mounted_volumes: Annotated[MountedVolumes, Depends(get_mounted_volumes)], ) -> TaskId: assert request # nosec @@ -135,6 +136,7 @@ async def runs_docker_compose_down_task( app=app, shared_store=shared_store, settings=settings, + mounted_volumes=mounted_volumes, ) except TaskAlreadyRunningError as e: return cast(str, e.managed_task.task_id) # type: ignore[attr-defined] # pylint:disable=no-member diff --git a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/cli.py b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/cli.py index 76d663383f6c..e895c3db122c 100644 --- a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/cli.py +++ b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/cli.py @@ -1,8 +1,8 @@ import asyncio import json import logging +from collections.abc import AsyncIterator from contextlib import asynccontextmanager -from typing import AsyncIterator import typer from fastapi import FastAPI @@ -17,7 +17,11 @@ from .modules.outputs import OutputsManager, setup_outputs log = logging.getLogger(__name__) -main = typer.Typer(name=PROJECT_NAME) +main = typer.Typer( + name=PROJECT_NAME, + pretty_exceptions_enable=False, + pretty_exceptions_show_locals=False, +) main.command()(create_settings_command(settings_cls=ApplicationSettings, logger=log)) diff --git a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/long_running_tasks.py b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/long_running_tasks.py index db6deb715a82..4a8f041b80eb 100644 --- a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/long_running_tasks.py +++ b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/long_running_tasks.py @@ -53,7 +53,10 @@ from ..modules.mounted_fs import MountedVolumes from ..modules.notifications._notifications_ports import PortNotifier from ..modules.outputs import OutputsManager, event_propagation_disabled -from .long_running_tasksutils import run_before_shutdown_actions +from .long_running_tasks_utils import ( + ensure_read_permissions_on_user_service_data, + run_before_shutdown_actions, +) from .resource_tracking import send_service_started, send_service_stopped _logger = logging.getLogger(__name__) @@ -237,6 +240,7 @@ async def task_runs_docker_compose_down( app: FastAPI, shared_store: SharedStore, settings: ApplicationSettings, + mounted_volumes: MountedVolumes, ) -> None: if shared_store.compose_spec is None: _logger.warning("No compose-spec was found") @@ -312,6 +316,8 @@ async def _send_resource_tracking_stop(platform_status: SimcorePlatformStatus): await _send_resource_tracking_stop(SimcorePlatformStatus.OK) raise + await ensure_read_permissions_on_user_service_data(mounted_volumes) + await _send_resource_tracking_stop(SimcorePlatformStatus.OK) # removing compose-file spec diff --git a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/long_running_tasksutils.py b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/long_running_tasks_utils.py similarity index 57% rename from services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/long_running_tasksutils.py rename to services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/long_running_tasks_utils.py index d533ebc793de..21d9adaebbb5 100644 --- a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/long_running_tasksutils.py +++ b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/long_running_tasks_utils.py @@ -1,4 +1,7 @@ import logging +import os +from datetime import timedelta +from typing import Final from models_library.callbacks_mapping import UserServiceCommand from servicelib.logging_utils import log_context @@ -9,10 +12,13 @@ ContainerExecTimeoutError, ) from ..models.shared_store import SharedStore -from ..modules.container_utils import run_command_in_container +from ..modules.mounted_fs import MountedVolumes +from .container_utils import run_command_in_container _logger = logging.getLogger(__name__) +_TIMEOUT_PERMISSION_CHANGES: Final[timedelta] = timedelta(minutes=5) + async def run_before_shutdown_actions( shared_store: SharedStore, before_shutdown: list[UserServiceCommand] @@ -40,3 +46,22 @@ async def run_before_shutdown_actions( container_name, exc_info=True, ) + + +async def ensure_read_permissions_on_user_service_data( + mounted_volumes: MountedVolumes, +) -> None: + # Makes sure sidecar has access to all files in the user services. + # The user could have removed read permissions form a file, which will cause an error. + + # NOTE: command runs inside self container since the user service container might not always be running + self_container = os.environ["HOSTNAME"] + for path_to_store in ( # apply changes to otuputs and all state folders + *mounted_volumes.disk_state_paths_iter(), + mounted_volumes.disk_outputs_path, + ): + await run_command_in_container( + self_container, + command=f"chmod -R o+rX '{path_to_store}'", + timeout=_TIMEOUT_PERMISSION_CHANGES.total_seconds(), + ) diff --git a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/prometheus_metrics.py b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/prometheus_metrics.py index df5ae853d244..eb7ad93ed9e1 100644 --- a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/prometheus_metrics.py +++ b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/prometheus_metrics.py @@ -107,13 +107,13 @@ async def _update_metrics(self): ) self._metrics_response = MetricsResponse.from_reply(metrics_fetch_result) except ContainerExecContainerNotFoundError as e: - _logger.info( - "Container %s was not found could nto recover metrics", + _logger.debug( + "Container %s was not found could not recover metrics", container_name, ) self._metrics_response = MetricsResponse.from_error(e) except Exception as e: # pylint: disable=broad-exception-caught - _logger.info("Unexpected exception", exc_info=True) + _logger.debug("Could not recover metrics", exc_info=True) self._metrics_response = MetricsResponse.from_error(e) async def _task_metrics_recovery(self) -> None: diff --git a/services/dynamic-sidecar/tests/unit/conftest.py b/services/dynamic-sidecar/tests/unit/conftest.py index ee2c106bb695..47488a06e48e 100644 --- a/services/dynamic-sidecar/tests/unit/conftest.py +++ b/services/dynamic-sidecar/tests/unit/conftest.py @@ -11,6 +11,7 @@ from aiodocker.volumes import DockerVolume from async_asgi_testclient import TestClient from fastapi import FastAPI +from pytest_mock.plugin import MockerFixture from pytest_simcore.helpers.monkeypatch_envs import EnvVarsDict, setenvs_from_dict from simcore_service_dynamic_sidecar.core.application import AppState, create_app from simcore_service_dynamic_sidecar.core.docker_compose_utils import ( @@ -157,3 +158,10 @@ def port_notifier(app: FastAPI) -> PortNotifier: settings.DY_SIDECAR_PROJECT_ID, settings.DY_SIDECAR_NODE_ID, ) + + +@pytest.fixture +def mock_ensure_read_permissions_on_user_service_data(mocker: MockerFixture) -> None: + mocker.patch( + "simcore_service_dynamic_sidecar.modules.long_running_tasks.ensure_read_permissions_on_user_service_data", + ) diff --git a/services/dynamic-sidecar/tests/unit/test_api_rest_containers_long_running_tasks.py b/services/dynamic-sidecar/tests/unit/test_api_rest_containers_long_running_tasks.py index 9c050ae8a0ef..75bc03dab744 100644 --- a/services/dynamic-sidecar/tests/unit/test_api_rest_containers_long_running_tasks.py +++ b/services/dynamic-sidecar/tests/unit/test_api_rest_containers_long_running_tasks.py @@ -520,6 +520,7 @@ def _get_awaitable() -> Awaitable: async def test_containers_down_after_starting( + mock_ensure_read_permissions_on_user_service_data: None, httpx_async_client: AsyncClient, client: Client, compose_spec: str, diff --git a/services/dynamic-sidecar/tests/unit/test_api_rest_workflow_service_metrics.py b/services/dynamic-sidecar/tests/unit/test_api_rest_workflow_service_metrics.py index 662b7033b882..baa77632eb62 100644 --- a/services/dynamic-sidecar/tests/unit/test_api_rest_workflow_service_metrics.py +++ b/services/dynamic-sidecar/tests/unit/test_api_rest_workflow_service_metrics.py @@ -172,7 +172,7 @@ async def _get_task_id_docker_compose_down(httpx_async_client: AsyncClient) -> T def _get_resource_tracking_messages( - mock_core_rabbitmq: dict[str, AsyncMock] + mock_core_rabbitmq: dict[str, AsyncMock], ) -> list[RabbitResourceTrackingMessages]: return [ x[0][1] @@ -200,6 +200,7 @@ async def _wait_for_containers_to_be_running(app: FastAPI) -> None: async def test_service_starts_and_closes_as_expected( + mock_ensure_read_permissions_on_user_service_data: None, mock_core_rabbitmq: dict[str, AsyncMock], app: FastAPI, httpx_async_client: AsyncClient, @@ -383,6 +384,7 @@ async def _mocked_get_container_states( @pytest.mark.parametrize("expected_platform_state", SimcorePlatformStatus) async def test_user_services_crash_when_running( + mock_ensure_read_permissions_on_user_service_data: None, mock_core_rabbitmq: dict[str, AsyncMock], app: FastAPI, httpx_async_client: AsyncClient, diff --git a/services/dynamic-sidecar/tests/unit/test_modules_attribute_monitor.py b/services/dynamic-sidecar/tests/unit/test_modules_attribute_monitor.py index 93ea3d6b9729..5266730830af 100644 --- a/services/dynamic-sidecar/tests/unit/test_modules_attribute_monitor.py +++ b/services/dynamic-sidecar/tests/unit/test_modules_attribute_monitor.py @@ -125,6 +125,7 @@ async def logging_event_handler_observer( ], ) async def test_chown_triggers_event( + mock_ensure_read_permissions_on_user_service_data: None, logging_event_handler_observer: None, fake_dy_volumes_mount_dir: Path, command_template: str, diff --git a/services/efs-guardian/src/simcore_service_efs_guardian/services/background_tasks_setup.py b/services/efs-guardian/src/simcore_service_efs_guardian/services/background_tasks_setup.py index 2f42bc5d870d..e1480f84b205 100644 --- a/services/efs-guardian/src/simcore_service_efs_guardian/services/background_tasks_setup.py +++ b/services/efs-guardian/src/simcore_service_efs_guardian/services/background_tasks_setup.py @@ -2,7 +2,6 @@ import logging from collections.abc import Awaitable, Callable from datetime import timedelta -from typing import TypedDict from fastapi import FastAPI from servicelib.async_utils import cancel_wait_task @@ -12,44 +11,31 @@ from .background_tasks import removal_policy_task from .modules.redis import get_redis_lock_client - -@exclusive_periodic( - get_redis_lock_client, - task_interval=timedelta(hours=1), - retry_after=timedelta(minutes=5), -) -async def periodic_removal_policy_task(app: FastAPI) -> None: - await removal_policy_task(app) - - _logger = logging.getLogger(__name__) -class EfsGuardianBackgroundTask(TypedDict): - name: str - task_func: Callable - - -_EFS_GUARDIAN_BACKGROUND_TASKS = [ - EfsGuardianBackgroundTask( - name="efs_removal_policy_task", task_func=periodic_removal_policy_task - ) -] - - def _on_app_startup(app: FastAPI) -> Callable[[], Awaitable[None]]: async def _startup() -> None: with ( - log_context(_logger, logging.INFO, msg="Efs Guardian startup.."), + log_context(_logger, logging.INFO, msg="Efs Guardian background task "), log_catch(_logger, reraise=False), ): - app.state.efs_guardian_background_tasks = [] + app.state.efs_guardian_removal_policy_background_task = None - # Setup periodic tasks - for task in _EFS_GUARDIAN_BACKGROUND_TASKS: - app.state.efs_guardian_background_tasks.append( - asyncio.create_task(task["task_func"](), name=task["name"]) - ) + _logger.info("starting efs guardian removal policy task") + + @exclusive_periodic( + get_redis_lock_client(app), + task_interval=timedelta(hours=1), + retry_after=timedelta(minutes=5), + ) + async def _periodic_removal_policy_task() -> None: + await removal_policy_task(app) + + app.state.efs_guardian_removal_policy_background_task = asyncio.create_task( + _periodic_removal_policy_task(), + name=_periodic_removal_policy_task.__name__, + ) return _startup @@ -63,12 +49,9 @@ async def _stop() -> None: log_catch(_logger, reraise=False), ): assert _app # nosec - if _app.state.efs_guardian_background_tasks: - await asyncio.gather( - *[ - cancel_wait_task(task) - for task in _app.state.efs_guardian_background_tasks - ] + if _app.state.efs_guardian_removal_policy_background_task: + await cancel_wait_task( + _app.state.efs_guardian_removal_policy_background_task ) return _stop diff --git a/services/static-webserver/client/source/class/osparc/dashboard/NewPlusMenu.js b/services/static-webserver/client/source/class/osparc/dashboard/NewPlusMenu.js index 55485b370c41..64ec45195b01 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/NewPlusMenu.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/NewPlusMenu.js @@ -70,6 +70,8 @@ qx.Class.define("osparc.dashboard.NewPlusMenu", { spacingX: 20, }); + osparc.utils.Utils.setIdToWidget(this, "newPlusMenu"); + this.getContentElement().setStyles({ "border-color": qx.theme.manager.Color.getInstance().resolve("strong-main"), }); @@ -207,17 +209,9 @@ qx.Class.define("osparc.dashboard.NewPlusMenu", { __addIcon: function(menuButton, resourceInfo, resourceMetadata) { let source = null; if (resourceInfo && resourceInfo["icon"]) { - // first the one set in the ui_config source = resourceInfo["icon"]; - } else if (resourceMetadata && resourceMetadata["icon"]) { - // second the icon from the resource - source = resourceMetadata["icon"]; - } else if (resourceMetadata && resourceMetadata["thumbnail"]) { - // third the thumbnail from the resource - source = resourceMetadata["thumbnail"]; } else { - // finally product icon - source = osparc.dashboard.CardBase.PRODUCT_ICON; + source = osparc.utils.Utils.getIconFromResource(resourceMetadata); } if (source) { 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 259782e213f9..9d0cb2d1d3cf 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js @@ -188,12 +188,13 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { }); win.center(); win.open(); - win.addListenerOnce("close", () => { + win.addListener("changeConfirmed", e => { if (win.getConfirmed()) { this.openUpdateServices(); } else { this.__openResource(); } + win.close(); }); }, diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceUpgradeHelper.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceUpgradeHelper.js index 882389c1ce09..43f39c554194 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceUpgradeHelper.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceUpgradeHelper.js @@ -37,7 +37,6 @@ qx.Class.define("osparc.dashboard.ResourceUpgradeHelper", { this.bind("secondaryText", secondaryButton, "label"); secondaryButton.addListener("execute", () => { this.setConfirmed(false); - this.close(1); }, this); this.addButton(secondaryButton); @@ -50,7 +49,6 @@ qx.Class.define("osparc.dashboard.ResourceUpgradeHelper", { this.bind("primaryText", primaryButton, "label"); primaryButton.addListener("execute", () => { this.setConfirmed(true); - this.close(1); }, this); const command = new qx.ui.command.Command("Enter"); primaryButton.setCommand(command); @@ -86,7 +84,8 @@ qx.Class.define("osparc.dashboard.ResourceUpgradeHelper", { confirmed: { check: "Boolean", - init: false + init: null, + event: "changeConfirmed" } }, diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsServiceListItem.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsServiceListItem.js index e7b1d8929d7a..ab25f0207173 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsServiceListItem.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsServiceListItem.js @@ -33,7 +33,8 @@ qx.Class.define("osparc.desktop.credits.CreditsServiceListItem", { const name = this.getChildControl("title"); const serviceMetadata = osparc.service.Utils.getLatest(serviceKey); if (serviceMetadata) { - icon.setSource(serviceMetadata["thumbnail"] ? serviceMetadata["thumbnail"] : osparc.dashboard.CardBase.PRODUCT_THUMBNAIL); + const source = osparc.utils.Utils.getIconFromResource(serviceMetadata); + icon.setSource(source); name.setValue(serviceMetadata["name"]); } else { icon.setSource(osparc.dashboard.CardBase.PRODUCT_THUMBNAIL); diff --git a/services/static-webserver/client/source/class/osparc/product/tours/Tours.js b/services/static-webserver/client/source/class/osparc/product/tours/Tours.js index 5216ec483b1d..8f9e061817ad 100644 --- a/services/static-webserver/client/source/class/osparc/product/tours/Tours.js +++ b/services/static-webserver/client/source/class/osparc/product/tours/Tours.js @@ -26,12 +26,18 @@ qx.Class.define("osparc.product.tours.Tours", { statics: { TOURS: { - "s4llite": { - fetchTours: () => osparc.product.tours.Tours.fetchTours("/resource/osparc/tours/s4llite_tours.json") + "osparc": { + fetchTours: () => osparc.product.tours.Tours.fetchTours("/resource/osparc/tours/osparc_tours.json") }, "s4l": { fetchTours: () => osparc.product.tours.Tours.fetchTours("/resource/osparc/tours/s4l_tours.json") }, + "s4lacad": { + fetchTours: () => osparc.product.tours.Tours.fetchTours("/resource/osparc/tours/s4l_tours.json") + }, + "s4llite": { + fetchTours: () => osparc.product.tours.Tours.fetchTours("/resource/osparc/tours/s4llite_tours.json") + }, "tis": { fetchTours: () => osparc.product.tours.Tours.fetchTours("/resource/osparc/tours/tis_tours.json") }, diff --git a/services/static-webserver/client/source/class/osparc/service/StatusUI.js b/services/static-webserver/client/source/class/osparc/service/StatusUI.js index 7fab5f32b70d..dbd7d53cbdcc 100644 --- a/services/static-webserver/client/source/class/osparc/service/StatusUI.js +++ b/services/static-webserver/client/source/class/osparc/service/StatusUI.js @@ -235,7 +235,7 @@ qx.Class.define("osparc.service.StatusUI", { const chip = new osparc.ui.basic.Chip().set({ label: osparc.service.Utils.DEPRECATED_SERVICE_TEXT, icon: osparc.service.StatusUI.getIconSource("deprecated"), - textColor: "contrasted-text-dark", + textColor: "text-on-warning", backgroundColor: osparc.service.StatusUI.getColor("deprecated"), allowGrowX: false }); @@ -246,7 +246,7 @@ qx.Class.define("osparc.service.StatusUI", { const chip = new osparc.ui.basic.Chip().set({ label: osparc.service.Utils.RETIRED_SERVICE_TEXT, icon: osparc.service.StatusUI.getIconSource("retired"), - textColor: "contrasted-text-dark", + textColor: "text-on-warning", backgroundColor: osparc.service.StatusUI.getColor("retired"), allowGrowX: false }); diff --git a/services/static-webserver/client/source/class/osparc/service/Utils.js b/services/static-webserver/client/source/class/osparc/service/Utils.js index 48639568506a..d58094d5900b 100644 --- a/services/static-webserver/client/source/class/osparc/service/Utils.js +++ b/services/static-webserver/client/source/class/osparc/service/Utils.js @@ -234,8 +234,8 @@ qx.Class.define("osparc.service.Utils", { }, isDeprecated: function(metadata) { - if (metadata && "deprecated" in metadata && ![null, undefined].includes(metadata["deprecated"])) { - const deprecationTime = new Date(metadata["deprecated"]); + if (metadata && "retired" in metadata && ![null, undefined].includes(metadata["retired"])) { + const deprecationTime = new Date(metadata["retired"]); const now = new Date(); return deprecationTime.getTime() > now.getTime(); } @@ -243,8 +243,8 @@ qx.Class.define("osparc.service.Utils", { }, isRetired: function(metadata) { - if (metadata && "deprecated" in metadata && ![null, undefined].includes(metadata["deprecated"])) { - const deprecationTime = new Date(metadata["deprecated"]); + if (metadata && "retired" in metadata && ![null, undefined].includes(metadata["retired"])) { + const deprecationTime = new Date(metadata["retired"]); const now = new Date(); return deprecationTime.getTime() < now.getTime(); } @@ -252,7 +252,7 @@ qx.Class.define("osparc.service.Utils", { }, getDeprecationDateText: function(metadata) { - const deprecationTime = new Date(metadata["deprecated"]); + const deprecationTime = new Date(metadata["retired"]); return qx.locale.Manager.tr("It will be Retired: ") + osparc.utils.Utils.formatDate(deprecationTime); }, diff --git a/services/static-webserver/client/source/class/osparc/tours/Manager.js b/services/static-webserver/client/source/class/osparc/tours/Manager.js index cf2618c3d4ef..2d4445dcf7c2 100644 --- a/services/static-webserver/client/source/class/osparc/tours/Manager.js +++ b/services/static-webserver/client/source/class/osparc/tours/Manager.js @@ -66,7 +66,7 @@ qx.Class.define("osparc.tours.Manager", { switch (id) { case "intro-text": control = new qx.ui.basic.Label().set({ - value: this.tr("This collection of Guided Tours will show you how to use the framework:"), + value: this.tr("This collection of Guided Tours will show you how to use the platform:"), rich: true, wrap: true, font: "text-14" diff --git a/services/static-webserver/client/source/class/osparc/utils/Utils.js b/services/static-webserver/client/source/class/osparc/utils/Utils.js index 61a0255de31e..910c4ec3173f 100644 --- a/services/static-webserver/client/source/class/osparc/utils/Utils.js +++ b/services/static-webserver/client/source/class/osparc/utils/Utils.js @@ -111,6 +111,18 @@ qx.Class.define("osparc.utils.Utils", { return newName; }, + getIconFromResource: function(resourceMetadata) { + if (resourceMetadata) { + if (resourceMetadata["icon"]) { + return resourceMetadata["icon"]; + } + if (resourceMetadata["thumbnail"]) { + return resourceMetadata["thumbnail"]; + } + } + return osparc.dashboard.CardBase.PRODUCT_ICON; + }, + isEmail: function(value) { const reg = /^([A-Za-z0-9_\-.+])+@([A-Za-z0-9_\-.])+\.([A-Za-z]{2,})$/; return reg.test(value); diff --git a/services/static-webserver/client/source/resource/osparc/tours/osparc_tours.json b/services/static-webserver/client/source/resource/osparc/tours/osparc_tours.json new file mode 100644 index 000000000000..24ff1e63ef10 --- /dev/null +++ b/services/static-webserver/client/source/resource/osparc/tours/osparc_tours.json @@ -0,0 +1,108 @@ +{ + "studies": { + "id": "studies", + "name": "Studies", + "description": "All you need to know about Study handling", + "context": "osparc-test-id=newPlusBtn", + "steps": [{ + "beforeClick": { + "selector": "osparc-test-id=newPlusBtn", + "action": "open" + }, + "anchorEl": "osparc-test-id=newPlusMenu", + "title": "Create Studies", + "text": "Clicking on the (+) New button, allows you to create new Studies or new Folders to organize the studies", + "placement": "right" + }, { + "anchorEl": "osparc-test-id=searchBarFilter-textField-study", + "title": "Filter and Search", + "text": "This tool allows you to search Studies, Tutorials and Services.
You can search and filter by:
- Title, description, owner, id...
- Tags
- Shared with", + "placement": "bottom" + }, { + "beforeClick": { + "selector": "osparc-test-id=studyItemMenuButton", + "action": "open" + }, + "anchorEl": "osparc-test-id=studyItemMenuMenu", + "title": "More options button", + "text": "On the Study card, you can use the three dots button to access more information and operation on the Study.", + "placement": "left" + }, { + "anchorEl": "osparc-test-id=updateStudyBtn", + "title": "Update Services", + "text": "On the Study card, you can use the Update button to update the corresponding service to the latest version.", + "placement": "bottom" + }] + }, + "dashboard": { + "id": "dashboard", + "name": "Dashboard", + "description": "Introduction to the Dashboard tabs", + "context": "osparc-test-id=dashboardTabs", + "steps": [{ + "anchorEl": "osparc-test-id=dashboardTabs", + "title": "Dashboard Menu", + "text": "The menu tabs give you quick access to a set of core elements of the platform, namely Studies, Templates, Services and Data.", + "placement": "bottom" + }, { + "beforeClick": { + "selector": "osparc-test-id=studiesTabBtn" + }, + "anchorEl": "osparc-test-id=studiesTabBtn", + "text": "Any Study is accessible via the Dashboard. The Studies, which belong to or are shared with you, can be found here. You can also create Folders to help you organize the Studies", + "placement": "bottom" + }, { + "beforeClick": { + "selector": "osparc-test-id=templatesTabBtn" + }, + "anchorEl": "osparc-test-id=templatesTabBtn", + "text": "Clicking on a Template will create a copy of that Study, which will appear in your own Studies tab with the same name as the Template. Any changes you make to this copy will not affect the original Template.", + "placement": "bottom" + }, { + "beforeClick": { + "selector": "osparc-test-id=servicesTabBtn" + }, + "anchorEl": "osparc-test-id=servicesTabBtn", + "text": "Every Study in oSparc is composed of so-called Services.
These are building blocks for Studies and can provide data/files, visualize results (2D, 3D), implement code in Jupyter notebooks or perform computations to execute simulations within a Study.", + "placement": "bottom" + }, { + "beforeClick": { + "selector": "osparc-test-id=dataTabBtn" + }, + "anchorEl": "osparc-test-id=dataTabBtn", + "text": "All the Data of the Studies you have access to can bre explored here.", + "placement": "bottom" + }] + }, + "navbar": { + "id": "navbar", + "name": "Navigation Bar", + "description": "Introduction to the Navigation Bar", + "context": "osparc-test-id=navigationBar", + "steps": [{ + "beforeClick": { + "selector": "osparc-test-id=notificationsButton", + "event": "tap" + }, + "anchorEl": "osparc-test-id=notificationsContainer", + "text": "By clicking on the Bell, you will you see notifications about which Studies, Templates and Organizations have been shared with you.", + "placement": "bottom" + }, { + "beforeClick": { + "selector": "osparc-test-id=helpNavigationBtn", + "action": "open" + }, + "anchorEl": "osparc-test-id=helpNavigationMenu", + "text": "Under the question mark, you will find Manuals, Support and ways to give us Feedback. It also provides quick access to other Guided Tours.", + "placement": "left" + }, { + "beforeClick": { + "selector": "osparc-test-id=userMenuBtn", + "action": "open" + }, + "anchorEl": "osparc-test-id=userMenuMenu", + "text": "The User Menu gives you access to Your Account, Organizations and more.", + "placement": "left" + }] + } +} diff --git a/services/static-webserver/client/source/resource/osparc/tours/s4l_tours.json b/services/static-webserver/client/source/resource/osparc/tours/s4l_tours.json index 492544fa598f..0e5f056a68ce 100644 --- a/services/static-webserver/client/source/resource/osparc/tours/s4l_tours.json +++ b/services/static-webserver/client/source/resource/osparc/tours/s4l_tours.json @@ -1,4 +1,39 @@ { + "projects": { + "id": "projects", + "name": "Projects", + "description": "All you need to know about Project handling", + "context": "osparc-test-id=newPlusBtn", + "steps": [{ + "beforeClick": { + "selector": "osparc-test-id=newPlusBtn", + "action": "open" + }, + "anchorEl": "osparc-test-id=newPlusMenu", + "title": "Start Sim4Life and more", + "text": "Clicking on the (+) New button, allows you to create new Sim4Life projects or new Folders to organize the projects", + "placement": "right" + }, { + "anchorEl": "osparc-test-id=searchBarFilter-textField-study", + "title": "Filter and Search", + "text": "This tool allows you to search Projects, Tutorials and Services.
You can search and filter by:
- Title, description, owner, id...
- Tags
- Shared with", + "placement": "bottom" + }, { + "beforeClick": { + "selector": "osparc-test-id=studyItemMenuButton", + "action": "open" + }, + "anchorEl": "osparc-test-id=studyItemMenuMenu", + "title": "More options button", + "text": "On the Project card, you can use the three dots button to access more information and operation on the Project.", + "placement": "left" + }, { + "anchorEl": "osparc-test-id=updateStudyBtn", + "title": "Update Services", + "text": "On the Project card, you can use the Update button to update the corresponding service to the latest version.", + "placement": "bottom" + }] + }, "dashboard": { "id": "dashboard", "name": "Dashboard", @@ -28,7 +63,7 @@ "selector": "osparc-test-id=servicesTabBtn" }, "anchorEl": "osparc-test-id=servicesTabBtn", - "text": "Every Project in Sim4Life is composed of at lease one so-called Service.
Services are building blocks for Projects and can provide data/files, visualize results (2D, 3D), implement code in Jupyter notebooks or perform computations to execute simulations within a Project.", + "text": "Every Project in Sim4Life is composed of at least one so-called Service.
Services are building blocks for Projects and can provide data/files, visualize results (2D, 3D), implement code in Jupyter notebooks or perform computations to execute simulations within a Project.", "placement": "bottom" }] }, @@ -62,39 +97,5 @@ "text": "The User Menu gives you access to Your Account, Billing Center, Preferences, Organizations and more.", "placement": "left" }] - }, - "projects": { - "id": "projects", - "name": "Projects", - "description": "All you need to know about Project handling", - "context": "osparc-test-id=studiesTabBtn", - "steps": [{ - "beforeClick": { - "selector": "osparc-test-id=studiesTabBtn" - }, - "anchorEl": "osparc-test-id=startS4LButton", - "title": "Start Sim4Life", - "text": "Clicking on this (+) Start Sim4Life button, allows you to create and open a new Sim4Life project", - "placement": "right" - }, { - "anchorEl": "osparc-test-id=searchBarFilter-textField-study", - "title": "Filter and Search", - "text": "This tool allows you to filter Projects, Tutorials and Services.
You can search and filter by:
- Title, description, owner, id...
- Tags
- Shared with", - "placement": "bottom" - }, { - "beforeClick": { - "selector": "osparc-test-id=studyItemMenuButton", - "action": "open" - }, - "anchorEl": "osparc-test-id=studyItemMenuMenu", - "title": "More options button", - "text": "On the Project card, you can use the three dots button to access more information and operation on the Project.", - "placement": "left" - }, { - "anchorEl": "osparc-test-id=updateStudyBtn", - "title": "Update Services", - "text": "On the Project card, you can use the Update button to update the corresponding service to the latest version.", - "placement": "bottom" - }] } } diff --git a/services/static-webserver/client/source/resource/osparc/tours/s4llite_tours.json b/services/static-webserver/client/source/resource/osparc/tours/s4llite_tours.json index e1e509a6f838..7dfbd752dbcc 100644 --- a/services/static-webserver/client/source/resource/osparc/tours/s4llite_tours.json +++ b/services/static-webserver/client/source/resource/osparc/tours/s4llite_tours.json @@ -1,4 +1,39 @@ { + "projects": { + "id": "projects", + "name": "Projects", + "description": "All you need to know about Project handling", + "context": "osparc-test-id=newPlusBtn", + "steps": [{ + "beforeClick": { + "selector": "osparc-test-id=newPlusBtn", + "action": "open" + }, + "anchorEl": "osparc-test-id=newPlusMenu", + "title": "Start Sim4Life.lite", + "text": "Clicking on the (+) New button, allows you to create new Sim4Life.lite projects or new Folders to organize the projects", + "placement": "right" + }, { + "anchorEl": "osparc-test-id=searchBarFilter-textField-study", + "title": "Filter and Search", + "text": "This tool allows you to filter Projects and Tutorials.
You can search and filter by:
- Title, description, owner, id...
- Tags
- Shared with", + "placement": "bottom" + }, { + "beforeClick": { + "selector": "osparc-test-id=studyItemMenuButton", + "action": "open" + }, + "anchorEl": "osparc-test-id=studyItemMenuMenu", + "title": "More options button", + "text": "On the Project card, you can use the three dots button to access more information and operation on the Project.", + "placement": "left" + }, { + "anchorEl": "osparc-test-id=updateStudyBtn", + "title": "Update Services", + "text": "On the Project card, you can use the Update button to update the corresponding service to the latest version.", + "placement": "bottom" + }] + }, "dashboard": { "id": "dashboard", "name": "Dashboard", @@ -52,39 +87,5 @@ "text": "The User Menu gives you access to Your Account, Preferences, Organizations and more.", "placement": "left" }] - }, - "projects": { - "id": "projects", - "name": "Projects", - "description": "All you need to know about Project handling", - "context": "osparc-test-id=studiesTabBtn", - "steps": [{ - "beforeClick": { - "selector": "osparc-test-id=studiesTabBtn" - }, - "anchorEl": "osparc-test-id=startS4LButton", - "title": "Start Sim4Life.lite", - "text": "Clicking on this (+) Start Sim4Life.lite button, allows you to create and open a new Sim4Life.lite project", - "placement": "right" - }, { - "anchorEl": "osparc-test-id=searchBarFilter-textField-study", - "title": "Filter and Search", - "text": "This tool allows you to filter Projects and Tutorials.
You can search and filter by:
- Title, description, owner, id...
- Tags
- Shared with", - "placement": "bottom" - }, { - "beforeClick": { - "selector": "osparc-test-id=studyItemMenuButton", - "action": "open" - }, - "anchorEl": "osparc-test-id=studyItemMenuMenu", - "title": "More options button", - "text": "On the Project card, you can use the three dots button to access more information and operation on the Project.", - "placement": "left" - }, { - "anchorEl": "osparc-test-id=updateStudyBtn", - "title": "Update Services", - "text": "On the Project card, you can use the Update button to update the corresponding service to the latest version.", - "placement": "bottom" - }] } } diff --git a/services/storage/src/simcore_service_storage/api/rest/_files.py b/services/storage/src/simcore_service_storage/api/rest/_files.py index ca92fb1079fc..c0b6a4f4a7c9 100644 --- a/services/storage/src/simcore_service_storage/api/rest/_files.py +++ b/services/storage/src/simcore_service_storage/api/rest/_files.py @@ -1,6 +1,7 @@ import asyncio import logging from typing import Annotated, cast +from urllib.parse import quote from fastapi import APIRouter, Depends, Header, HTTPException, Request from models_library.api_schemas_storage.storage_schemas import ( @@ -202,11 +203,15 @@ async def upload_file( abort_url = ( URL(f"{request.url}") .with_path( - request.app.url_path_for( - "abort_upload_file", - location_id=f"{location_id}", - file_id=file_id, - ) + quote( + request.app.url_path_for( + "abort_upload_file", + location_id=f"{location_id}", + file_id=file_id, + ), + safe=":/", + ), + encoded=True, ) .with_query(user_id=query_params.user_id) ) @@ -214,11 +219,15 @@ async def upload_file( complete_url = ( URL(f"{request.url}") .with_path( - request.app.url_path_for( - "complete_upload_file", - location_id=f"{location_id}", - file_id=file_id, - ) + quote( + request.app.url_path_for( + "complete_upload_file", + location_id=f"{location_id}", + file_id=file_id, + ), + safe=":/", + ), + encoded=True, ) .with_query(user_id=query_params.user_id) ) @@ -273,12 +282,16 @@ async def complete_upload_file( route = ( URL(f"{request.url}") .with_path( - request.app.url_path_for( - "is_completed_upload_file", - location_id=f"{location_id}", - file_id=file_id, - future_id=task.get_name(), - ) + quote( + request.app.url_path_for( + "is_completed_upload_file", + location_id=f"{location_id}", + file_id=file_id, + future_id=task.get_name(), + ), + safe=":/", + ), + encoded=True, ) .with_query(user_id=query_params.user_id) )