From 2b7a4605293e96ead28f3344ef8d3aefae59dc05 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 24 Jul 2024 09:00:55 +0200 Subject: [PATCH 1/7] [FR] Switch to websockets for progress bars/notifications Fixes #6262 --- Procfile | 2 +- contrib/container/Dockerfile | 2 +- contrib/container/requirements.in | 1 + contrib/container/requirements.txt | 20 +- contrib/deploy/supervisord.conf | 2 +- src/backend/InvenTree/InvenTree/asgi.py | 32 +++ src/backend/InvenTree/InvenTree/routing.py | 11 + src/backend/InvenTree/InvenTree/settings.py | 21 +- src/backend/InvenTree/web/consumers.py | 82 +++++++ src/backend/requirements.in | 5 +- src/backend/requirements.txt | 201 +++++++++++++++++- src/frontend/package.json | 3 +- src/frontend/src/components/nav/Header.tsx | 2 + .../nav/PageAttendanceComponent.tsx | 196 +++++++++++++++++ src/frontend/yarn.lock | 5 + tasks.py | 4 +- 16 files changed, 576 insertions(+), 13 deletions(-) create mode 100644 src/backend/InvenTree/InvenTree/asgi.py create mode 100644 src/backend/InvenTree/InvenTree/routing.py create mode 100644 src/backend/InvenTree/web/consumers.py create mode 100644 src/frontend/src/components/nav/PageAttendanceComponent.tsx diff --git a/Procfile b/Procfile index 9b81d80a4dca..272396dab9ad 100644 --- a/Procfile +++ b/Procfile @@ -1,5 +1,5 @@ # Web process: gunicorn -web: env/bin/gunicorn --chdir $APP_HOME/src/backend/InvenTree -c src/backend/InvenTree/gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:$PORT +web: env/bin/gunicorn --chdir $APP_HOME/src/backend/InvenTree -c src/backend/InvenTree/gunicorn.conf.py InvenTree.asgi -b 0.0.0.0:$PORT -k uvicorn_worker.UvicornWorker # Worker process: qcluster worker: env/bin/python src/backend/InvenTree/manage.py qcluster # Invoke commands diff --git a/contrib/container/Dockerfile b/contrib/container/Dockerfile index 63e501352438..575dd5c02965 100644 --- a/contrib/container/Dockerfile +++ b/contrib/container/Dockerfile @@ -132,7 +132,7 @@ COPY src/backend/requirements.txt ${INVENTREE_BACKEND_DIR}/requirements.txt COPY --from=frontend ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web # Launch the production server -CMD gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:8000 --chdir ${INVENTREE_BACKEND_DIR}/InvenTree +CMD gunicorn -c ./gunicorn.conf.py InvenTree.asgi -b 0.0.0.0:8000 --chdir ${INVENTREE_BACKEND_DIR}/InvenTree -k uvicorn_worker.UvicornWorker FROM inventree_base AS dev diff --git a/contrib/container/requirements.in b/contrib/container/requirements.in index 47968937a53f..744a4e8a01ab 100644 --- a/contrib/container/requirements.in +++ b/contrib/container/requirements.in @@ -13,6 +13,7 @@ mariadb>=1.1.8 # gunicorn web server gunicorn>=22.0.0 +uvicorn-worker # ASGI worker for gunicorn # LDAP required packages django-auth-ldap # Django integration for ldap auth diff --git a/contrib/container/requirements.txt b/contrib/container/requirements.txt index 5a4f95c64a76..59d0a2efe70a 100644 --- a/contrib/container/requirements.txt +++ b/contrib/container/requirements.txt @@ -4,6 +4,10 @@ asgiref==3.8.1 \ --hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \ --hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590 # via django +click==8.1.7 \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de + # via uvicorn django==4.2.14 \ --hash=sha256:3ec32bc2c616ab02834b9cac93143a7dc1cdcd5b822d78ac95fc20a38c534240 \ --hash=sha256:fc6919875a6226c7ffcae1a7d51e0f2ceaf6f160393180818f6c95f51b1e7b96 @@ -15,7 +19,13 @@ django-auth-ldap==4.8.0 \ gunicorn==22.0.0 \ --hash=sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9 \ --hash=sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63 - # via -r contrib/container/requirements.in + # via + # -r contrib/container/requirements.in + # uvicorn-worker +h11==0.14.0 \ + --hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \ + --hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761 + # via uvicorn invoke==2.2.0 \ --hash=sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820 \ --hash=sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5 @@ -226,6 +236,14 @@ uv==0.1.38 \ --hash=sha256:ea44c07605d1359a7d82bf42706dd86d341f15f4ca2e1f36e51626a7111c2ad5 \ --hash=sha256:f87c9711493c53d32012a96b49c4d53aabdf7ed666cbf2c3fb55dd402a6b31a8 # via -r contrib/container/requirements.in +uvicorn==0.30.3 \ + --hash=sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81 \ + --hash=sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503 + # via uvicorn-worker +uvicorn-worker==0.2.0 \ + --hash=sha256:65dcef25ab80a62e0919640f9582216ee05b3bb1dc2f0e58b354ca0511c398fb \ + --hash=sha256:f6894544391796be6eeed37d48cae9d7739e5a105f7e37061eccef2eac5a0295 + # via -r contrib/container/requirements.in wheel==0.43.0 \ --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \ --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 diff --git a/contrib/deploy/supervisord.conf b/contrib/deploy/supervisord.conf index a480cba8041d..1198961e7660 100644 --- a/contrib/deploy/supervisord.conf +++ b/contrib/deploy/supervisord.conf @@ -24,7 +24,7 @@ port = 127.0.0.1:9001 [program:inventree-server] user=inventree directory=/home/inventree/src/InvenTree -command=/home/inventree/env/bin/gunicorn -c gunicorn.conf.py InvenTree.wsgi +command=/home/inventree/env/bin/gunicorn -c gunicorn.conf.py InvenTree.asgi -k uvicorn_worker.UvicornWorker startsecs=10 autostart=true autorestart=true diff --git a/src/backend/InvenTree/InvenTree/asgi.py b/src/backend/InvenTree/InvenTree/asgi.py new file mode 100644 index 000000000000..100c8cab1fde --- /dev/null +++ b/src/backend/InvenTree/InvenTree/asgi.py @@ -0,0 +1,32 @@ +"""ASGI config for InvenTree project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ +""" + +import os + +# from django.conf import settings +from django.core.asgi import get_asgi_application + +from channels.auth import AuthMiddlewareStack +from channels.routing import ProtocolTypeRouter, URLRouter +from channels.security.websocket import AllowedHostsOriginValidator + +os.environ.setdefault( + 'DJANGO_SETTINGS_MODULE', 'InvenTree.settings' +) # pragma: no cover + +from InvenTree import routing # noqa: E402 + +# Set up django +# settings.configure() + +application = ProtocolTypeRouter({ + 'http': get_asgi_application(), + 'websocket': AllowedHostsOriginValidator( + AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)) + ), +}) diff --git a/src/backend/InvenTree/InvenTree/routing.py b/src/backend/InvenTree/InvenTree/routing.py new file mode 100644 index 000000000000..a99db6cee1e0 --- /dev/null +++ b/src/backend/InvenTree/InvenTree/routing.py @@ -0,0 +1,11 @@ +"""Async routings for InvenTree.""" + +from django.urls import path + +from channels.routing import URLRouter + +from web.consumers import InvenTreeConsumer + +websocket_urlpatterns = [ + path('ws/', URLRouter([path('page//', InvenTreeConsumer.as_asgi())])) +] diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index cedeb4e215df..fa4f8cabd0d4 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -23,7 +23,12 @@ import pytz from dotenv import load_dotenv -from InvenTree.cache import get_cache_config, is_global_cache_enabled +from InvenTree.cache import ( + cache_host, + cache_port, + get_cache_config, + is_global_cache_enabled, +) from InvenTree.config import get_boolean_setting, get_custom_file, get_setting from InvenTree.ready import isInMainThread from InvenTree.sentry import default_sentry_dsn, init_sentry @@ -185,6 +190,7 @@ ) INSTALLED_APPS = [ + 'daphne', # ASGI enabled dev server # Admin site integration 'django.contrib.admin', # InvenTree apps @@ -240,6 +246,7 @@ 'dj_rest_auth.registration', # Registration APIs - dj-rest-auth' 'drf_spectacular', # API documentation 'django_ical', # For exporting calendars + 'channels', # websockets ] MIDDLEWARE = CONFIG.get( @@ -517,6 +524,7 @@ # WSGI default setting WSGI_APPLICATION = 'InvenTree.wsgi.application' +ASGI_APPLICATION = 'InvenTree.asgi.application' """ Configure the database backend based on the user-specified values. @@ -843,6 +851,17 @@ # as well Q_CLUSTER['django_redis'] = 'worker' +# Settings for channels +if GLOBAL_CACHE_ENABLED: # pragma: no cover + CHANNEL_LAYERS = { + 'default': { + 'BACKEND': 'channels_redis.core.RedisChannelLayer', + 'CONFIG': {'hosts': [cache_host(), cache_port()]}, + } + } +else: + CHANNEL_LAYERS = {'default': {'BACKEND': 'channels.layers.InMemoryChannelLayer'}} + # database user sessions SESSION_ENGINE = 'user_sessions.backends.db' LOGOUT_REDIRECT_URL = get_setting( diff --git a/src/backend/InvenTree/web/consumers.py b/src/backend/InvenTree/web/consumers.py new file mode 100644 index 000000000000..9843a7238f66 --- /dev/null +++ b/src/backend/InvenTree/web/consumers.py @@ -0,0 +1,82 @@ +"""Websocket consumers for web app.""" + +import json + +from asgiref.sync import async_to_sync +from channels.generic.websocket import JsonWebsocketConsumer + + +class InvenTreeConsumer(JsonWebsocketConsumer): + """This consumer is used to enable page attendance widgets.""" + + def __init__(self, *args, **kwargs): + """Set up context.""" + super().__init__(args, kwargs) + self.user = None + + def connect(self): + """Join a user to a page.""" + user = self.scope.get('user') + if not user or not user.is_authenticated: + self.close() + return + self.user = user + self.accept() + + from django.contrib.auth.models import User + + from InvenTree.serializers import UserSerializer + + self.room_name = self.scope['url_route']['kwargs']['url_name'] + self.room_group_name = 'chat_%s' % self.room_name + # Join room group + async_to_sync(self.channel_layer.group_add)( + self.room_group_name, self.channel_name + ) + + # Announce all users + instances = User.objects.all() + self.send_json({ + 'type': 'users', + 'users': UserSerializer(instances, many=True).data, + }) + + def disconnect(self, code): + """Remove user from a page.""" + async_to_sync(self.channel_layer.group_discard)( + self.room_group_name, self.channel_name + ) + return super().disconnect(code) + + def receive_json(self, content, **kwargs): + """Handler for processing incoming messages.""" + message_type = content.get('type', None) + message = content.get('message', None) + + async_to_sync(self.channel_layer.group_send)( + self.room_group_name, + { + 'type': 'chat_message', + 'message': message, + 'user': self.user.get_username(), + }, + ) + + if message_type == '#TODO': + # TODO: implement this + ... + return super().receive_json(content, **kwargs) + + # Receive message from room group + def chat_message(self, event): + """Send message to WebSocket.""" + message = event['message'] + + # Send message to WebSocket + self.send( + text_data=json.dumps({ + 'type': 'chat_message', + 'message': message, + 'user': event['user'], + }) + ) diff --git a/src/backend/requirements.in b/src/backend/requirements.in index 327047bb49d2..3d12f836cbeb 100644 --- a/src/backend/requirements.in +++ b/src/backend/requirements.in @@ -1,8 +1,10 @@ # Please keep this list sorted - if you pin a version provide a reason Django<5.0 # Django package +channels[daphne] # Websockers +channels_redis # Websockets with Redis coreapi # API documentation for djangorestframework cryptography>=40.0.0,!=40.0.2 # Core cryptographic functionality -django-allauth[openid,saml] # SSO for external providers via OpenID +django-allauth[openid,saml] # SSO for external providers via OpenID django-allauth-2fa # MFA / 2FA django-cleanup # Automated deletion of old / unused uploaded files django-cors-headers # CORS headers extension for DRF @@ -51,6 +53,7 @@ regex # Advanced regular expressions sentry-sdk # Error reporting (optional) setuptools # Standard dependency tablib[xls,xlsx,yaml] # Support for XLS and XLSX formats +uvicorn-worker # ASGI worker for gunicorn weasyprint # PDF generation whitenoise # Enhanced static file serving diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 1cfa4a0c7fb4..68f2cc690a47 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -4,6 +4,9 @@ asgiref==3.8.1 \ --hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \ --hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590 # via + # channels + # channels-redis + # daphne # django # django-cors-headers async-timeout==4.0.3 \ @@ -14,8 +17,18 @@ attrs==23.2.0 \ --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 # via + # automat # jsonschema # referencing + # service-identity + # twisted +autobahn==23.6.2 \ + --hash=sha256:ec9421c52a2103364d1ef0468036e6019ee84f71721e86b36fe19ad6966c1181 + # via daphne +automat==22.10.0 \ + --hash=sha256:c3164f8742b9dc440f3682482d32aaff7bb53f71740dd018533f9de286b64180 \ + --hash=sha256:e56beb84edad19dcc11d30e8d9b895f75deeb5ef5e96b84a467066b3b84bb04e + # via twisted babel==2.15.0 \ --hash=sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb \ --hash=sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413 @@ -171,6 +184,16 @@ cffi==1.16.0 \ # via # cryptography # weasyprint +channels[daphne]==4.1.0 \ + --hash=sha256:a3c4419307f582c3f71d67bfb6eff748ae819c2f360b9b141694d84f242baa48 \ + --hash=sha256:e0ed375719f5c1851861f05ed4ce78b0166f9245ca0ecd836cb77d4bb531489d + # via + # -r src/backend/requirements.in + # channels-redis +channels-redis==4.2.0 \ + --hash=sha256:01c26c4d5d3a203f104bba9e5585c0305a70df390d21792386586068162027fd \ + --hash=sha256:2c5b944a39bd984b72aa8005a3ae11637bf29b5092adeb91c9aad4ab819a8ac4 + # via -r src/backend/requirements.in charset-normalizer==3.3.2 \ --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ @@ -263,6 +286,14 @@ charset-normalizer==3.3.2 \ --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 # via requests +click==8.1.7 \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de + # via uvicorn +constantly==23.10.4 \ + --hash=sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9 \ + --hash=sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd + # via twisted coreapi==2.3.3 \ --hash=sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb \ --hash=sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3 @@ -306,11 +337,18 @@ cryptography==42.0.8 \ --hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e # via # -r src/backend/requirements.in + # autobahn # djangorestframework-simplejwt + # pyopenssl + # service-identity cssselect2==0.7.0 \ --hash=sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a \ --hash=sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969 # via weasyprint +daphne==4.1.2 \ + --hash=sha256:618d1322bb4d875342b99dd2a10da2d9aae7ee3645f765965fdc1e658ea5290a \ + --hash=sha256:fcbcace38eb86624ae247c7ffdc8ac12f155d7d19eafac4247381896d6f33761 + # via channels defusedxml==0.7.1 \ --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 @@ -336,6 +374,7 @@ django==4.2.14 \ --hash=sha256:fc6919875a6226c7ffcae1a7d51e0f2ceaf6f160393180818f6c95f51b1e7b96 # via # -r src/backend/requirements.in + # channels # dj-rest-auth # django-allauth # django-allauth-2fa @@ -664,11 +703,23 @@ grpcio==1.64.1 \ gunicorn==22.0.0 \ --hash=sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9 \ --hash=sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63 - # via -r src/backend/requirements.in + # via + # -r src/backend/requirements.in + # uvicorn-worker +h11==0.14.0 \ + --hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \ + --hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761 + # via uvicorn html5lib==1.1 \ --hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \ --hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f # via weasyprint +hyperlink==21.0.0 \ + --hash=sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b \ + --hash=sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4 + # via + # autobahn + # twisted icalendar==5.0.13 \ --hash=sha256:5ded5415e2e1edef5ab230024a75878a7a81d518a3b1ae4f34bf20b173c84dc2 \ --hash=sha256:92799fde8cce0b61daa8383593836d1e19136e504fa1671f471f98be9b029706 @@ -676,7 +727,10 @@ icalendar==5.0.13 \ idna==3.7 \ --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 - # via requests + # via + # hyperlink + # requests + # twisted importlib-metadata==7.1.0 \ --hash=sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570 \ --hash=sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2 @@ -684,6 +738,10 @@ importlib-metadata==7.1.0 \ # django-q2 # markdown # opentelemetry-api +incremental==22.10.0 \ + --hash=sha256:912feeb5e0f7e0188e6f42241d2f450002e11bbc0937c65865045854c24c0bd0 \ + --hash=sha256:b864a1f30885ee72c5ac2835a761b8fe8aa9c28b9395cacf27286602688d3e51 + # via twisted inflection==0.5.1 \ --hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \ --hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2 @@ -923,6 +981,64 @@ markupsafe==2.1.5 \ --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 # via jinja2 +msgpack==1.0.8 \ + --hash=sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982 \ + --hash=sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3 \ + --hash=sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40 \ + --hash=sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee \ + --hash=sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693 \ + --hash=sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950 \ + --hash=sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151 \ + --hash=sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24 \ + --hash=sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305 \ + --hash=sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b \ + --hash=sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c \ + --hash=sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659 \ + --hash=sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d \ + --hash=sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18 \ + --hash=sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746 \ + --hash=sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868 \ + --hash=sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2 \ + --hash=sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba \ + --hash=sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228 \ + --hash=sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2 \ + --hash=sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273 \ + --hash=sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c \ + --hash=sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653 \ + --hash=sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a \ + --hash=sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596 \ + --hash=sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd \ + --hash=sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8 \ + --hash=sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa \ + --hash=sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85 \ + --hash=sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc \ + --hash=sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836 \ + --hash=sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3 \ + --hash=sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58 \ + --hash=sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128 \ + --hash=sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db \ + --hash=sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f \ + --hash=sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77 \ + --hash=sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad \ + --hash=sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13 \ + --hash=sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8 \ + --hash=sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b \ + --hash=sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a \ + --hash=sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543 \ + --hash=sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b \ + --hash=sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce \ + --hash=sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d \ + --hash=sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a \ + --hash=sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c \ + --hash=sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f \ + --hash=sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e \ + --hash=sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011 \ + --hash=sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04 \ + --hash=sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480 \ + --hash=sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a \ + --hash=sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d \ + --hash=sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d + # via channels-redis odfpy==1.4.1 \ --hash=sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec # via tablib @@ -1132,6 +1248,16 @@ py-moneyed==3.0 \ --hash=sha256:4906f0f02cf2b91edba2e156f2d4e9a78f224059ab8c8fa2ff26230c75d894e8 \ --hash=sha256:9583a14f99c05b46196193d8185206e9b73c8439fc8a5eee9cfc7e733676d9bb # via django-money +pyasn1==0.6.0 \ + --hash=sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c \ + --hash=sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473 + # via + # pyasn1-modules + # service-identity +pyasn1-modules==0.4.0 \ + --hash=sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6 \ + --hash=sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b + # via service-identity pycparser==2.22 \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc @@ -1144,6 +1270,10 @@ pyjwt==2.8.0 \ --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 # via djangorestframework-simplejwt +pyopenssl==24.2.1 \ + --hash=sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95 \ + --hash=sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d + # via twisted pyphen==0.15.0 \ --hash=sha256:999b430916ab42ae9912537cd95c074e0c6691e89a9d05999f9b610a68f34858 \ --hash=sha256:a430623decac53dc3691241253263cba36b9dd7a44ffd2680b706af368cda2f2 @@ -1346,7 +1476,9 @@ rapidfuzz==3.9.3 \ redis==5.0.7 \ --hash=sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db \ --hash=sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b - # via django-redis + # via + # channels-redis + # django-redis referencing==0.35.1 \ --hash=sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c \ --hash=sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de @@ -1549,13 +1681,19 @@ sentry-sdk==2.8.0 \ # via # -r src/backend/requirements.in # django-q-sentry +service-identity==24.1.0 \ + --hash=sha256:6829c9d62fb832c2e1c435629b0a8c476e1929881f28bee4d20bc24161009221 \ + --hash=sha256:a28caf8130c8a5c1c7a6f5293faaf239bbfb7751e4862436920ee6f2616f568a + # via twisted setuptools==71.0.3 \ --hash=sha256:3d8531791a27056f4a38cd3e54084d8b1c4228ff9cf3f2d7dd075ec99f9fd70d \ --hash=sha256:f501b6e6db709818dc76882582d9c516bf3b67b948864c5fa1d1624c09a49207 # via # -r src/backend/requirements.in + # autobahn # django-money # opentelemetry-instrumentation + # zope-interface sgmllib3k==1.0.0 \ --hash=sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9 # via feedparser @@ -1563,6 +1701,7 @@ six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via + # automat # bleach # html5lib # isodate @@ -1586,6 +1725,14 @@ tinycss2==1.2.1 \ # bleach # cssselect2 # weasyprint +twisted[tls]==24.3.0 \ + --hash=sha256:039f2e6a49ab5108abd94de187fa92377abe5985c7a72d68d0ad266ba19eae63 \ + --hash=sha256:6b38b6ece7296b5e122c9eb17da2eeab3d98a198f50ca9efd00fb03e5b4fd4ae + # via daphne +txaio==23.1.1 \ + --hash=sha256:aaea42f8aad50e0ecfb976130ada140797e9dcb85fad2cf72b0f37f8cefcb490 \ + --hash=sha256:f9a9216e976e5e3246dfd112ad7ad55ca915606b60b84a757ac769bd404ff704 + # via autobahn typing-extensions==4.12.2 \ --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 @@ -1595,6 +1742,8 @@ typing-extensions==4.12.2 \ # opentelemetry-sdk # py-moneyed # qrcode + # twisted + # uvicorn uritemplate==4.1.1 \ --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e @@ -1608,6 +1757,14 @@ urllib3==2.2.2 \ # dulwich # requests # sentry-sdk +uvicorn==0.30.3 \ + --hash=sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81 \ + --hash=sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503 + # via uvicorn-worker +uvicorn-worker==0.2.0 \ + --hash=sha256:65dcef25ab80a62e0919640f9582216ee05b3bb1dc2f0e58b354ca0511c398fb \ + --hash=sha256:f6894544391796be6eeed37d48cae9d7739e5a105f7e37061eccef2eac5a0295 + # via -r src/backend/requirements.in wcwidth==0.2.13 \ --hash=sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859 \ --hash=sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5 @@ -1777,6 +1934,44 @@ zipp==3.19.2 \ --hash=sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19 \ --hash=sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c # via importlib-metadata +zope-interface==6.4.post2 \ + --hash=sha256:00b5c3e9744dcdc9e84c24ed6646d5cf0cf66551347b310b3ffd70f056535854 \ + --hash=sha256:0e4fa5d34d7973e6b0efa46fe4405090f3b406f64b6290facbb19dcbf642ad6b \ + --hash=sha256:136cacdde1a2c5e5bc3d0b2a1beed733f97e2dad8c2ad3c2e17116f6590a3827 \ + --hash=sha256:1730c93a38b5a18d24549bc81613223962a19d457cfda9bdc66e542f475a36f4 \ + --hash=sha256:1a62fd6cd518693568e23e02f41816adedfca637f26716837681c90b36af3671 \ + --hash=sha256:1c207e6f6dfd5749a26f5a5fd966602d6b824ec00d2df84a7e9a924e8933654e \ + --hash=sha256:2eccd5bef45883802848f821d940367c1d0ad588de71e5cabe3813175444202c \ + --hash=sha256:33ee982237cffaf946db365c3a6ebaa37855d8e3ca5800f6f48890209c1cfefc \ + --hash=sha256:3d136e5b8821073e1a09dde3eb076ea9988e7010c54ffe4d39701adf0c303438 \ + --hash=sha256:47654177e675bafdf4e4738ce58cdc5c6d6ee2157ac0a78a3fa460942b9d64a8 \ + --hash=sha256:47937cf2e7ed4e0e37f7851c76edeb8543ec9b0eae149b36ecd26176ff1ca874 \ + --hash=sha256:4ac46298e0143d91e4644a27a769d1388d5d89e82ee0cf37bf2b0b001b9712a4 \ + --hash=sha256:4c0b208a5d6c81434bdfa0f06d9b667e5de15af84d8cae5723c3a33ba6611b82 \ + --hash=sha256:551db2fe892fcbefb38f6f81ffa62de11090c8119fd4e66a60f3adff70751ec7 \ + --hash=sha256:599f3b07bde2627e163ce484d5497a54a0a8437779362395c6b25e68c6590ede \ + --hash=sha256:5ef8356f16b1a83609f7a992a6e33d792bb5eff2370712c9eaae0d02e1924341 \ + --hash=sha256:5fe919027f29b12f7a2562ba0daf3e045cb388f844e022552a5674fcdf5d21f1 \ + --hash=sha256:6f0a6be264afb094975b5ef55c911379d6989caa87c4e558814ec4f5125cfa2e \ + --hash=sha256:706efc19f9679a1b425d6fa2b4bc770d976d0984335eaea0869bd32f627591d2 \ + --hash=sha256:73f9752cf3596771c7726f7eea5b9e634ad47c6d863043589a1c3bb31325c7eb \ + --hash=sha256:762e616199f6319bb98e7f4f27d254c84c5fb1c25c908c2a9d0f92b92fb27530 \ + --hash=sha256:866a0f583be79f0def667a5d2c60b7b4cc68f0c0a470f227e1122691b443c934 \ + --hash=sha256:86a94af4a88110ed4bb8961f5ac72edf782958e665d5bfceaab6bf388420a78b \ + --hash=sha256:8e0343a6e06d94f6b6ac52fbc75269b41dd3c57066541a6c76517f69fe67cb43 \ + --hash=sha256:97e615eab34bd8477c3f34197a17ce08c648d38467489359cb9eb7394f1083f7 \ + --hash=sha256:a96e6d4074db29b152222c34d7eec2e2db2f92638d2b2b2c704f9e8db3ae0edc \ + --hash=sha256:b912750b13d76af8aac45ddf4679535def304b2a48a07989ec736508d0bbfbde \ + --hash=sha256:bc2676312cc3468a25aac001ec727168994ea3b69b48914944a44c6a0b251e79 \ + --hash=sha256:cebff2fe5dc82cb22122e4e1225e00a4a506b1a16fafa911142ee124febf2c9e \ + --hash=sha256:d22fce0b0f5715cdac082e35a9e735a1752dc8585f005d045abb1a7c20e197f9 \ + --hash=sha256:d3f7e001328bd6466b3414215f66dde3c7c13d8025a9c160a75d7b2687090d15 \ + --hash=sha256:d3fe667935e9562407c2511570dca14604a654988a13d8725667e95161d92e9b \ + --hash=sha256:dabb70a6e3d9c22df50e08dc55b14ca2a99da95a2d941954255ac76fd6982bc5 \ + --hash=sha256:e2fb8e8158306567a3a9a41670c1ff99d0567d7fc96fa93b7abf8b519a46b250 \ + --hash=sha256:e96ac6b3169940a8cd57b4f2b8edcad8f5213b60efcd197d59fbe52f0accd66e \ + --hash=sha256:fbf649bc77510ef2521cf797700b96167bb77838c40780da7ea3edd8b78044d1 + # via twisted zopfli==0.2.3 \ --hash=sha256:0574372283befa5af98fb31407e1fe6822f2f9c437ef69e7fa260e49022d8a65 \ --hash=sha256:082f030b2b7d6d4597ac517816e499c63b92130aa8f4f74a3788ebaa5770f974 \ diff --git a/src/frontend/package.json b/src/frontend/package.json index 48592cc1fe3f..2da197113943 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -51,8 +51,8 @@ "dayjs": "^1.11.10", "embla-carousel-react": "^8.1.6", "html5-qrcode": "^2.3.8", - "qrcode": "^1.5.3", "mantine-datatable": "^7.11.2", + "qrcode": "^1.5.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-grid-layout": "^1.4.4", @@ -60,6 +60,7 @@ "react-is": "^18.3.1", "react-router-dom": "^6.24.0", "react-select": "^5.8.0", + "react-use-websocket": "^4.8.1", "recharts": "^2.12.4", "styled-components": "^6.1.11", "zustand": "^4.5.4" diff --git a/src/frontend/src/components/nav/Header.tsx b/src/frontend/src/components/nav/Header.tsx index 7ba9ef6f8b63..9a042cb8f58e 100644 --- a/src/frontend/src/components/nav/Header.tsx +++ b/src/frontend/src/components/nav/Header.tsx @@ -19,6 +19,7 @@ import { MainMenu } from './MainMenu'; import { NavHoverMenu } from './NavHoverMenu'; import { NavigationDrawer } from './NavigationDrawer'; import { NotificationDrawer } from './NotificationDrawer'; +import { PageAttendanceComponent } from './PageAttendanceComponent'; import { SearchDrawer } from './SearchDrawer'; export function Header() { @@ -103,6 +104,7 @@ export function Header() { + diff --git a/src/frontend/src/components/nav/PageAttendanceComponent.tsx b/src/frontend/src/components/nav/PageAttendanceComponent.tsx new file mode 100644 index 000000000000..1cfff215a055 --- /dev/null +++ b/src/frontend/src/components/nav/PageAttendanceComponent.tsx @@ -0,0 +1,196 @@ +import { Trans, t } from '@lingui/macro'; +import { + ActionIcon, + Button, + Divider, + Drawer, + Group, + Indicator, + List, + Pill, + ScrollArea, + Stack, + Text, + TextInput +} from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { IconEyeDotted, IconLink } from '@tabler/icons-react'; +import { useEffect, useMemo, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import useWebSocket, { ReadyState } from 'react-use-websocket'; + +import { useLocalState } from '../../states/LocalState'; +import { StylishText } from '../items/StylishText'; + +interface PageMessages { + type: 'chat_message' | 'users' | 'unknown'; + message: string; + data: any | null; + user: string; +} + +export const PageAttendanceComponent = () => { + const [host] = useLocalState((state) => [state.host]); + const location = useLocation(); + const [socketUrl, setSocketUrl] = useState(null); + + // websocket history + const [messageHistory, setMessageHistory] = useState[]>([]); + const [parsedMessageHistory, setParsedMessageHistory] = useState< + PageMessages[] + >([]); + const [ + isAttendanceDrawerOpened, + { open: openAttendanceDrawer, close: closeAttendanceDrawer } + ] = useDisclosure(false); + + const messageCount = useMemo(() => { + return parsedMessageHistory.filter( + (message) => message.type === 'chat_message' + ).length; + }, [parsedMessageHistory]); + + // Websockets + useEffect(() => { + setMessageHistory([]); + setParsedMessageHistory([]); + let new_room_name = location.pathname.slice(1).replace('/', '__'); + if (new_room_name === '') new_room_name = 'home'; + const cleaned_host = host.replace(/(^\w+:|^)\/\//, ''); + setSocketUrl(`ws://${cleaned_host}/ws/page/${new_room_name}/`); + }, [host, location.pathname]); + const { sendMessage, lastMessage, readyState } = useWebSocket(socketUrl); + + useEffect(() => { + if (lastMessage !== null) { + setMessageHistory((prev) => prev.concat(lastMessage)); + setParsedMessageHistory((prev) => + prev.concat(JSON.parse(lastMessage.data)) + ); + } + }, [lastMessage]); + + return ( + + + + + { + closeAttendanceDrawer(); + }} + sendMessage={sendMessage} + readyState={readyState} + messages={parsedMessageHistory} + /> + + ); +}; + +function AttendanceDrawer({ + opened, + onClose, + sendMessage, + readyState, + messages +}: { + opened: boolean; + onClose: () => void; + sendMessage: (message: string) => void; + readyState: ReadyState; + messages: PageMessages[]; +}) { + const [inputValue, setInputValue] = useState(''); + function handleClickSendMessage() { + sendMessage(JSON.stringify({ message: inputValue })); + setInputValue(''); + } + const connectionStatus = { + [ReadyState.CONNECTING]: t`Connecting`, + [ReadyState.OPEN]: t`Open`, + [ReadyState.CLOSING]: t`Closing`, + [ReadyState.CLOSED]: t`Closed`, + [ReadyState.UNINSTANTIATED]: t`Uninstantiated` + }[readyState]; + const displayMessages = useMemo(() => { + return messages.filter((message) => message.type === 'chat_message'); + }, [messages]); + + return ( + + {t`Live Page Attendance`}{' '} + + + {connectionStatus} + + + } + > + + + + Shows users that have this page currently open. All chat is empheral + and only visible as long as a user is on the page. + + + + { + setInputValue(e.target.value); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + setTimeout(() => { + handleClickSendMessage(); + }, 200); + } + }} + /> + + }> + {displayMessages.map((message, idx) => ( + + + {message.user} + {message.message} + + + ))} + + + + ); +} diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index 3a2772ee6304..6023a8a1b386 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -5511,6 +5511,11 @@ react-transition-group@4.4.5, react-transition-group@^4.3.0, react-transition-gr loose-envify "^1.4.0" prop-types "^15.6.2" +react-use-websocket@^4.8.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/react-use-websocket/-/react-use-websocket-4.8.1.tgz#be06a0bc956c6d56391a29cbe2caa6ae8edb5615" + integrity sha512-FTXuG5O+LFozmu1BRfrzl7UIQngECvGJmL7BHsK4TYXuVt+mCizVA8lT0hGSIF0Z0TedF7bOo1nRzOUdginhDw== + react@^18.2.0, react@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" diff --git a/tasks.py b/tasks.py index 57c2ceed305e..f985f2e337fd 100644 --- a/tasks.py +++ b/tasks.py @@ -817,9 +817,7 @@ def gunicorn(c, address='0.0.0.0:8000', workers=None): Note: This server will not auto-reload in response to code changes. """ config_file = localDir().joinpath('contrib', 'container', 'gunicorn.conf.py') - cmd = ( - f'gunicorn -c {config_file} InvenTree.wsgi -b {address} --chdir {managePyDir()}' - ) + cmd = f'gunicorn -c {config_file} InvenTree.asgi -b {address} --chdir {managePyDir()} -k uvicorn_worker.UvicornWorker' if workers: cmd += f' --workers={workers}' From 33903751620c9686b9e6a4c629e2c68879b2594f Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 24 Jul 2024 09:03:03 +0200 Subject: [PATCH 2/7] fix req --- src/backend/requirements-dev.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/backend/requirements-dev.txt b/src/backend/requirements-dev.txt index c22dad58b434..06318a4fcd85 100644 --- a/src/backend/requirements-dev.txt +++ b/src/backend/requirements-dev.txt @@ -167,7 +167,9 @@ charset-normalizer==3.3.2 \ click==8.1.7 \ --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de - # via pip-tools + # via + # -c src/backend/requirements.txt + # pip-tools coverage[toml]==7.5.4 \ --hash=sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f \ --hash=sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d \ From be0ccdafb8ad2e2c356d37b24b90aff8a749b4da Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 26 Jul 2024 20:17:35 +0200 Subject: [PATCH 3/7] fix path replacement --- src/frontend/src/components/nav/PageAttendanceComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/components/nav/PageAttendanceComponent.tsx b/src/frontend/src/components/nav/PageAttendanceComponent.tsx index 1cfff215a055..4b1be0bb07bd 100644 --- a/src/frontend/src/components/nav/PageAttendanceComponent.tsx +++ b/src/frontend/src/components/nav/PageAttendanceComponent.tsx @@ -54,7 +54,7 @@ export const PageAttendanceComponent = () => { useEffect(() => { setMessageHistory([]); setParsedMessageHistory([]); - let new_room_name = location.pathname.slice(1).replace('/', '__'); + let new_room_name = location.pathname.slice(1).replaceAll('/', '__'); if (new_room_name === '') new_room_name = 'home'; const cleaned_host = host.replace(/(^\w+:|^)\/\//, ''); setSocketUrl(`ws://${cleaned_host}/ws/page/${new_room_name}/`); From 8c908c7a323eec3ba774bb5555727df4c1d20e22 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 26 Jul 2024 20:21:52 +0200 Subject: [PATCH 4/7] cleanup imports --- src/frontend/src/components/nav/PageAttendanceComponent.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/frontend/src/components/nav/PageAttendanceComponent.tsx b/src/frontend/src/components/nav/PageAttendanceComponent.tsx index 4b1be0bb07bd..ccb4c86d7418 100644 --- a/src/frontend/src/components/nav/PageAttendanceComponent.tsx +++ b/src/frontend/src/components/nav/PageAttendanceComponent.tsx @@ -8,7 +8,6 @@ import { Indicator, List, Pill, - ScrollArea, Stack, Text, TextInput From baa5f019757140fbd5e4b93f11b4eb519f625a50 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 26 Jul 2024 20:24:17 +0200 Subject: [PATCH 5/7] cleanup code --- .../src/components/nav/PageAttendanceComponent.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/frontend/src/components/nav/PageAttendanceComponent.tsx b/src/frontend/src/components/nav/PageAttendanceComponent.tsx index ccb4c86d7418..698507ff143c 100644 --- a/src/frontend/src/components/nav/PageAttendanceComponent.tsx +++ b/src/frontend/src/components/nav/PageAttendanceComponent.tsx @@ -24,7 +24,7 @@ import { StylishText } from '../items/StylishText'; interface PageMessages { type: 'chat_message' | 'users' | 'unknown'; message: string; - data: any | null; + data: any; user: string; } @@ -34,7 +34,6 @@ export const PageAttendanceComponent = () => { const [socketUrl, setSocketUrl] = useState(null); // websocket history - const [messageHistory, setMessageHistory] = useState[]>([]); const [parsedMessageHistory, setParsedMessageHistory] = useState< PageMessages[] >([]); @@ -51,7 +50,6 @@ export const PageAttendanceComponent = () => { // Websockets useEffect(() => { - setMessageHistory([]); setParsedMessageHistory([]); let new_room_name = location.pathname.slice(1).replaceAll('/', '__'); if (new_room_name === '') new_room_name = 'home'; @@ -62,7 +60,6 @@ export const PageAttendanceComponent = () => { useEffect(() => { if (lastMessage !== null) { - setMessageHistory((prev) => prev.concat(lastMessage)); setParsedMessageHistory((prev) => prev.concat(JSON.parse(lastMessage.data)) ); @@ -100,13 +97,13 @@ function AttendanceDrawer({ sendMessage, readyState, messages -}: { +}: Readonly<{ opened: boolean; onClose: () => void; sendMessage: (message: string) => void; readyState: ReadyState; messages: PageMessages[]; -}) { +}>) { const [inputValue, setInputValue] = useState(''); function handleClickSendMessage() { sendMessage(JSON.stringify({ message: inputValue })); From 6f432c30e5812908fb230aface0ab4ba1739a8c3 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 28 Jul 2024 21:24:23 +0200 Subject: [PATCH 6/7] cleanup code following sonar scan reports --- src/backend/InvenTree/InvenTree/asgi.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/asgi.py b/src/backend/InvenTree/InvenTree/asgi.py index 100c8cab1fde..b3735230a765 100644 --- a/src/backend/InvenTree/InvenTree/asgi.py +++ b/src/backend/InvenTree/InvenTree/asgi.py @@ -8,7 +8,6 @@ import os -# from django.conf import settings from django.core.asgi import get_asgi_application from channels.auth import AuthMiddlewareStack @@ -21,9 +20,6 @@ from InvenTree import routing # noqa: E402 -# Set up django -# settings.configure() - application = ProtocolTypeRouter({ 'http': get_asgi_application(), 'websocket': AllowedHostsOriginValidator( From d927321b41d1feb14d14e28255b63616b9f8ad33 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 10 Sep 2024 20:38:24 +0200 Subject: [PATCH 7/7] style fixes --- src/backend/InvenTree/InvenTree/asgi.py | 2 +- src/backend/InvenTree/web/consumers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/asgi.py b/src/backend/InvenTree/InvenTree/asgi.py index b3735230a765..56cf43ea1303 100644 --- a/src/backend/InvenTree/InvenTree/asgi.py +++ b/src/backend/InvenTree/InvenTree/asgi.py @@ -18,7 +18,7 @@ 'DJANGO_SETTINGS_MODULE', 'InvenTree.settings' ) # pragma: no cover -from InvenTree import routing # noqa: E402 +from InvenTree import routing application = ProtocolTypeRouter({ 'http': get_asgi_application(), diff --git a/src/backend/InvenTree/web/consumers.py b/src/backend/InvenTree/web/consumers.py index 9843a7238f66..c0f1168d6be5 100644 --- a/src/backend/InvenTree/web/consumers.py +++ b/src/backend/InvenTree/web/consumers.py @@ -28,7 +28,7 @@ def connect(self): from InvenTree.serializers import UserSerializer self.room_name = self.scope['url_route']['kwargs']['url_name'] - self.room_group_name = 'chat_%s' % self.room_name + self.room_group_name = f'chat_{self.room_name}' # Join room group async_to_sync(self.channel_layer.group_add)( self.room_group_name, self.channel_name