diff --git a/.env-devel b/.env-devel index 3a8f10903751..94abfdde6a6e 100644 --- a/.env-devel +++ b/.env-devel @@ -395,6 +395,7 @@ WEBSERVER_PROJECTS={} WEBSERVER_PROMETHEUS_API_VERSION=v1 WEBSERVER_PROMETHEUS_URL=http://prometheus:9090 WEBSERVER_PUBLICATIONS=1 +WEBSERVER_REALTIME_COLLABORATION='{"RTC_MAX_NUMBER_OF_USERS":3}' WEBSERVER_SCICRUNCH={} WEBSERVER_SESSION_SECRET_KEY='REPLACE_ME_with_result__Fernet_generate_key=' WEBSERVER_SOCKETIO=1 diff --git a/services/docker-compose.yml b/services/docker-compose.yml index b9c3c08abd2b..b4680e177098 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -673,6 +673,7 @@ services: SWARM_STACK_NAME: ${SWARM_STACK_NAME} WEBSERVER_DEV_FEATURES_ENABLED: ${WEBSERVER_DEV_FEATURES_ENABLED} + WEBSERVER_REALTIME_COLLABORATION: ${WEBSERVER_REALTIME_COLLABORATION} WEBSERVER_LOGLEVEL: ${WEBSERVER_LOGLEVEL} WEBSERVER_PROFILING: ${WEBSERVER_PROFILING} diff --git a/services/static-webserver/client/Makefile b/services/static-webserver/client/Makefile index 850dfe8989b2..cb7293594589 100644 --- a/services/static-webserver/client/Makefile +++ b/services/static-webserver/client/Makefile @@ -44,7 +44,7 @@ follow-dev-logs: ## follow the logs of the qx compiler .PHONY: compile touch upgrade compile: ## qx compiles host' 'source' -> image's 'build-output' # qx compile 'source' within $(docker_image) image [itisfoundation/qooxdoo-kit:${QOOXDOO_KIT_TAG}] - @docker --debug buildx build \ + @docker buildx build \ --load \ --file $(docker_file) \ --tag $(docker_image) \ @@ -58,7 +58,7 @@ compile: ## qx compiles host' 'source' -> image's 'build-output' touch: ## minimal image build with /project/output-build inside # touch /project/output-build such that multi-stage 'services/web/Dockerfile' can build development target (fixes #1097) - @docker --debug buildx build \ + @docker buildx build \ --load \ --file $(docker_file) \ --tag $(docker_image) \ diff --git a/services/web/server/src/simcore_service_webserver/application.py b/services/web/server/src/simcore_service_webserver/application.py index 4af3a5c27e40..96056e14c4a5 100644 --- a/services/web/server/src/simcore_service_webserver/application.py +++ b/services/web/server/src/simcore_service_webserver/application.py @@ -7,6 +7,9 @@ from aiohttp import web from servicelib.aiohttp.application import create_safe_application +from simcore_service_webserver.collaboration.bootstrap import ( + setup_realtime_collaboration, +) from ._meta import WELCOME_DB_LISTENER_MSG, WELCOME_GC_MSG, WELCOME_MSG, info from .activity.plugin import setup_activity @@ -160,6 +163,7 @@ def create_application() -> web.Application: setup_publications(app) setup_studies_dispatcher(app) setup_exporter(app) + setup_realtime_collaboration(app) # NOTE: *last* events app.on_startup.append(_welcome_banner) diff --git a/services/web/server/src/simcore_service_webserver/application_settings.py b/services/web/server/src/simcore_service_webserver/application_settings.py index af7c4c79ed87..a858290461c7 100644 --- a/services/web/server/src/simcore_service_webserver/application_settings.py +++ b/services/web/server/src/simcore_service_webserver/application_settings.py @@ -29,6 +29,7 @@ from ._meta import API_VERSION, API_VTAG, APP_NAME from .catalog.settings import CatalogSettings +from .collaboration.settings import RealTimeCollaborationSettings from .constants import APP_SETTINGS_KEY from .diagnostics.settings import DiagnosticsSettings from .director_v2.settings import DirectorV2Settings @@ -55,7 +56,7 @@ # NOTE: to mark a plugin as a DEV-FEATURE annotated it with -# `Field(json_schema_extra={_X_DEV_FEATURE_FLAG: True})` +# `Field(json_schema_extra={_X_FEATURE_UNDER_DEVELOPMENT: True})` # This will force it to be disabled when WEBSERVER_DEV_FEATURES_ENABLED=False _X_FEATURE_UNDER_DEVELOPMENT: Final[str] = "x-dev-feature" @@ -277,6 +278,17 @@ class ApplicationSettings(BaseApplicationSettings, MixinLoggingSettings): Field(json_schema_extra={"auto_default_from_env": True}), ] + WEBSERVER_REALTIME_COLLABORATION: Annotated[ + RealTimeCollaborationSettings | None, + Field( + description="Enables real-time collaboration features", + json_schema_extra={ + "auto_default_from_env": True, + _X_FEATURE_UNDER_DEVELOPMENT: True, + }, + ), + ] + WEBSERVER_REDIS: Annotated[ RedisSettings | None, Field(json_schema_extra={"auto_default_from_env": True}) ] @@ -482,6 +494,7 @@ def _get_disabled_advertised_plugins(self) -> list[str]: "WEBSERVER_LICENSES", "WEBSERVER_PAYMENTS", "WEBSERVER_SCICRUNCH", + "WEBSERVER_REALTIME_COLLABORATION", } return [_ for _ in advertised_plugins if not self.is_enabled(_)] + [ # NOTE: Permanently retired in https://github.com/ITISFoundation/osparc-simcore/pull/7182 @@ -558,6 +571,9 @@ def to_client_statics(self) -> dict[str, Any]: "WEBSERVER_PROJECTS": { "PROJECTS_MAX_NUM_RUNNING_DYNAMIC_NODES", }, + "WEBSERVER_REALTIME_COLLABORATION": { + "RTC_MAX_NUMBER_OF_USERS", + }, "WEBSERVER_SESSION": {"SESSION_COOKIE_MAX_AGE"}, "WEBSERVER_TRASH": { "TRASH_RETENTION_DAYS", diff --git a/services/web/server/src/simcore_service_webserver/collaboration/__init__.py b/services/web/server/src/simcore_service_webserver/collaboration/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/services/web/server/src/simcore_service_webserver/collaboration/bootstrap.py b/services/web/server/src/simcore_service_webserver/collaboration/bootstrap.py new file mode 100644 index 000000000000..147da70b5a43 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/collaboration/bootstrap.py @@ -0,0 +1,18 @@ +import logging + +from aiohttp import web +from servicelib.aiohttp.application_setup import ModuleCategory, app_module_setup + +_logger = logging.getLogger(__name__) + + +@app_module_setup( + __name__, + ModuleCategory.ADDON, + settings_name="WEBSERVER_REALTIME_COLLABORATION", + logger=_logger, +) +def setup_realtime_collaboration(app: web.Application): + from .settings import get_plugin_settings + + assert get_plugin_settings(app), "setup_settings not called?" diff --git a/services/web/server/src/simcore_service_webserver/collaboration/settings.py b/services/web/server/src/simcore_service_webserver/collaboration/settings.py new file mode 100644 index 000000000000..f5e49f79dab8 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/collaboration/settings.py @@ -0,0 +1,26 @@ +from typing import Annotated + +from aiohttp import web +from pydantic import ( + PositiveInt, +) +from pydantic.fields import Field +from settings_library.base import BaseCustomSettings + +from ..constants import APP_SETTINGS_KEY + + +class RealTimeCollaborationSettings(BaseCustomSettings): + RTC_MAX_NUMBER_OF_USERS: Annotated[ + PositiveInt, + Field( + description="Maximum number of users allowed in a real-time collaboration session", + ), + ] + + +def get_plugin_settings(app: web.Application) -> RealTimeCollaborationSettings: + settings = app[APP_SETTINGS_KEY].WEBSERVER_REALTIME_COLLABORATION + assert settings, "setup_settings not called?" # nosec + assert isinstance(settings, RealTimeCollaborationSettings) # nosec + return settings diff --git a/services/web/server/tests/unit/isolated/test_application_settings.py b/services/web/server/tests/unit/isolated/test_application_settings.py index c8c12355922b..4c662ed12d66 100644 --- a/services/web/server/tests/unit/isolated/test_application_settings.py +++ b/services/web/server/tests/unit/isolated/test_application_settings.py @@ -73,6 +73,9 @@ def test_settings_to_client_statics(app_settings: ApplicationSettings): def test_settings_to_client_statics_plugins( mock_webserver_service_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatch ): + monkeypatch.delenv("WEBSERVER_REALTIME_COLLABORATION", raising=False) + + # explicitly disable these plugins disable_plugins = { "WEBSERVER_EXPORTER", "WEBSERVER_SCICRUNCH", @@ -82,12 +85,21 @@ def test_settings_to_client_statics_plugins( for name in disable_plugins: monkeypatch.setenv(name, "null") + # explicitly disable WEBSERVER_FOLDERS monkeypatch.setenv("WEBSERVER_FOLDERS", "0") disable_plugins.add("WEBSERVER_FOLDERS") + # set WEBSERVER_REALTIME_COLLABORATION (NOTE: for now WEBSERVER_DEV_FEATURES_ENABLED=True) ) + monkeypatch.setenv( + "WEBSERVER_REALTIME_COLLABORATION", '{"RTC_MAX_NUMBER_OF_USERS":3}' + ) + settings = ApplicationSettings.create_from_envs() - statics = settings.to_client_statics() + assert settings.WEBSERVER_DEV_FEATURES_ENABLED + + # ------------- + statics = settings.to_client_statics() print("STATICS:\n", json_dumps(statics, indent=1)) assert settings.WEBSERVER_LOGIN @@ -111,6 +123,15 @@ def test_settings_to_client_statics_plugins( assert statics["vcsReleaseTag"] assert TypeAdapter(HttpUrl).validate_python(statics["vcsReleaseUrl"]) + # check WEBSERVER_REALTIME_COLLABORATION enabled + assert "WEBSERVER_REALTIME_COLLABORATION" not in statics["pluginsDisabled"] + assert settings.WEBSERVER_REALTIME_COLLABORATION + assert ( + statics["webserverRealtimeCollaboration"]["RTC_MAX_NUMBER_OF_USERS"] + == settings.WEBSERVER_REALTIME_COLLABORATION.RTC_MAX_NUMBER_OF_USERS + ) + + # check disabled plugins assert set(statics["pluginsDisabled"]) == (disable_plugins)