Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6c9ca9214 | |||
| c91fac7bdd | |||
| ca98507642 |
@@ -81,7 +81,7 @@ jobs:
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
python-version: "3.12"
|
||||
- name: Install development dependencies
|
||||
run: pip install --user .[dev]
|
||||
- name: Check code formatting with ruff
|
||||
@@ -185,7 +185,7 @@ jobs:
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install development dependencies
|
||||
run: pip install --user .[dev]
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# Django Meet
|
||||
|
||||
# ---- base image to inherit from ----
|
||||
FROM python:3.13.5-alpine3.21 AS base
|
||||
FROM python:3.12.6-alpine3.20 AS base
|
||||
|
||||
# Upgrade pip to its latest release to speed up dependencies installation
|
||||
RUN python -m pip install --upgrade pip setuptools
|
||||
|
||||
@@ -250,7 +250,7 @@ You can use Visio on https://meet.127.0.0.1.nip.io from the local device. The pr
|
||||
These are the environmental options available on meet backend.
|
||||
|
||||
| Option | Description | default |
|
||||
| ----------------------------------------------- | ---------------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ----------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| DATA_DIR | Data directory location | /data |
|
||||
| DJANGO_ALLOWED_HOSTS | Hosts that are allowed | [] |
|
||||
| DJANGO_SECRET_KEY | Secret key used for Django security | |
|
||||
@@ -270,7 +270,7 @@ These are the environmental options available on meet backend.
|
||||
| AWS_STORAGE_BUCKET_NAME | S3 bucket name | meet-media-storage |
|
||||
| DJANGO_LANGUAGE_CODE | Default language | en-us |
|
||||
| REDIS_URL | Redis endpoint | redis://redis:6379/1 |
|
||||
| SESSION_COOKIE_AGE | Session cookie expiration in seconds | 43200 (12 hours) |
|
||||
| SESSION_COOKIE_AGE | Session cookie expiration in seconds | 43200 (12 hours) |
|
||||
| REQUEST_ENTRY_THROTTLE_RATES | Entry request throttle rates | 150/minute |
|
||||
| CREATION_CALLBACK_THROTTLE_RATES | Creation callback throttle rates | 600/minute |
|
||||
| SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK | Enable Django deploy check | false |
|
||||
@@ -293,7 +293,7 @@ These are the environmental options available on meet backend.
|
||||
| EMAIL_LOGO_IMG | Email logo image | |
|
||||
| EMAIL_DOMAIN | Email domain | |
|
||||
| EMAIL_APP_BASE_URL | Email app base URL | |
|
||||
| DJANGO_CORS_ALLOW_ALL_ORIGINS | Allow all CORS origins | false |
|
||||
| DJANGO_CORS_ALLOW_ALL_ORIGINS | Allow all CORS origins | true |
|
||||
| DJANGO_CORS_ALLOWED_ORIGINS | Origins to allow (string list) | [] |
|
||||
| DJANGO_CORS_ALLOWED_ORIGIN_REGEXES | Origins to allow (regex patterns) | [] |
|
||||
| SENTRY_DSN | Sentry server DSN | |
|
||||
@@ -351,10 +351,10 @@ These are the environmental options available on meet backend.
|
||||
| LOBBY_KEY_PREFIX | Lobby key prefix | room_lobby |
|
||||
| LOBBY_WAITING_TIMEOUT | Lobby waiting timeout in seconds | 3 |
|
||||
| LOBBY_DENIED_TIMEOUT | Lobby deny timeout in seconds | 5 |
|
||||
| LOBBY_ACCEPTED_TIMEOUT | Lobby accept timeout in seconds | 21600 (6 hours) |
|
||||
| LOBBY_ACCEPTED_TIMEOUT | Lobby accept timeout in seconds | 21600 (6 hours) |
|
||||
| LOBBY_NOTIFICATION_TYPE | Lobby notification types | participantWaiting |
|
||||
| LOBBY_COOKIE_NAME | Lobby cookie name | lobbyParticipantId |
|
||||
| ROOM_CREATION_CALLBACK_CACHE_TIMEOUT | Room creation callback cache timeout | 600 (10 minutes) |
|
||||
| ROOM_CREATION_CALLBACK_CACHE_TIMEOUT | Room creation callback cache timeout | 600 (10 minutes) |
|
||||
| ROOM_TELEPHONY_ENABLED | Enable SIP telephony feature | false |
|
||||
| ROOM_TELEPHONY_PIN_LENGTH | Telephony PIN length | 10 |
|
||||
| ROOM_TELEPHONY_PIN_MAX_RETRIES | Telephony PIN maximum retries | 5 |
|
||||
|
||||
@@ -42,7 +42,7 @@ LOGIN_REDIRECT_URL=http://localhost:3000
|
||||
LOGIN_REDIRECT_URL_FAILURE=http://localhost:3000
|
||||
LOGOUT_REDIRECT_URL=http://localhost:3000
|
||||
|
||||
OIDC_REDIRECT_ALLOWED_HOSTS=localhost:8083,localhost:3000
|
||||
OIDC_REDIRECT_ALLOWED_HOSTS=["http://localhost:8083", "http://localhost:3000"]
|
||||
OIDC_AUTH_REQUEST_EXTRA_PARAMS={"acr_values": "eidas1"}
|
||||
|
||||
# Livekit Token settings
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
"enabled": false,
|
||||
"groupName": "ignored python dependencies",
|
||||
"matchManagers": ["pep621"],
|
||||
"matchPackageNames": ["redis"]
|
||||
"matchPackageNames": []
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
|
||||
@@ -42,6 +42,7 @@ def get_frontend_configuration(request):
|
||||
"available_modes": settings.RECORDING_WORKER_CLASSES.keys(),
|
||||
"expiration_days": settings.RECORDING_EXPIRATION_DAYS,
|
||||
},
|
||||
"custom_css": "/custom.css",
|
||||
}
|
||||
frontend_configuration.update(settings.FRONTEND_CONFIGURATION)
|
||||
return Response(frontend_configuration)
|
||||
|
||||
@@ -64,7 +64,7 @@ class RoomPermissions(permissions.BasePermission):
|
||||
if request.method == "DELETE":
|
||||
return obj.is_owner(user)
|
||||
|
||||
return obj.is_administrator_or_owner(user)
|
||||
return obj.is_administrator(user)
|
||||
|
||||
|
||||
class ResourceAccessPermission(IsAuthenticated):
|
||||
@@ -80,7 +80,7 @@ class ResourceAccessPermission(IsAuthenticated):
|
||||
if request.method == "DELETE" and obj.role == RoleChoices.OWNER:
|
||||
return obj.user == user
|
||||
|
||||
return obj.resource.is_administrator_or_owner(user)
|
||||
return obj.resource.is_administrator(user)
|
||||
|
||||
|
||||
class HasAbilityPermission(IsAuthenticated):
|
||||
@@ -98,7 +98,7 @@ class HasPrivilegesOnRoom(IsAuthenticated):
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
"""Determine if user has privileges on room."""
|
||||
return obj.is_administrator_or_owner(request.user)
|
||||
return obj.is_owner(request.user) or obj.is_administrator(request.user)
|
||||
|
||||
|
||||
class IsRecordingEnabled(permissions.BasePermission):
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Client serializers for the Meet core app."""
|
||||
|
||||
import uuid
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from rest_framework import serializers
|
||||
@@ -60,9 +58,7 @@ class ResourceAccessSerializerMixin:
|
||||
request = self.context.get("request", None)
|
||||
user = getattr(request, "user", None)
|
||||
|
||||
if not (
|
||||
user and user.is_authenticated and resource.is_administrator_or_owner(user)
|
||||
):
|
||||
if not (user and user.is_authenticated and resource.is_administrator(user)):
|
||||
raise PermissionDenied(
|
||||
_("You must be administrator or owner of a room to add accesses to it.")
|
||||
)
|
||||
@@ -122,11 +118,9 @@ class RoomSerializer(serializers.ModelSerializer):
|
||||
return output
|
||||
|
||||
role = instance.get_role(request.user)
|
||||
is_admin_or_owner = models.RoleChoices.check_administrator_role(
|
||||
role
|
||||
) or models.RoleChoices.check_owner_role(role)
|
||||
is_admin = models.RoleChoices.check_administrator_role(role)
|
||||
|
||||
if is_admin_or_owner:
|
||||
if is_admin:
|
||||
access_serializer = NestedResourceAccessSerializer(
|
||||
instance.accesses.select_related("resource", "user").all(),
|
||||
context=self.context,
|
||||
@@ -134,7 +128,7 @@ class RoomSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
output["accesses"] = access_serializer.data
|
||||
|
||||
if not is_admin_or_owner:
|
||||
if not is_admin:
|
||||
del output["configuration"]
|
||||
|
||||
should_access_room = (
|
||||
@@ -153,7 +147,7 @@ class RoomSerializer(serializers.ModelSerializer):
|
||||
room_id=room_id, user=request.user, username=username
|
||||
)
|
||||
|
||||
output["is_administrable"] = is_admin_or_owner
|
||||
output["is_administrable"] = is_admin
|
||||
|
||||
return output
|
||||
|
||||
@@ -221,14 +215,6 @@ class ParticipantEntrySerializer(serializers.Serializer):
|
||||
participant_id = serializers.CharField(required=True)
|
||||
allow_entry = serializers.BooleanField(required=True)
|
||||
|
||||
def validate_participant_id(self, value):
|
||||
"""Validate that the participant_id is a valid UUID hex string."""
|
||||
try:
|
||||
uuid.UUID(hex=value, version=4)
|
||||
except (ValueError, TypeError) as e:
|
||||
raise serializers.ValidationError("Invalid UUID hex format") from e
|
||||
return value
|
||||
|
||||
def create(self, validated_data):
|
||||
"""Not implemented as this is a validation-only serializer."""
|
||||
raise NotImplementedError("ParticipantEntrySerializer is validation-only")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# ruff: noqa: S311
|
||||
"""
|
||||
Core application factories
|
||||
"""
|
||||
|
||||
@@ -35,7 +35,7 @@ class RoleChoices(models.TextChoices):
|
||||
@classmethod
|
||||
def check_administrator_role(cls, role):
|
||||
"""Check if a role is administrator."""
|
||||
return role == cls.ADMIN
|
||||
return role in [cls.ADMIN, cls.OWNER]
|
||||
|
||||
@classmethod
|
||||
def check_owner_role(cls, role):
|
||||
@@ -288,13 +288,13 @@ class Resource(BaseModel):
|
||||
role = RoleChoices.MEMBER
|
||||
return role
|
||||
|
||||
def is_administrator_or_owner(self, user):
|
||||
def is_administrator(self, user):
|
||||
"""
|
||||
Check if a user is administrator or owner of the resource."""
|
||||
role = self.get_role(user)
|
||||
return RoleChoices.check_administrator_role(
|
||||
role
|
||||
) or RoleChoices.check_owner_role(role)
|
||||
Check if a user is administrator of the resource.
|
||||
|
||||
Users carrying the "owner" role are considered as administrators a fortiori.
|
||||
"""
|
||||
return RoleChoices.check_administrator_role(self.get_role(user))
|
||||
|
||||
def is_owner(self, user):
|
||||
"""Check if a user is owner of the resource."""
|
||||
|
||||
@@ -287,7 +287,9 @@ def test_finds_user_whitespace_email(django_assert_num_queries, settings):
|
||||
[
|
||||
"john.doe@example.com", # Fullwidth character in domain
|
||||
"john.doe@еxample.com", # Cyrillic 'е' in domain
|
||||
"JOHN.DOe@exam𝔭le.com", # Mixed Gothic '𝔭' in domain
|
||||
"john.doe@exаmple.com", # Cyrillic 'а' (a) in domain
|
||||
"john.doe@e𝓧𝓪𝓶𝓹𝓵𝓮.com", # Mixed fullwidth and cursive in domain
|
||||
],
|
||||
)
|
||||
def test_authentication_getter_existing_user_email_tricky(email, monkeypatch, settings):
|
||||
|
||||
@@ -132,18 +132,18 @@ def test_request_entry_with_existing_participants(settings):
|
||||
|
||||
# Add two participants already waiting in the lobby
|
||||
cache.set(
|
||||
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
f"mocked-cache-prefix_{room.id}_participant1",
|
||||
{
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "participant1",
|
||||
"username": "user1",
|
||||
"status": "waiting",
|
||||
"color": "#123456",
|
||||
},
|
||||
)
|
||||
cache.set(
|
||||
f"mocked-cache-prefix_{room.id}_f4ca3ab8a6c04ad88097b8da33f60f10",
|
||||
f"mocked-cache-prefix_{room.id}_participant2",
|
||||
{
|
||||
"id": "f4ca3ab8a6c04ad88097b8da33f60f10",
|
||||
"id": "participant2",
|
||||
"username": "user2",
|
||||
"status": "accepted",
|
||||
"color": "#654321",
|
||||
@@ -257,9 +257,7 @@ def test_request_entry_authenticated_user_public_room(settings):
|
||||
with (
|
||||
mock.patch.object(LobbyService, "notify_participants", return_value=None),
|
||||
mock.patch.object(
|
||||
LobbyService,
|
||||
"_get_or_create_participant_id",
|
||||
return_value="2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
LobbyService, "_get_or_create_participant_id", return_value="123"
|
||||
),
|
||||
mock.patch.object(
|
||||
utils, "generate_livekit_config", return_value={"token": "test-token"}
|
||||
@@ -276,11 +274,11 @@ def test_request_entry_authenticated_user_public_room(settings):
|
||||
# Verify the lobby cookie was set
|
||||
cookie = response.cookies.get("mocked-cookie")
|
||||
assert cookie is not None
|
||||
assert cookie.value == "2f7f162fe7d1421b90e702bfbfbf8def"
|
||||
assert cookie.value == "123"
|
||||
|
||||
# Verify response content matches expected structure and values
|
||||
assert response.json() == {
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "123",
|
||||
"username": "test_user",
|
||||
"status": "accepted",
|
||||
"color": "mocked-color",
|
||||
@@ -302,9 +300,9 @@ def test_request_entry_waiting_participant_public_room(settings):
|
||||
|
||||
# Add a waiting participant to the room's lobby cache
|
||||
cache.set(
|
||||
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
f"mocked-cache-prefix_{room.id}_participant1",
|
||||
{
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "participant1",
|
||||
"username": "user1",
|
||||
"status": "waiting",
|
||||
"color": "#123456",
|
||||
@@ -312,7 +310,7 @@ def test_request_entry_waiting_participant_public_room(settings):
|
||||
)
|
||||
|
||||
# Simulate a browser with existing participant cookie
|
||||
client.cookies.load({"mocked-cookie": "2f7f162fe7d1421b90e702bfbfbf8def"})
|
||||
client.cookies.load({"mocked-cookie": "participant1"})
|
||||
|
||||
with (
|
||||
mock.patch.object(LobbyService, "notify_participants", return_value=None),
|
||||
@@ -330,11 +328,11 @@ def test_request_entry_waiting_participant_public_room(settings):
|
||||
# Verify the lobby cookie was set
|
||||
cookie = response.cookies.get("mocked-cookie")
|
||||
assert cookie is not None
|
||||
assert cookie.value == "2f7f162fe7d1421b90e702bfbfbf8def"
|
||||
assert cookie.value == "participant1"
|
||||
|
||||
# Verify response content matches expected structure and values
|
||||
assert response.json() == {
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "participant1",
|
||||
"username": "user1",
|
||||
"status": "accepted",
|
||||
"color": "#123456",
|
||||
@@ -381,7 +379,7 @@ def test_allow_participant_to_enter_anonymous():
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
||||
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
|
||||
{"participant_id": "test-id", "allow_entry": True},
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
@@ -396,7 +394,7 @@ def test_allow_participant_to_enter_non_owner():
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
||||
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
|
||||
{"participant_id": "test-id", "allow_entry": True},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
@@ -414,7 +412,7 @@ def test_allow_participant_to_enter_public_room():
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
||||
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
|
||||
{"participant_id": "test-id", "allow_entry": True},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
@@ -437,9 +435,9 @@ def test_allow_participant_to_enter_success(settings, allow_entry, updated_statu
|
||||
settings.LOBBY_KEY_PREFIX = "mocked-cache-prefix"
|
||||
|
||||
cache.set(
|
||||
f"mocked-cache-prefix_{room.id!s}_2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
f"mocked-cache-prefix_{room.id!s}_participant1",
|
||||
{
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "test-id",
|
||||
"status": "waiting",
|
||||
"username": "foo",
|
||||
"color": "123",
|
||||
@@ -448,18 +446,13 @@ def test_allow_participant_to_enter_success(settings, allow_entry, updated_statu
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
||||
{
|
||||
"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"allow_entry": allow_entry,
|
||||
},
|
||||
{"participant_id": "participant1", "allow_entry": allow_entry},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"message": "Participant was updated."}
|
||||
|
||||
participant_data = cache.get(
|
||||
f"mocked-cache-prefix_{room.id!s}_2f7f162fe7d1421b90e702bfbfbf8def"
|
||||
)
|
||||
participant_data = cache.get(f"mocked-cache-prefix_{room.id!s}_participant1")
|
||||
assert participant_data.get("status") == updated_status
|
||||
|
||||
|
||||
@@ -475,14 +468,12 @@ def test_allow_participant_to_enter_participant_not_found(settings):
|
||||
|
||||
settings.LOBBY_KEY_PREFIX = "mocked-cache-prefix"
|
||||
|
||||
participant_data = cache.get(
|
||||
f"mocked-cache-prefix_{room.id!s}_2f7f162fe7d1421b90e702bfbfbf8def"
|
||||
)
|
||||
participant_data = cache.get(f"mocked-cache-prefix_{room.id!s}_test-id")
|
||||
assert participant_data is None
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
||||
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
|
||||
{"participant_id": "test-id", "allow_entry": True},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
@@ -572,18 +563,18 @@ def test_list_waiting_participants_success(settings):
|
||||
|
||||
# Add participants in the lobby
|
||||
cache.set(
|
||||
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
f"mocked-cache-prefix_{room.id}_participant1",
|
||||
{
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "participant1",
|
||||
"username": "user1",
|
||||
"status": "waiting",
|
||||
"color": "#123456",
|
||||
},
|
||||
)
|
||||
cache.set(
|
||||
f"mocked-cache-prefix_{room.id}_f4ca3ab8a6c04ad88097b8da33f60f10",
|
||||
f"mocked-cache-prefix_{room.id}_participant2",
|
||||
{
|
||||
"id": "f4ca3ab8a6c04ad88097b8da33f60f10",
|
||||
"id": "participant2",
|
||||
"username": "user2",
|
||||
"status": "waiting",
|
||||
"color": "#654321",
|
||||
@@ -597,13 +588,13 @@ def test_list_waiting_participants_success(settings):
|
||||
participants = response.json().get("participants")
|
||||
assert sorted(participants, key=lambda p: p["id"]) == [
|
||||
{
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "participant1",
|
||||
"username": "user1",
|
||||
"status": "waiting",
|
||||
"color": "#123456",
|
||||
},
|
||||
{
|
||||
"id": "f4ca3ab8a6c04ad88097b8da33f60f10",
|
||||
"id": "participant2",
|
||||
"username": "user2",
|
||||
"status": "waiting",
|
||||
"color": "#654321",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Test LiveKit webhook endpoint on the rooms API.
|
||||
"""
|
||||
|
||||
# ruff: noqa: PLR0913
|
||||
# pylint: disable=R0913,W0621,R0917,W0613
|
||||
import base64
|
||||
import hashlib
|
||||
@@ -24,7 +25,7 @@ def webhook_event_data():
|
||||
"name": "00000000-0000-0000-0000-000000000000",
|
||||
"emptyTimeout": 300,
|
||||
"creationTime": "1692627281",
|
||||
"turnPassword": "fake-turn-password",
|
||||
"turnPassword": "2Pvdj+/WV1xV4EkB8klJ9xkXDWY=",
|
||||
"enabledCodecs": [
|
||||
{"mime": "audio/opus"},
|
||||
{"mime": "video/H264"},
|
||||
|
||||
@@ -102,7 +102,7 @@ def test_models_rooms_access_rights_none(django_assert_num_queries):
|
||||
with django_assert_num_queries(0):
|
||||
assert room.get_role(None) is None
|
||||
with django_assert_num_queries(0):
|
||||
assert room.is_administrator_or_owner(None) is False
|
||||
assert room.is_administrator(None) is False
|
||||
with django_assert_num_queries(0):
|
||||
assert room.is_owner(None) is False
|
||||
|
||||
@@ -115,7 +115,7 @@ def test_models_rooms_access_rights_anonymous(django_assert_num_queries):
|
||||
with django_assert_num_queries(0):
|
||||
assert room.get_role(user) is None
|
||||
with django_assert_num_queries(0):
|
||||
assert room.is_administrator_or_owner(user) is False
|
||||
assert room.is_administrator(user) is False
|
||||
with django_assert_num_queries(0):
|
||||
assert room.is_owner(user) is False
|
||||
|
||||
@@ -128,7 +128,7 @@ def test_models_rooms_access_rights_authenticated(django_assert_num_queries):
|
||||
with django_assert_num_queries(1):
|
||||
assert room.get_role(user) is None
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_administrator_or_owner(user) is False
|
||||
assert room.is_administrator(user) is False
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_owner(user) is False
|
||||
|
||||
@@ -141,7 +141,7 @@ def test_models_rooms_access_rights_member_direct(django_assert_num_queries):
|
||||
with django_assert_num_queries(1):
|
||||
assert room.get_role(user) == "member"
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_administrator_or_owner(user) is False
|
||||
assert room.is_administrator(user) is False
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_owner(user) is False
|
||||
|
||||
@@ -154,7 +154,7 @@ def test_models_rooms_access_rights_administrator_direct(django_assert_num_queri
|
||||
with django_assert_num_queries(1):
|
||||
assert room.get_role(user) == "administrator"
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_administrator_or_owner(user) is True
|
||||
assert room.is_administrator(user) is True
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_owner(user) is False
|
||||
|
||||
@@ -167,7 +167,7 @@ def test_models_rooms_access_rights_owner_direct(django_assert_num_queries):
|
||||
with django_assert_num_queries(1):
|
||||
assert room.get_role(user) == "owner"
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_administrator_or_owner(user) is True
|
||||
assert room.is_administrator(user) is True
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_owner(user) is True
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
meet's sandbox management script.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from os import environ
|
||||
|
||||
if __name__ == "__main__":
|
||||
environ.setdefault("DJANGO_SETTINGS_MODULE", "meet.settings")
|
||||
environ.setdefault("DJANGO_CONFIGURATION", "Development")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "meet.settings")
|
||||
os.environ.setdefault("DJANGO_CONFIGURATION", "Development")
|
||||
|
||||
from configurations.management import execute_from_command_line
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""Meet celery configuration file."""
|
||||
|
||||
from os import environ
|
||||
import os
|
||||
|
||||
from celery import Celery
|
||||
from configurations.importer import install
|
||||
|
||||
# Set the default Django settings module for the 'celery' program.
|
||||
environ.setdefault("DJANGO_SETTINGS_MODULE", "meet.settings")
|
||||
environ.setdefault("DJANGO_CONFIGURATION", "Development")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "meet.settings")
|
||||
os.environ.setdefault("DJANGO_CONFIGURATION", "Development")
|
||||
|
||||
install(check_options=True)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||
"""
|
||||
|
||||
import json
|
||||
from os import path
|
||||
import os
|
||||
from socket import gethostbyname, gethostname
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -22,7 +22,7 @@ from sentry_sdk.integrations.django import DjangoIntegration
|
||||
from sentry_sdk.integrations.logging import ignore_logger
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = path.dirname(path.dirname(path.abspath(__file__)))
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def get_release():
|
||||
@@ -38,7 +38,7 @@ def get_release():
|
||||
# Try to get the current release from the version.json file generated by the
|
||||
# CI during the Docker image build
|
||||
try:
|
||||
with open(path.join(BASE_DIR, "version.json"), encoding="utf8") as version:
|
||||
with open(os.path.join(BASE_DIR, "version.json"), encoding="utf8") as version:
|
||||
return json.load(version)["version"]
|
||||
except FileNotFoundError:
|
||||
return "NA" # Default: not available
|
||||
@@ -69,7 +69,7 @@ class Base(Configuration):
|
||||
|
||||
API_VERSION = "v1.0"
|
||||
|
||||
DATA_DIR = values.Value(path.join("/", "data"), environ_name="DATA_DIR")
|
||||
DATA_DIR = values.Value(os.path.join("/", "data"), environ_name="DATA_DIR")
|
||||
|
||||
# Security
|
||||
ALLOWED_HOSTS = values.ListValue([])
|
||||
@@ -106,9 +106,9 @@ class Base(Configuration):
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = path.join(DATA_DIR, "static")
|
||||
STATIC_ROOT = os.path.join(DATA_DIR, "static")
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = path.join(DATA_DIR, "media")
|
||||
MEDIA_ROOT = os.path.join(DATA_DIR, "media")
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
@@ -166,7 +166,7 @@ class Base(Configuration):
|
||||
)
|
||||
)
|
||||
|
||||
LOCALE_PATHS = (path.join(BASE_DIR, "locale"),)
|
||||
LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),)
|
||||
|
||||
TIME_ZONE = "UTC"
|
||||
USE_I18N = True
|
||||
@@ -176,7 +176,7 @@ class Base(Configuration):
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [path.join(BASE_DIR, "templates")],
|
||||
"DIRS": [os.path.join(BASE_DIR, "templates")],
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
@@ -319,6 +319,9 @@ class Base(Configuration):
|
||||
"feedback": values.DictValue(
|
||||
{}, environ_name="FRONTEND_FEEDBACK", environ_prefix=None
|
||||
),
|
||||
"transcript": values.DictValue(
|
||||
{}, environ_name="FRONTEND_TRANSCRIPT", environ_prefix=None
|
||||
)
|
||||
}
|
||||
|
||||
# Mail
|
||||
@@ -340,7 +343,7 @@ class Base(Configuration):
|
||||
|
||||
# CORS
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
CORS_ALLOW_ALL_ORIGINS = values.BooleanValue(False)
|
||||
CORS_ALLOW_ALL_ORIGINS = values.BooleanValue(True)
|
||||
CORS_ALLOWED_ORIGINS = values.ListValue([])
|
||||
CORS_ALLOWED_ORIGIN_REGEXES = values.ListValue([])
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
from os import environ
|
||||
import os
|
||||
|
||||
from configurations.wsgi import get_wsgi_application
|
||||
|
||||
environ.setdefault("DJANGO_SETTINGS_MODULE", "meet.settings")
|
||||
environ.setdefault("DJANGO_CONFIGURATION", "Development")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "meet.settings")
|
||||
os.environ.setdefault("DJANGO_CONFIGURATION", "Development")
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
||||
+28
-27
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "meet"
|
||||
version = "0.1.26"
|
||||
version = "0.1.23"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -25,38 +25,39 @@ license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"boto3==1.38.42",
|
||||
"boto3==1.37.24",
|
||||
"Brotli==1.1.0",
|
||||
"brevo-python==1.1.2",
|
||||
"celery[redis]==5.5.3",
|
||||
"celery[redis]==5.4.0",
|
||||
"django-configurations==2.5.1",
|
||||
"django-cors-headers==4.7.0",
|
||||
"django-countries==7.6.1",
|
||||
"django-lasuite==0.0.10",
|
||||
"django-lasuite==0.0.7",
|
||||
"django-parler==2.3",
|
||||
"redis==5.2.1",
|
||||
"django-redis==6.0.0",
|
||||
"django-storages[s3]==1.14.6",
|
||||
"django-redis==5.4.0",
|
||||
"django-storages[s3]==1.14.5",
|
||||
"django-timezone-field>=5.1",
|
||||
"django==5.2.3",
|
||||
"djangorestframework==3.16.0",
|
||||
"django==5.1.9",
|
||||
"djangorestframework==3.15.2",
|
||||
"drf_spectacular==0.28.0",
|
||||
"dockerflow==2024.4.2",
|
||||
"easy_thumbnails==2.10",
|
||||
"factory_boy==3.3.3",
|
||||
"gunicorn==23.0.0",
|
||||
"jsonschema==4.24.0",
|
||||
"markdown==3.8.2",
|
||||
"jsonschema==4.23.0",
|
||||
"june-analytics-python==2.3.0",
|
||||
"markdown==3.7",
|
||||
"nested-multipart-parser==1.5.0",
|
||||
"psycopg[binary]==3.2.9",
|
||||
"psycopg[binary]==3.2.6",
|
||||
"PyJWT==2.10.1",
|
||||
"python-frontmatter==1.1.0",
|
||||
"requests==2.32.4",
|
||||
"sentry-sdk==2.30.0",
|
||||
"requests==2.32.3",
|
||||
"sentry-sdk==2.24.1",
|
||||
"whitenoise==6.9.0",
|
||||
"mozilla-django-oidc==4.0.1",
|
||||
"livekit-api==1.0.3",
|
||||
"aiohttp==3.12.13",
|
||||
"livekit-api==1.0.2",
|
||||
"aiohttp==3.11.14",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -67,22 +68,22 @@ dependencies = [
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"django-extensions==4.1",
|
||||
"drf-spectacular-sidecar==2025.6.1",
|
||||
"freezegun==1.5.2",
|
||||
"django-extensions==3.2.3",
|
||||
"drf-spectacular-sidecar==2025.3.1",
|
||||
"freezegun==1.5.1",
|
||||
"ipdb==0.13.13",
|
||||
"ipython==9.3.0",
|
||||
"pyfakefs==5.9.1",
|
||||
"ipython==9.0.2",
|
||||
"pyfakefs==5.8.0",
|
||||
"pylint-django==2.6.1",
|
||||
"pylint==3.3.7",
|
||||
"pytest-cov==6.2.1",
|
||||
"pytest-django==4.11.1",
|
||||
"pytest==8.4.1",
|
||||
"pylint==3.3.6",
|
||||
"pytest-cov==6.0.0",
|
||||
"pytest-django==4.10.0",
|
||||
"pytest==8.3.5",
|
||||
"pytest-icdiff==0.9",
|
||||
"pytest-xdist==3.7.0",
|
||||
"pytest-xdist==3.6.1",
|
||||
"responses==0.25.7",
|
||||
"ruff==0.12.0",
|
||||
"types-requests==2.32.4.20250611",
|
||||
"ruff==0.11.2",
|
||||
"types-requests==2.32.0.20250306",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
server_tokens off;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
|
||||
Generated
+56
-80
@@ -1,28 +1,27 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"version": "0.1.26",
|
||||
"version": "0.1.23",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "meet",
|
||||
"version": "0.1.26",
|
||||
"version": "0.1.23",
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "2.9.12",
|
||||
"@livekit/components-styles": "1.1.6",
|
||||
"@livekit/track-processors": "0.5.7",
|
||||
"@livekit/components-react": "2.8.1",
|
||||
"@livekit/components-styles": "1.1.5",
|
||||
"@livekit/track-processors": "0.5.6",
|
||||
"@pandacss/preset-panda": "0.53.6",
|
||||
"@react-aria/toast": "3.0.2",
|
||||
"@remixicon/react": "4.6.0",
|
||||
"@tanstack/react-query": "5.76.0",
|
||||
"@timephy/rnnoise-wasm": "1.0.0",
|
||||
"crisp-sdk-web": "1.0.25",
|
||||
"hoofd": "1.7.3",
|
||||
"i18next": "25.1.2",
|
||||
"i18next-browser-languagedetector": "8.1.0",
|
||||
"i18next-parser": "9.3.0",
|
||||
"i18next-resources-to-backend": "1.2.1",
|
||||
"livekit-client": "2.13.8",
|
||||
"livekit-client": "2.11.4",
|
||||
"posthog-js": "1.240.6",
|
||||
"react": "18.3.1",
|
||||
"react-aria-components": "1.8.0",
|
||||
@@ -406,9 +405,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz",
|
||||
"integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==",
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz",
|
||||
"integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==",
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/@clack/core": {
|
||||
@@ -982,22 +981,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz",
|
||||
"integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==",
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.0.tgz",
|
||||
"integrity": "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.13",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz",
|
||||
"integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==",
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz",
|
||||
"integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.0",
|
||||
"@floating-ui/utils": "^0.2.9"
|
||||
"@floating-ui/utils": "^0.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
@@ -1215,39 +1214,48 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/components-core": {
|
||||
"version": "0.12.8",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.12.8.tgz",
|
||||
"integrity": "sha512-ZqQ88DkZZw6h4XY/lFklOFsM76zZX0mIpa6HKxDgMgW3QpDjl7oOpQCHZYvaDhmJJ9X2m58oOCuf3RUdTKSJMA==",
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.12.1.tgz",
|
||||
"integrity": "sha512-R7qWoVzPckOYxEHZgP3Kp8u+amu+isnTptgoZV7+bpmLRBHI7mWnaD+0uDWlyIMjI1pBbK3wHg0ILKa5UytI+A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "1.6.13",
|
||||
"@floating-ui/dom": "1.6.11",
|
||||
"loglevel": "1.9.1",
|
||||
"rxjs": "7.8.2"
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"livekit-client": "^2.13.3",
|
||||
"livekit-client": "^2.8.1",
|
||||
"tslib": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/components-react": {
|
||||
"version": "2.9.12",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.9.12.tgz",
|
||||
"integrity": "sha512-GSbVNEeJSGvjyRzUVHJvBahAvrC/zAG7gOD+UlgYnxjA1fEte4gSUtwbcdVauABGWZGtiaU2cQvSuNhCQaXRZQ==",
|
||||
"node_modules/@livekit/components-core/node_modules/rxjs": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@livekit/components-core": "0.12.8",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/components-react": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.8.1.tgz",
|
||||
"integrity": "sha512-XpuDu7iDMcN4pkV8CYNzHf9hLNdYOeEtbmCr7Zesy6Au3BxUl4aS1Ajmg0b75Rx7zTlkyCJt9Lm4VrEqbJCI6Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@livekit/components-core": "0.12.1",
|
||||
"clsx": "2.1.1",
|
||||
"usehooks-ts": "3.1.1"
|
||||
"usehooks-ts": "3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@livekit/krisp-noise-filter": "^0.2.12",
|
||||
"livekit-client": "^2.13.3",
|
||||
"livekit-client": "^2.8.1",
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18",
|
||||
"tslib": "^2.6.2"
|
||||
@@ -1259,9 +1267,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/components-styles": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-styles/-/components-styles-1.1.6.tgz",
|
||||
"integrity": "sha512-V6zfuREC2ksW8z6T6WSbEvdLB5ICVikGz1GtLr59UcxHDyAsKDbuDHAyl3bF3xBqPKYmY3GWF3Qk39rnScyOtA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-styles/-/components-styles-1.1.5.tgz",
|
||||
"integrity": "sha512-SocIPcwm18S28zVruvJcmiHfbUIwGTfxGbUOIp1Db78EON/iJ2v7B2g/xD5sr+c7jXoV1DNUPl2qQiFW2S9dbw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -1274,24 +1282,23 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@livekit/protocol": {
|
||||
"version": "1.39.2",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.39.2.tgz",
|
||||
"integrity": "sha512-kYbIO/JlC6cylSxd4WJrBps9+zoZ9gifL7t3iW9whT8rbo5jHx03I4dwBLhzOonVyX+memSEO90m/ymNoT+aAw==",
|
||||
"version": "1.36.1",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.36.1.tgz",
|
||||
"integrity": "sha512-nN3QnITAQ5yXk7UKfotH7CRWIlEozNWeKVyFJ0/+dtSzvWP/ib+10l1DDnRYi3A1yICJOGAKFgJ5d6kmi1HCUA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/track-processors": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/track-processors/-/track-processors-0.5.7.tgz",
|
||||
"integrity": "sha512-/2SkuVAF+YiPNtOi9zQJz/yH1WGaK53XZ3PaESpLOiEYUBsYky13BrriXCXUf6kwn5R5+7ZsYWc2k3XSsAuLtg==",
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/track-processors/-/track-processors-0.5.6.tgz",
|
||||
"integrity": "sha512-TlzObrSlp2PKor4VXqg6iefLRFVEb2T1lXwddBFdkPod60XVgYoMOj7V5xJm+UTE2MEtlE0003vUli9PyQGB1g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@mediapipe/tasks-vision": "0.10.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/dom-mediacapture-transform": "^0.1.9",
|
||||
"livekit-client": "^1.12.0 || ^2.1.0"
|
||||
}
|
||||
},
|
||||
@@ -3889,11 +3896,6 @@
|
||||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@timephy/rnnoise-wasm": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@timephy/rnnoise-wasm/-/rnnoise-wasm-1.0.0.tgz",
|
||||
"integrity": "sha512-zzRdFyALbhaNIuEo3LKazPWxabatbPxkaBrHzRjGDlVHX2dFklh68HUENHlvHkKsq+OOQSC1ag5sGPDMDgg2CA=="
|
||||
},
|
||||
"node_modules/@ts-morph/common": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.25.0.tgz",
|
||||
@@ -3947,30 +3949,6 @@
|
||||
"@babel/types": "^7.20.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/dom-mediacapture-record": {
|
||||
"version": "1.0.22",
|
||||
"resolved": "https://registry.npmjs.org/@types/dom-mediacapture-record/-/dom-mediacapture-record-1.0.22.tgz",
|
||||
"integrity": "sha512-mUMZLK3NvwRLcAAT9qmcK+9p7tpU2FHdDsntR3YI4+GY88XrgG4XiE7u1Q2LAN2/FZOz/tdMDC3GQCR4T8nFuw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/dom-mediacapture-transform": {
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/dom-mediacapture-transform/-/dom-mediacapture-transform-0.1.11.tgz",
|
||||
"integrity": "sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/dom-webcodecs": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/dom-webcodecs": {
|
||||
"version": "0.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.15.tgz",
|
||||
"integrity": "sha512-omOlCPvTWyPm4ZE5bZUhlSvnHM2ZWM2U+1cPiYFL/e8aV5O9MouELp+L4dMKNTON0nTeHqEg+KWDfFQMY5Wkaw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
@@ -7369,13 +7347,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/livekit-client": {
|
||||
"version": "2.13.8",
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.13.8.tgz",
|
||||
"integrity": "sha512-HXE89EizbgS/V+gh74GLGYOzvQ9d8qAsWt/N5lYkVGJX8PCq+7WK8whq5baFF7FBGkVPaE5NmW7Nk2V5nHvvWA==",
|
||||
"version": "2.11.4",
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.11.4.tgz",
|
||||
"integrity": "sha512-V82NdyhHo3iBxxQmPTk95Fe2+VZMABWZL56t9oZmMgycoF8li8gs/KG7yyDsKki50EeKLgwOmlwqx/Xf0pc0+Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@livekit/mutex": "1.1.1",
|
||||
"@livekit/protocol": "1.39.2",
|
||||
"@livekit/protocol": "1.36.1",
|
||||
"events": "^3.3.0",
|
||||
"loglevel": "^1.9.2",
|
||||
"sdp-transform": "^2.15.0",
|
||||
@@ -7383,9 +7361,6 @@
|
||||
"tslib": "2.8.1",
|
||||
"typed-emitter": "^2.1.0",
|
||||
"webrtc-adapter": "^9.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/dom-mediacapture-record": "^1"
|
||||
}
|
||||
},
|
||||
"node_modules/livekit-client/node_modules/loglevel": {
|
||||
@@ -8697,6 +8672,7 @@
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -9526,9 +9502,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/usehooks-ts": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz",
|
||||
"integrity": "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.0.tgz",
|
||||
"integrity": "sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash.debounce": "^4.0.8"
|
||||
@@ -9537,7 +9513,7 @@
|
||||
"node": ">=16.15.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||
"react": "^16.8.0 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"private": true,
|
||||
"version": "0.1.26",
|
||||
"version": "0.1.23",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "panda codegen && vite",
|
||||
@@ -13,21 +13,20 @@
|
||||
"check": "prettier --check ./src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "2.9.12",
|
||||
"@livekit/components-styles": "1.1.6",
|
||||
"@livekit/track-processors": "0.5.7",
|
||||
"@livekit/components-react": "2.8.1",
|
||||
"@livekit/components-styles": "1.1.5",
|
||||
"@livekit/track-processors": "0.5.6",
|
||||
"@pandacss/preset-panda": "0.53.6",
|
||||
"@react-aria/toast": "3.0.2",
|
||||
"@remixicon/react": "4.6.0",
|
||||
"@tanstack/react-query": "5.76.0",
|
||||
"@timephy/rnnoise-wasm": "1.0.0",
|
||||
"crisp-sdk-web": "1.0.25",
|
||||
"hoofd": "1.7.3",
|
||||
"i18next": "25.1.2",
|
||||
"i18next-browser-languagedetector": "8.1.0",
|
||||
"i18next-parser": "9.3.0",
|
||||
"i18next-resources-to-backend": "1.2.1",
|
||||
"livekit-client": "2.13.8",
|
||||
"livekit-client": "2.11.4",
|
||||
"posthog-js": "1.240.6",
|
||||
"react": "18.3.1",
|
||||
"react-aria-components": "1.8.0",
|
||||
|
||||
@@ -10,12 +10,19 @@ export interface ApiConfig {
|
||||
}
|
||||
support?: {
|
||||
id: string
|
||||
help_article_transcript: string
|
||||
help_article_recording: string
|
||||
help_article_more_tools: string
|
||||
}
|
||||
feedback: {
|
||||
url: string
|
||||
}
|
||||
transcript: {
|
||||
form_beta_users: string
|
||||
}
|
||||
silence_livekit_debug_logs?: boolean
|
||||
is_silent_login_enabled?: boolean
|
||||
custom_css?: string
|
||||
recording?: {
|
||||
is_enabled?: boolean
|
||||
available_modes?: RecordingMode[]
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useConfig } from '@/api/useConfig'
|
||||
import { useAnalytics } from '@/features/analytics/hooks/useAnalytics'
|
||||
import { useSupport } from '@/features/support/hooks/useSupport'
|
||||
import { useSyncUserPreferencesWithBackend } from '@/features/auth'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export const AppInitialization = () => {
|
||||
const { data } = useConfig()
|
||||
@@ -12,11 +13,22 @@ export const AppInitialization = () => {
|
||||
analytics = {},
|
||||
support = {},
|
||||
silence_livekit_debug_logs = false,
|
||||
custom_css = '',
|
||||
} = data || {}
|
||||
|
||||
useAnalytics(analytics)
|
||||
useSupport(support)
|
||||
|
||||
useEffect(() => {
|
||||
if (custom_css) {
|
||||
const link = document.createElement('link')
|
||||
link.href = custom_css
|
||||
link.id = 'visio-custom-css'
|
||||
link.rel = 'stylesheet'
|
||||
document.head.appendChild(link)
|
||||
}
|
||||
}, [custom_css])
|
||||
|
||||
silenceLiveKitLogs(silence_livekit_debug_logs)
|
||||
|
||||
return null
|
||||
|
||||
@@ -10,8 +10,7 @@ export const NotFoundScreen = () => {
|
||||
<Screen layout="centered">
|
||||
<CenteredContent title={t('notFound.heading')} withBackButton>
|
||||
<Text centered>
|
||||
{t('notFound.body')}{' '}
|
||||
<Bold>https://visio.numerique.gouv.fr/xxx-yyyy-zzz.</Bold>
|
||||
{t('notFound.body')} <Bold>{window.origin}/xxx-yyyy-zzz.</Bold>
|
||||
</Text>
|
||||
</CenteredContent>
|
||||
</Screen>
|
||||
|
||||
@@ -2,5 +2,4 @@ export enum FeatureFlags {
|
||||
Transcript = 'transcription-summary',
|
||||
ScreenRecording = 'screen-recording',
|
||||
faceLandmarks = 'face-landmarks',
|
||||
noiseReduction = 'noise-reduction',
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Button, LinkButton } from '@/primitives'
|
||||
import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BETA_USERS_FORM_URL } from '@/utils/constants'
|
||||
import { useConfig } from '@/api/useConfig'
|
||||
|
||||
const Heading = styled('h2', {
|
||||
base: {
|
||||
@@ -171,6 +171,8 @@ export const IntroSlider = () => {
|
||||
const { t } = useTranslation('home', { keyPrefix: 'introSlider' })
|
||||
const NUMBER_SLIDES = SLIDES.length
|
||||
|
||||
const { data } = useConfig()
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<div
|
||||
@@ -203,7 +205,7 @@ export const IntroSlider = () => {
|
||||
<Body>{t(`${slide.key}.body`)}</Body>
|
||||
{slide.isAvailableInBeta && (
|
||||
<LinkButton
|
||||
href={BETA_USERS_FORM_URL}
|
||||
href={data?.transcript.form_beta_users}
|
||||
target="_blank"
|
||||
tooltip={t('beta.tooltip')}
|
||||
variant={'primary'}
|
||||
|
||||
@@ -23,7 +23,7 @@ export const JoinMeetingDialog = () => {
|
||||
name="roomId"
|
||||
label={t('joinInputLabel')}
|
||||
description={t('joinInputExample', {
|
||||
example: 'https://visio.numerique.gouv.fr/azer-tyu-qsdf',
|
||||
example: window.origin + '/azer-tyu-qsdf',
|
||||
})}
|
||||
validate={(value) => {
|
||||
return !isRoomValid(value.trim()) ? (
|
||||
|
||||
@@ -14,7 +14,6 @@ import { useEffect, useMemo, useState } from 'react'
|
||||
import { ConnectionState, RoomEvent } from 'livekit-client'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RecordingStatus, recordingStore } from '@/stores/recording'
|
||||
import { CRISP_HELP_ARTICLE_RECORDING } from '@/utils/constants'
|
||||
|
||||
import {
|
||||
NotificationType,
|
||||
@@ -24,8 +23,10 @@ import {
|
||||
import posthog from 'posthog-js'
|
||||
import { useSnapshot } from 'valtio/index'
|
||||
import { Spinner } from '@/primitives/Spinner'
|
||||
import { useConfig } from '@/api/useConfig'
|
||||
|
||||
export const ScreenRecordingSidePanel = () => {
|
||||
const { data } = useConfig()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const recordingSnap = useSnapshot(recordingStore)
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'screenRecording' })
|
||||
@@ -199,9 +200,11 @@ export const ScreenRecordingSidePanel = () => {
|
||||
})}
|
||||
>
|
||||
{t('start.body')} <br />{' '}
|
||||
<A href={CRISP_HELP_ARTICLE_RECORDING} target="_blank">
|
||||
{t('start.linkMore')}
|
||||
</A>
|
||||
{data?.support?.help_article_recording && (
|
||||
<A href={data.support.help_article_recording} target="_blank">
|
||||
{t('start.linkMore')}
|
||||
</A>
|
||||
)}
|
||||
</Text>
|
||||
<Button
|
||||
isDisabled={isDisabled}
|
||||
|
||||
@@ -16,10 +16,6 @@ import { useEffect, useMemo, useState } from 'react'
|
||||
import { ConnectionState, RoomEvent } from 'livekit-client'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RecordingStatus, recordingStore } from '@/stores/recording'
|
||||
import {
|
||||
BETA_USERS_FORM_URL,
|
||||
CRISP_HELP_ARTICLE_TRANSCRIPT,
|
||||
} from '@/utils/constants'
|
||||
import { FeatureFlags } from '@/features/analytics/enums'
|
||||
import {
|
||||
NotificationType,
|
||||
@@ -29,8 +25,11 @@ import {
|
||||
import posthog from 'posthog-js'
|
||||
import { useSnapshot } from 'valtio/index'
|
||||
import { Spinner } from '@/primitives/Spinner'
|
||||
import { useConfig } from '@/api/useConfig'
|
||||
|
||||
export const TranscriptSidePanel = () => {
|
||||
const { data } = useConfig()
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'transcript' })
|
||||
|
||||
@@ -161,9 +160,14 @@ export const TranscriptSidePanel = () => {
|
||||
>
|
||||
{t('notAdminOrOwner.body')}
|
||||
<br />
|
||||
<A href={CRISP_HELP_ARTICLE_TRANSCRIPT} target="_blank">
|
||||
{t('notAdminOrOwner.linkMore')}
|
||||
</A>
|
||||
{data?.support?.help_article_transcript && (
|
||||
<A
|
||||
href={data.support.help_article_transcript}
|
||||
target="_blank"
|
||||
>
|
||||
{t('notAdminOrOwner.linkMore')}
|
||||
</A>
|
||||
)}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
@@ -180,14 +184,19 @@ export const TranscriptSidePanel = () => {
|
||||
})}
|
||||
>
|
||||
{t('beta.body')}{' '}
|
||||
<A href={CRISP_HELP_ARTICLE_TRANSCRIPT} target="_blank">
|
||||
{t('start.linkMore')}
|
||||
</A>
|
||||
{data?.support?.help_article_transcript && (
|
||||
<A
|
||||
href={data.support.help_article_transcript}
|
||||
target="_blank"
|
||||
>
|
||||
{t('start.linkMore')}
|
||||
</A>
|
||||
)}
|
||||
</Text>
|
||||
<LinkButton
|
||||
size="sm"
|
||||
variant="tertiary"
|
||||
href={BETA_USERS_FORM_URL}
|
||||
href={data?.transcript.form_beta_users}
|
||||
target="_blank"
|
||||
>
|
||||
{t('beta.button')}
|
||||
@@ -263,9 +272,14 @@ export const TranscriptSidePanel = () => {
|
||||
})}
|
||||
>
|
||||
{t('start.body')} <br />{' '}
|
||||
<A href={CRISP_HELP_ARTICLE_TRANSCRIPT} target="_blank">
|
||||
{t('start.linkMore')}
|
||||
</A>
|
||||
{data?.support?.help_article_transcript && (
|
||||
<A
|
||||
href={data.support.help_article_transcript}
|
||||
target="_blank"
|
||||
>
|
||||
{t('start.linkMore')}
|
||||
</A>
|
||||
)}
|
||||
</Text>
|
||||
<Button
|
||||
isDisabled={isDisabled}
|
||||
|
||||
@@ -15,8 +15,8 @@ import { InviteDialog } from './InviteDialog'
|
||||
import { VideoConference } from '../livekit/prefabs/VideoConference'
|
||||
import posthog from 'posthog-js'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { LocalUserChoices } from '../routes/Room'
|
||||
import { BackgroundProcessorFactory } from '../livekit/components/blur'
|
||||
import { LocalUserChoices } from '@/stores/userChoices'
|
||||
|
||||
export const Conference = ({
|
||||
roomId,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { SelectToggleDevice } from '../livekit/components/controls/SelectToggleD
|
||||
import { Field } from '@/primitives/Field'
|
||||
import { Button, Dialog, Text, Form } from '@/primitives'
|
||||
import { HStack, VStack } from '@/styled-system/jsx'
|
||||
import { LocalUserChoices } from '../routes/Room'
|
||||
import { Heading } from 'react-aria-components'
|
||||
import { RiImageCircleAiFill } from '@remixicon/react'
|
||||
import {
|
||||
@@ -27,7 +28,6 @@ import { ApiLobbyStatus, ApiRequestEntry } from '../api/requestEntry'
|
||||
import { Spinner } from '@/primitives/Spinner'
|
||||
import { ApiAccessLevel } from '../api/ApiRoom'
|
||||
import { useLoginHint } from '@/hooks/useLoginHint'
|
||||
import { LocalUserChoices } from '@/stores/userChoices'
|
||||
|
||||
const onError = (e: Error) => console.error('ERROR', e)
|
||||
|
||||
@@ -116,25 +116,39 @@ export const Join = ({
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||
|
||||
const {
|
||||
userChoices: {
|
||||
audioEnabled,
|
||||
videoEnabled,
|
||||
audioDeviceId,
|
||||
videoDeviceId,
|
||||
processorSerialized,
|
||||
username,
|
||||
},
|
||||
saveAudioInputEnabled,
|
||||
saveVideoInputEnabled,
|
||||
userChoices: initialUserChoices,
|
||||
saveAudioInputDeviceId,
|
||||
saveVideoInputDeviceId,
|
||||
saveUsername,
|
||||
saveProcessorSerialized,
|
||||
} = usePersistentUserChoices()
|
||||
} = usePersistentUserChoices({})
|
||||
|
||||
const [processor, setProcessor] = useState(
|
||||
BackgroundProcessorFactory.deserializeProcessor(processorSerialized)
|
||||
const [audioEnabled, setAudioEnabled] = useState(true)
|
||||
const [videoEnabled, setVideoEnabled] = useState(true)
|
||||
const [audioDeviceId, setAudioDeviceId] = useState<string>(
|
||||
initialUserChoices.audioDeviceId
|
||||
)
|
||||
const [videoDeviceId, setVideoDeviceId] = useState<string>(
|
||||
initialUserChoices.videoDeviceId
|
||||
)
|
||||
const [username, setUsername] = useState<string>(initialUserChoices.username)
|
||||
const [processor, setProcessor] = useState(
|
||||
BackgroundProcessorFactory.deserializeProcessor(
|
||||
initialUserChoices.processorSerialized
|
||||
)
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
saveAudioInputDeviceId(audioDeviceId)
|
||||
}, [audioDeviceId, saveAudioInputDeviceId])
|
||||
|
||||
useEffect(() => {
|
||||
saveVideoInputDeviceId(videoDeviceId)
|
||||
}, [videoDeviceId, saveVideoInputDeviceId])
|
||||
|
||||
useEffect(() => {
|
||||
saveUsername(username)
|
||||
}, [username, saveUsername])
|
||||
|
||||
useEffect(() => {
|
||||
saveProcessorSerialized(processor?.serialize())
|
||||
@@ -147,8 +161,8 @@ export const Join = ({
|
||||
|
||||
const tracks = usePreviewTracks(
|
||||
{
|
||||
audio: { deviceId: audioDeviceId },
|
||||
video: { deviceId: videoDeviceId },
|
||||
audio: { deviceId: initialUserChoices.audioDeviceId },
|
||||
video: { deviceId: initialUserChoices.videoDeviceId },
|
||||
},
|
||||
onError
|
||||
)
|
||||
@@ -337,9 +351,9 @@ export const Join = ({
|
||||
</H>
|
||||
<Field
|
||||
type="text"
|
||||
onChange={saveUsername}
|
||||
onChange={setUsername}
|
||||
label={t('usernameLabel')}
|
||||
defaultValue={username}
|
||||
defaultValue={initialUserChoices?.username}
|
||||
validate={(value) => !value && t('errors.usernameEmpty')}
|
||||
wrapperProps={{
|
||||
noMargin: true,
|
||||
@@ -460,11 +474,11 @@ export const Join = ({
|
||||
source={Track.Source.Microphone}
|
||||
initialState={audioEnabled}
|
||||
track={audioTrack}
|
||||
initialDeviceId={audioDeviceId}
|
||||
onChange={(enabled) => saveAudioInputEnabled(enabled)}
|
||||
initialDeviceId={initialUserChoices.audioDeviceId}
|
||||
onChange={(enabled) => setAudioEnabled(enabled)}
|
||||
onDeviceError={(error) => console.error(error)}
|
||||
onActiveDeviceChange={(deviceId) =>
|
||||
saveAudioInputDeviceId(deviceId ?? '')
|
||||
setAudioDeviceId(deviceId ?? '')
|
||||
}
|
||||
variant="tertiary"
|
||||
/>
|
||||
@@ -472,11 +486,11 @@ export const Join = ({
|
||||
source={Track.Source.Camera}
|
||||
initialState={videoEnabled}
|
||||
track={videoTrack}
|
||||
initialDeviceId={videoDeviceId}
|
||||
onChange={(enabled) => saveVideoInputEnabled(enabled)}
|
||||
initialDeviceId={initialUserChoices.videoDeviceId}
|
||||
onChange={(enabled) => setVideoEnabled(enabled)}
|
||||
onDeviceError={(error) => console.error(error)}
|
||||
onActiveDeviceChange={(deviceId) =>
|
||||
saveVideoInputDeviceId(deviceId ?? '')
|
||||
setVideoDeviceId(deviceId ?? '')
|
||||
}
|
||||
variant="tertiary"
|
||||
/>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { A, Div, Text } from '@/primitives'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { Button as RACButton } from 'react-aria-components'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { CRISP_HELP_ARTICLE_MORE_TOOLS } from '@/utils/constants'
|
||||
import { ReactNode } from 'react'
|
||||
import { RiFileTextFill, RiLiveFill } from '@remixicon/react'
|
||||
import { SubPanelId, useSidePanel } from '../hooks/useSidePanel'
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
useIsRecordingActive,
|
||||
} from '@/features/recording'
|
||||
import { FeatureFlags } from '@/features/analytics/enums'
|
||||
import { useConfig } from '@/api/useConfig'
|
||||
|
||||
export interface ToolsButtonProps {
|
||||
icon: ReactNode
|
||||
@@ -113,6 +113,7 @@ const ToolButton = ({
|
||||
}
|
||||
|
||||
export const Tools = () => {
|
||||
const { data } = useConfig()
|
||||
const { openTranscript, openScreenRecording, activeSubPanelId } =
|
||||
useSidePanel()
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'moreTools' })
|
||||
@@ -160,10 +161,14 @@ export const Tools = () => {
|
||||
margin="md"
|
||||
>
|
||||
{t('body')}{' '}
|
||||
<A href={CRISP_HELP_ARTICLE_MORE_TOOLS} target="_blank">
|
||||
{t('moreLink')}
|
||||
</A>
|
||||
.
|
||||
{data?.support?.help_article_more_tools && (
|
||||
<>
|
||||
<A href={data?.support?.help_article_more_tools} target="_blank">
|
||||
{t('moreLink')}
|
||||
</A>
|
||||
.
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
{isTranscriptEnabled && (
|
||||
<ToolButton
|
||||
|
||||
@@ -99,7 +99,7 @@ export const SelectToggleDevice = <T extends ToggleSource>({
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||
const trackProps = useTrackToggle(props)
|
||||
|
||||
const { userChoices } = usePersistentUserChoices()
|
||||
const { userChoices } = usePersistentUserChoices({})
|
||||
|
||||
const toggle = () => {
|
||||
if (props.source === Track.Source.Camera) {
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { useRoomContext } from '@livekit/components-react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { DisconnectReason, RoomEvent } from 'livekit-client'
|
||||
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled'
|
||||
import posthog from 'posthog-js'
|
||||
|
||||
export const useConnectionObserver = () => {
|
||||
const room = useRoomContext()
|
||||
const connectionStartTimeRef = useRef<number | null>(null)
|
||||
|
||||
const isAnalyticsEnabled = useIsAnalyticsEnabled()
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAnalyticsEnabled) return
|
||||
|
||||
const handleConnection = () => {
|
||||
// Preserve original connection timestamp across reconnections to measure
|
||||
// total session duration from first connect to final disconnect.
|
||||
if (connectionStartTimeRef.current != null) return
|
||||
connectionStartTimeRef.current = Date.now()
|
||||
posthog.capture('connection-event')
|
||||
}
|
||||
|
||||
const handleReconnect = () => {
|
||||
posthog.capture('reconnect-event')
|
||||
}
|
||||
|
||||
const handleDisconnect = (
|
||||
disconnectReason: DisconnectReason | undefined
|
||||
) => {
|
||||
const connectionEndTime = Date.now()
|
||||
|
||||
posthog.capture('disconnect-event', {
|
||||
// Calculate total session duration from first connection to final disconnect
|
||||
// This duration is sensitive to refreshing the page.
|
||||
sessionDuration: connectionStartTimeRef.current
|
||||
? connectionEndTime - connectionStartTimeRef.current
|
||||
: -1,
|
||||
reason: disconnectReason
|
||||
? DisconnectReason[disconnectReason]
|
||||
: 'UNKNOWN',
|
||||
})
|
||||
}
|
||||
|
||||
room.on(RoomEvent.Connected, handleConnection)
|
||||
room.on(RoomEvent.Disconnected, handleDisconnect)
|
||||
room.on(RoomEvent.Reconnecting, handleReconnect)
|
||||
|
||||
return () => {
|
||||
room.off(RoomEvent.Connected, handleConnection)
|
||||
room.off(RoomEvent.Disconnected, handleDisconnect)
|
||||
room.off(RoomEvent.Reconnecting, handleReconnect)
|
||||
}
|
||||
}, [room, isAnalyticsEnabled])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
connectionStartTimeRef.current = null
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useEffect } from 'react'
|
||||
import { Track } from 'livekit-client'
|
||||
import { useRoomContext } from '@livekit/components-react'
|
||||
import { RnnNoiseProcessor } from '../processors/RnnNoiseProcessor'
|
||||
import { usePersistentUserChoices } from './usePersistentUserChoices'
|
||||
import { useNoiseReductionAvailable } from '@/features/rooms/livekit/hooks/useNoiseReductionAvailable'
|
||||
|
||||
export const useNoiseReduction = () => {
|
||||
const room = useRoomContext()
|
||||
const noiseReductionAvailable = useNoiseReductionAvailable()
|
||||
|
||||
const {
|
||||
userChoices: { noiseReductionEnabled },
|
||||
} = usePersistentUserChoices()
|
||||
|
||||
const audioTrack = room.localParticipant.getTrackPublication(
|
||||
Track.Source.Microphone
|
||||
)?.audioTrack
|
||||
|
||||
useEffect(() => {
|
||||
if (!audioTrack || !noiseReductionAvailable) return
|
||||
|
||||
const processor = audioTrack?.getProcessor()
|
||||
|
||||
if (noiseReductionEnabled && !processor) {
|
||||
const rnnNoiseProcessor = new RnnNoiseProcessor()
|
||||
audioTrack.setProcessor(rnnNoiseProcessor)
|
||||
} else if (!noiseReductionEnabled && processor) {
|
||||
audioTrack.stopProcessor()
|
||||
}
|
||||
}, [audioTrack, noiseReductionEnabled, noiseReductionAvailable])
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { useIsMobile } from '@/utils/useIsMobile'
|
||||
import { useFeatureFlagEnabled } from 'posthog-js/react'
|
||||
import { FeatureFlags } from '@/features/analytics/enums'
|
||||
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled'
|
||||
|
||||
export const useNoiseReductionAvailable = () => {
|
||||
const featureEnabled = useFeatureFlagEnabled(FeatureFlags.faceLandmarks)
|
||||
const isAnalyticsEnabled = useIsAnalyticsEnabled()
|
||||
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
return !isMobile && (!isAnalyticsEnabled || featureEnabled)
|
||||
}
|
||||
@@ -1,34 +1,71 @@
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { userChoicesStore } from '@/stores/userChoices'
|
||||
import { ProcessorSerialized } from '@/features/rooms/livekit/components/blur'
|
||||
import { UsePersistentUserChoicesOptions } from '@livekit/components-react'
|
||||
import React from 'react'
|
||||
import { LocalUserChoices } from '../../routes/Room'
|
||||
import { saveUserChoices, loadUserChoices } from '@livekit/components-core'
|
||||
import { ProcessorSerialized } from '../components/blur'
|
||||
|
||||
export function usePersistentUserChoices() {
|
||||
const userChoicesSnap = useSnapshot(userChoicesStore)
|
||||
/**
|
||||
* From @livekit/component-react
|
||||
*
|
||||
* A hook that provides access to user choices stored in local storage, such as
|
||||
* selected media devices and their current state (on or off), as well as the user name.
|
||||
* @alpha
|
||||
*/
|
||||
export function usePersistentUserChoices(
|
||||
options: UsePersistentUserChoicesOptions = {}
|
||||
) {
|
||||
const [userChoices, setSettings] = React.useState<LocalUserChoices>(
|
||||
loadUserChoices(options.defaults, options.preventLoad ?? false)
|
||||
)
|
||||
|
||||
const saveAudioInputEnabled = React.useCallback((isEnabled: boolean) => {
|
||||
setSettings((prev: LocalUserChoices) => ({
|
||||
...prev,
|
||||
audioEnabled: isEnabled,
|
||||
}))
|
||||
}, [])
|
||||
const saveVideoInputEnabled = React.useCallback((isEnabled: boolean) => {
|
||||
setSettings((prev: LocalUserChoices) => ({
|
||||
...prev,
|
||||
videoEnabled: isEnabled,
|
||||
}))
|
||||
}, [])
|
||||
const saveAudioInputDeviceId = React.useCallback((deviceId: string) => {
|
||||
setSettings((prev: LocalUserChoices) => ({
|
||||
...prev,
|
||||
audioDeviceId: deviceId,
|
||||
}))
|
||||
}, [])
|
||||
const saveVideoInputDeviceId = React.useCallback((deviceId: string) => {
|
||||
setSettings((prev: LocalUserChoices) => ({
|
||||
...prev,
|
||||
videoDeviceId: deviceId,
|
||||
}))
|
||||
}, [])
|
||||
const saveUsername = React.useCallback((username: string) => {
|
||||
setSettings((prev: LocalUserChoices) => ({ ...prev, username: username }))
|
||||
}, [])
|
||||
const saveProcessorSerialized = React.useCallback(
|
||||
(processorSerialized?: ProcessorSerialized) => {
|
||||
setSettings((prev: LocalUserChoices) => ({
|
||||
...prev,
|
||||
processorSerialized,
|
||||
}))
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
saveUserChoices(userChoices, options.preventSave ?? false)
|
||||
}, [userChoices, options.preventSave])
|
||||
|
||||
return {
|
||||
userChoices: userChoicesSnap,
|
||||
saveAudioInputEnabled: (isEnabled: boolean) => {
|
||||
userChoicesStore.audioEnabled = isEnabled
|
||||
},
|
||||
saveVideoInputEnabled: (isEnabled: boolean) => {
|
||||
userChoicesStore.videoEnabled = isEnabled
|
||||
},
|
||||
saveAudioInputDeviceId: (deviceId: string) => {
|
||||
userChoicesStore.audioDeviceId = deviceId
|
||||
},
|
||||
saveVideoInputDeviceId: (deviceId: string) => {
|
||||
userChoicesStore.videoDeviceId = deviceId
|
||||
},
|
||||
saveUsername: (username: string) => {
|
||||
userChoicesStore.username = username
|
||||
},
|
||||
saveNoiseReductionEnabled: (enabled: boolean) => {
|
||||
userChoicesStore.noiseReductionEnabled = enabled
|
||||
},
|
||||
saveProcessorSerialized: (
|
||||
processorSerialized: ProcessorSerialized | undefined
|
||||
) => {
|
||||
userChoicesStore.processorSerialized = processorSerialized
|
||||
},
|
||||
userChoices,
|
||||
saveAudioInputEnabled,
|
||||
saveVideoInputEnabled,
|
||||
saveAudioInputDeviceId,
|
||||
saveVideoInputDeviceId,
|
||||
saveUsername,
|
||||
saveProcessorSerialized,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
formatChatMessageLinks,
|
||||
useChat,
|
||||
useParticipants,
|
||||
useRoomContext,
|
||||
} from '@livekit/components-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSnapshot } from 'valtio'
|
||||
@@ -13,7 +12,6 @@ import { Div, Text } from '@/primitives'
|
||||
import { ChatInput } from '../components/chat/Input'
|
||||
import { ChatEntry } from '../components/chat/Entry'
|
||||
import { useSidePanel } from '../hooks/useSidePanel'
|
||||
import { LocalParticipant, RemoteParticipant, RoomEvent } from 'livekit-client'
|
||||
|
||||
export interface ChatProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
@@ -29,7 +27,6 @@ export function Chat({ ...props }: ChatProps) {
|
||||
const inputRef = React.useRef<HTMLTextAreaElement>(null)
|
||||
const ulRef = React.useRef<HTMLUListElement>(null)
|
||||
|
||||
const room = useRoomContext()
|
||||
const { send, chatMessages, isSending } = useChat()
|
||||
|
||||
const { isChatOpen } = useSidePanel()
|
||||
@@ -39,7 +36,6 @@ export function Chat({ ...props }: ChatProps) {
|
||||
const participants = useParticipants()
|
||||
|
||||
const lastReadMsgAt = React.useRef<ChatMessage['timestamp']>(0)
|
||||
const previousMessageCount = React.useRef(0)
|
||||
|
||||
async function handleSubmit(text: string) {
|
||||
if (!send || !text) return
|
||||
@@ -47,19 +43,6 @@ export function Chat({ ...props }: ChatProps) {
|
||||
inputRef?.current?.focus()
|
||||
}
|
||||
|
||||
// TEMPORARY: This is a brittle workaround that relies on message count tracking
|
||||
// due to recent LiveKit useChat changes breaking the previous implementation
|
||||
// (see https://github.com/livekit/components-js/issues/1158)
|
||||
// Remove this once we refactor chat to use the new text stream approach
|
||||
React.useEffect(() => {
|
||||
if (!chatMessages || chatMessages.length <= previousMessageCount.current)
|
||||
return
|
||||
const msg = chatMessages.slice(-1)[0]
|
||||
const from = msg.from as RemoteParticipant | LocalParticipant | undefined
|
||||
room.emit(RoomEvent.ChatMessage, msg, from)
|
||||
previousMessageCount.current = chatMessages.length
|
||||
}, [chatMessages, room])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (chatMessages.length > 0 && ulRef.current) {
|
||||
ulRef.current?.scrollTo({ top: ulRef.current.scrollHeight })
|
||||
|
||||
@@ -47,13 +47,16 @@ export interface ControlBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export function ControlBar({ onDeviceError }: ControlBarProps) {
|
||||
export function ControlBar({
|
||||
saveUserChoices = true,
|
||||
onDeviceError,
|
||||
}: ControlBarProps) {
|
||||
const {
|
||||
saveAudioInputEnabled,
|
||||
saveVideoInputEnabled,
|
||||
saveAudioInputDeviceId,
|
||||
saveVideoInputDeviceId,
|
||||
} = usePersistentUserChoices()
|
||||
} = usePersistentUserChoices({ preventSave: !saveUserChoices })
|
||||
|
||||
const microphoneOnChange = React.useCallback(
|
||||
(enabled: boolean, isUserInitiated: boolean) =>
|
||||
|
||||
@@ -30,8 +30,6 @@ import { SidePanel } from '../components/SidePanel'
|
||||
import { useSidePanel } from '../hooks/useSidePanel'
|
||||
import { RecordingStateToast } from '@/features/recording'
|
||||
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
|
||||
import { useConnectionObserver } from '../hooks/useConnectionObserver'
|
||||
import { useNoiseReduction } from '../hooks/useNoiseReduction'
|
||||
|
||||
const LayoutWrapper = styled(
|
||||
'div',
|
||||
@@ -76,8 +74,6 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
||||
const lastAutoFocusedScreenShareTrack =
|
||||
React.useRef<TrackReferenceOrPlaceholder | null>(null)
|
||||
|
||||
useConnectionObserver()
|
||||
|
||||
const tracks = useTracks(
|
||||
[
|
||||
{ source: Track.Source.Camera, withPlaceholder: true },
|
||||
@@ -88,8 +84,6 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
||||
|
||||
const layoutContext = useCreateLayoutContext()
|
||||
|
||||
useNoiseReduction()
|
||||
|
||||
const screenShareTracks = tracks
|
||||
.filter(isTrackReference)
|
||||
.filter((track) => track.publication.source === Track.Source.ScreenShare)
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import { Track, TrackProcessor, ProcessorOptions } from 'livekit-client'
|
||||
import { NoiseSuppressorWorklet_Name } from '@timephy/rnnoise-wasm'
|
||||
|
||||
// This is an example how to get the script path using Vite, may be different when using other build tools
|
||||
// NOTE: `?worker&url` is important (`worker` to generate a working script, `url` to get its url to load it)
|
||||
import NoiseSuppressorWorklet from '@timephy/rnnoise-wasm/NoiseSuppressorWorklet?worker&url'
|
||||
|
||||
// Use Jitsi's approach: maintain a global AudioContext variable
|
||||
// and suspend/resume it as needed to manage audio state
|
||||
let audioContext: AudioContext
|
||||
|
||||
export interface AudioProcessorInterface
|
||||
extends TrackProcessor<Track.Kind.Audio> {
|
||||
name: string
|
||||
}
|
||||
|
||||
export class RnnNoiseProcessor implements AudioProcessorInterface {
|
||||
name: string = 'noise-reduction'
|
||||
processedTrack?: MediaStreamTrack
|
||||
|
||||
private source?: MediaStreamTrack
|
||||
private sourceNode?: MediaStreamAudioSourceNode
|
||||
private destinationNode?: MediaStreamAudioDestinationNode
|
||||
private noiseSuppressionNode?: AudioWorkletNode
|
||||
|
||||
constructor() {}
|
||||
|
||||
async init(opts: ProcessorOptions<Track.Kind.Audio>) {
|
||||
if (!opts.track) {
|
||||
throw new Error('Track is required for audio processing')
|
||||
}
|
||||
|
||||
this.source = opts.track as MediaStreamTrack
|
||||
|
||||
if (!audioContext) {
|
||||
audioContext = new AudioContext()
|
||||
} else {
|
||||
await audioContext.resume()
|
||||
}
|
||||
|
||||
await audioContext.audioWorklet.addModule(NoiseSuppressorWorklet)
|
||||
|
||||
this.sourceNode = audioContext.createMediaStreamSource(
|
||||
new MediaStream([this.source])
|
||||
)
|
||||
|
||||
this.noiseSuppressionNode = new AudioWorkletNode(
|
||||
audioContext,
|
||||
NoiseSuppressorWorklet_Name
|
||||
)
|
||||
|
||||
this.destinationNode = audioContext.createMediaStreamDestination()
|
||||
|
||||
// Connect the audio processing chain
|
||||
this.sourceNode
|
||||
.connect(this.noiseSuppressionNode)
|
||||
.connect(this.destinationNode)
|
||||
|
||||
// Get the processed track
|
||||
const tracks = this.destinationNode.stream.getAudioTracks()
|
||||
if (tracks.length === 0) {
|
||||
throw new Error('No audio tracks found for processing')
|
||||
}
|
||||
|
||||
this.processedTrack = tracks[0]
|
||||
}
|
||||
|
||||
async restart(opts: ProcessorOptions<Track.Kind.Audio>) {
|
||||
await this.destroy()
|
||||
return this.init(opts)
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
// Clean up audio nodes and context
|
||||
this.sourceNode?.disconnect()
|
||||
this.noiseSuppressionNode?.disconnect()
|
||||
this.destinationNode?.disconnect()
|
||||
|
||||
/**
|
||||
* Audio Context Lifecycle Management
|
||||
*
|
||||
* We prefer suspending the audio context rather than destroying and recreating it
|
||||
* to avoid memory leaks in WebAssembly-based audio processing.
|
||||
*
|
||||
* Issue: When an AudioContext containing WebAssembly modules is destroyed,
|
||||
* the WASM resources are not properly garbage collected. This causes:
|
||||
* - Retained JavaScript VM instances
|
||||
* - Growing memory consumption over multiple create/destroy cycles
|
||||
* - Potential performance degradation
|
||||
*
|
||||
* Solution: Use suspend() and resume() methods instead of close() to maintain
|
||||
* the same context instance while controlling audio processing state.
|
||||
*/
|
||||
await audioContext.suspend()
|
||||
|
||||
this.sourceNode = undefined
|
||||
this.destinationNode = undefined
|
||||
this.source = undefined
|
||||
this.processedTrack = undefined
|
||||
this.noiseSuppressionNode = undefined
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,23 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { usePersistentUserChoices } from '@livekit/components-react'
|
||||
import {
|
||||
usePersistentUserChoices,
|
||||
type LocalUserChoices as LocalUserChoicesLK,
|
||||
} from '@livekit/components-react'
|
||||
import { useLocation, useParams } from 'wouter'
|
||||
import { ErrorScreen } from '@/components/ErrorScreen'
|
||||
import { useUser, UserAware } from '@/features/auth'
|
||||
import { Conference } from '../components/Conference'
|
||||
import { Join } from '../components/Join'
|
||||
import { useKeyboardShortcuts } from '@/features/shortcuts/useKeyboardShortcuts'
|
||||
import { ProcessorSerialized } from '../livekit/components/blur'
|
||||
import {
|
||||
isRoomValid,
|
||||
normalizeRoomId,
|
||||
} from '@/features/rooms/utils/isRoomValid'
|
||||
import { LocalUserChoices } from '@/stores/userChoices'
|
||||
|
||||
export type LocalUserChoices = LocalUserChoicesLK & {
|
||||
processorSerialized?: ProcessorSerialized
|
||||
}
|
||||
|
||||
export const Room = () => {
|
||||
const { isLoggedIn } = useUser()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DialogProps, Field, H, Switch } from '@/primitives'
|
||||
import { DialogProps, Field, H } from '@/primitives'
|
||||
|
||||
import { TabPanel, TabPanelProps } from '@/primitives/Tabs'
|
||||
import {
|
||||
@@ -11,48 +11,17 @@ import { useTranslation } from 'react-i18next'
|
||||
import { SoundTester } from '@/components/SoundTester'
|
||||
import { HStack } from '@/styled-system/jsx'
|
||||
import { ActiveSpeaker } from '@/features/rooms/components/ActiveSpeaker'
|
||||
import { usePersistentUserChoices } from '@/features/rooms/livekit/hooks/usePersistentUserChoices'
|
||||
import { ReactNode } from 'react'
|
||||
import { css } from '@/styled-system/css'
|
||||
import posthog from 'posthog-js'
|
||||
import { useNoiseReductionAvailable } from '@/features/rooms/livekit/hooks/useNoiseReductionAvailable'
|
||||
|
||||
type RowWrapperProps = {
|
||||
heading: string
|
||||
children: ReactNode[]
|
||||
beta?: boolean
|
||||
}
|
||||
|
||||
const BetaBadge = () => (
|
||||
<span
|
||||
className={css({
|
||||
content: '"Beta"',
|
||||
display: 'block',
|
||||
letterSpacing: '-0.02rem',
|
||||
padding: '0 0.25rem',
|
||||
backgroundColor: '#E8EDFF',
|
||||
color: '#0063CB',
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
margin: '0 0 0.9375rem 0.3125rem',
|
||||
lineHeight: '1rem',
|
||||
borderRadius: '4px',
|
||||
width: 'fit-content',
|
||||
height: 'fit-content',
|
||||
marginTop: { base: '10px', sm: '5px' },
|
||||
})}
|
||||
>
|
||||
Beta
|
||||
</span>
|
||||
)
|
||||
|
||||
const RowWrapper = ({ heading, children, beta }: RowWrapperProps) => {
|
||||
const RowWrapper = ({ heading, children }: RowWrapperProps) => {
|
||||
return (
|
||||
<>
|
||||
<HStack>
|
||||
<H lvl={2}>{heading}</H>
|
||||
{beta && <BetaBadge />}
|
||||
</HStack>
|
||||
<H lvl={2}>{heading}</H>
|
||||
<HStack
|
||||
gap={0}
|
||||
style={{
|
||||
@@ -91,12 +60,6 @@ export const AudioTab = ({ id }: AudioTabProps) => {
|
||||
const { t } = useTranslation('settings')
|
||||
const { localParticipant } = useRoomContext()
|
||||
|
||||
const {
|
||||
userChoices: { noiseReductionEnabled },
|
||||
saveAudioInputDeviceId,
|
||||
saveNoiseReductionEnabled,
|
||||
} = usePersistentUserChoices()
|
||||
|
||||
const isSpeaking = useIsSpeaking(localParticipant)
|
||||
|
||||
const {
|
||||
@@ -143,8 +106,6 @@ export const AudioTab = ({ id }: AudioTabProps) => {
|
||||
return defaultItem.value
|
||||
}
|
||||
|
||||
const noiseReductionAvailable = useNoiseReductionAvailable()
|
||||
|
||||
return (
|
||||
<TabPanel padding={'md'} flex id={id}>
|
||||
<RowWrapper heading={t('audio.microphone.heading')}>
|
||||
@@ -155,10 +116,7 @@ export const AudioTab = ({ id }: AudioTabProps) => {
|
||||
defaultSelectedKey={
|
||||
activeDeviceIdIn || getDefaultSelectedKey(itemsIn)
|
||||
}
|
||||
onSelectionChange={(key) => {
|
||||
setActiveMediaDeviceIn(key as string)
|
||||
saveAudioInputDeviceId(key as string)
|
||||
}}
|
||||
onSelectionChange={(key) => setActiveMediaDeviceIn(key as string)}
|
||||
{...disabledProps}
|
||||
style={{
|
||||
width: '100%',
|
||||
@@ -168,7 +126,7 @@ export const AudioTab = ({ id }: AudioTabProps) => {
|
||||
{localParticipant.isMicrophoneEnabled ? (
|
||||
<ActiveSpeaker isSpeaking={isSpeaking} />
|
||||
) : (
|
||||
<span>{t('audio.microphone.disabled')}</span>
|
||||
<span>Micro désactivé</span>
|
||||
)}
|
||||
</>
|
||||
</RowWrapper>
|
||||
@@ -194,23 +152,6 @@ export const AudioTab = ({ id }: AudioTabProps) => {
|
||||
<SoundTester />
|
||||
</RowWrapper>
|
||||
)}
|
||||
{noiseReductionAvailable && (
|
||||
<RowWrapper heading={t('audio.noiseReduction.heading')} beta>
|
||||
<Switch
|
||||
aria-label={t(
|
||||
`audio.noiseReduction.ariaLabel.${noiseReductionEnabled ? 'disable' : 'enable'}`
|
||||
)}
|
||||
isSelected={noiseReductionEnabled}
|
||||
onChange={(v) => {
|
||||
saveNoiseReductionEnabled(v)
|
||||
if (v) posthog.capture('noise-reduction-init')
|
||||
}}
|
||||
>
|
||||
{t('audio.noiseReduction.label')}
|
||||
</Switch>
|
||||
<div />
|
||||
</RowWrapper>
|
||||
)}
|
||||
</TabPanel>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
"disclaimer": "Die Nachrichten sind nur für Teilnehmer zum Zeitpunkt des Sendens sichtbar. Alle Nachrichten werden am Ende des Anrufs gelöscht."
|
||||
},
|
||||
"moreTools": {
|
||||
"body": "Greifen Sie auf weitere Tools in Visio zu, um Ihre Meetings zu verbessern,",
|
||||
"body": "Greifen Sie auf weitere Tools in Visio zu, um Ihre Meetings zu verbessern.",
|
||||
"moreLink": "mehr erfahren",
|
||||
"tools": {
|
||||
"transcript": {
|
||||
|
||||
@@ -9,16 +9,7 @@
|
||||
"audio": {
|
||||
"microphone": {
|
||||
"heading": "Mikrofon",
|
||||
"label": "Wählen Sie Ihre Audioeingabe",
|
||||
"disabled": "Mikrofon deaktiviert"
|
||||
},
|
||||
"noiseReduction": {
|
||||
"label": "Rauschunterdrückung",
|
||||
"heading": "Rauschunterdrückung",
|
||||
"ariaLabel": {
|
||||
"enable": "Rauschunterdrückung aktivieren",
|
||||
"disable": "Rauschunterdrückung deaktivieren"
|
||||
}
|
||||
"label": "Wählen Sie Ihre Audioeingabe"
|
||||
},
|
||||
"speakers": {
|
||||
"heading": "Lautsprecher",
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
"disclaimer": "The messages are visible to participants only at the time they are sent. All messages are deleted at the end of the call."
|
||||
},
|
||||
"moreTools": {
|
||||
"body": "Access more tools in Visio to enhance your meetings,",
|
||||
"body": "Access more tools in Visio to enhance your meetings.",
|
||||
"moreLink": "learn more",
|
||||
"tools": {
|
||||
"transcript": {
|
||||
|
||||
@@ -9,16 +9,7 @@
|
||||
"audio": {
|
||||
"microphone": {
|
||||
"heading": "Microphone",
|
||||
"label": "Select your audio input",
|
||||
"disabled": "Microphone disabled"
|
||||
},
|
||||
"noiseReduction": {
|
||||
"label": "Noise reduction",
|
||||
"heading": "Noise reduction",
|
||||
"ariaLabel": {
|
||||
"enable": "Enable noise reduction",
|
||||
"disable": "Disable noise reduction"
|
||||
}
|
||||
"label": "Select your audio input"
|
||||
},
|
||||
"speakers": {
|
||||
"heading": "Speakers",
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
"disclaimer": "Les messages sont visibles par les participants uniquement au moment de\nleur envoi. Tous les messages sont supprimés à la fin de l'appel."
|
||||
},
|
||||
"moreTools": {
|
||||
"body": "Accèder à d'avantage d'outils dans Visio pour améliorer vos réunions,",
|
||||
"body": "Accèder à d'avantage d'outils dans Visio pour améliorer vos réunions.",
|
||||
"moreLink": "en savoir plus",
|
||||
"tools": {
|
||||
"transcript": {
|
||||
|
||||
@@ -9,16 +9,7 @@
|
||||
"audio": {
|
||||
"microphone": {
|
||||
"heading": "Micro",
|
||||
"label": "Sélectionner votre entrée audio",
|
||||
"disabled": "Micro désactivé"
|
||||
},
|
||||
"noiseReduction": {
|
||||
"label": "Réduction de bruit",
|
||||
"heading": "Réduction de bruit",
|
||||
"ariaLabel": {
|
||||
"enable": "Activer la réduction du bruit",
|
||||
"disable": "Désactiver la réduction du bruit"
|
||||
}
|
||||
"label": "Sélectionner votre entrée audio"
|
||||
},
|
||||
"speakers": {
|
||||
"heading": "Haut-parleurs",
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
"disclaimer": "De berichten zijn alleen voor de deelnemers zichtbaar op het moment dat ze worden verzonden. Alle berichten worden verwijderd aan het einde van het gesprek."
|
||||
},
|
||||
"moreTools": {
|
||||
"body": "Toegang tot meer tools in Visio om je vergaderingen te verbeteren,",
|
||||
"body": "Toegang tot meer tools in Visio om je vergaderingen te verbeteren.",
|
||||
"moreLink": "lees meer",
|
||||
"tools": {
|
||||
"transcript": {
|
||||
|
||||
@@ -9,16 +9,7 @@
|
||||
"audio": {
|
||||
"microphone": {
|
||||
"heading": "Microfoon",
|
||||
"label": "Selecteer uw audioinvoer",
|
||||
"disabled": "Microfoon uitgeschakeld"
|
||||
},
|
||||
"noiseReduction": {
|
||||
"label": "Ruisonderdrukking",
|
||||
"heading": "Ruisonderdrukking",
|
||||
"ariaLabel": {
|
||||
"enable": "Ruisonderdrukking inschakelen",
|
||||
"disable": "Ruisonderdrukking uitschakelen"
|
||||
}
|
||||
"label": "Selecteer uw audioinvoer"
|
||||
},
|
||||
"speakers": {
|
||||
"heading": "Luidsprekers",
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { proxy, subscribe } from 'valtio'
|
||||
import { ProcessorSerialized } from '@/features/rooms/livekit/components/blur'
|
||||
import {
|
||||
loadUserChoices,
|
||||
saveUserChoices,
|
||||
LocalUserChoices as LocalUserChoicesLK,
|
||||
} from '@livekit/components-core'
|
||||
|
||||
export type LocalUserChoices = LocalUserChoicesLK & {
|
||||
processorSerialized?: ProcessorSerialized
|
||||
noiseReductionEnabled?: boolean
|
||||
}
|
||||
|
||||
function getUserChoicesState(): LocalUserChoices {
|
||||
return {
|
||||
noiseReductionEnabled: false,
|
||||
...loadUserChoices(),
|
||||
}
|
||||
}
|
||||
|
||||
export const userChoicesStore = proxy<LocalUserChoices>(getUserChoicesState())
|
||||
|
||||
subscribe(userChoicesStore, () => {
|
||||
saveUserChoices(userChoicesStore, false)
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
export const BETA_USERS_FORM_URL =
|
||||
'https://grist.numerique.gouv.fr/o/docs/forms/3fFfvJoTBEQ6ZiMi8zsQwX/17' as const
|
||||
|
||||
export const CRISP_HELP_ARTICLE_MORE_TOOLS =
|
||||
'https://lasuite.crisp.help/fr/article/visio-tools-bvxj23' as const
|
||||
|
||||
export const CRISP_HELP_ARTICLE_TRANSCRIPT =
|
||||
'https://lasuite.crisp.help/fr/article/visio-transcript-1sjq43x' as const
|
||||
|
||||
export const CRISP_HELP_ARTICLE_RECORDING =
|
||||
'https://lasuite.crisp.help/fr/article/visio-enregistrement-wgc8o0' as const
|
||||
@@ -54,7 +54,8 @@ backend:
|
||||
LIVEKIT_API_URL: https://livekit.127.0.0.1.nip.io/
|
||||
ALLOW_UNREGISTERED_ROOMS: False
|
||||
FRONTEND_SILENCE_LIVEKIT_DEBUG: False
|
||||
FRONTEND_SUPPORT: "{'id': '58ea6697-8eba-4492-bc59-ad6562585041'}"
|
||||
FRONTEND_SUPPORT: "{'id': '58ea6697-8eba-4492-bc59-ad6562585041', 'help_article_transcript': 'https://lasuite.crisp.help/fr/article/visio-transcript-1sjq43x', 'help_article_recording': 'https://lasuite.crisp.help/fr/article/visio-enregistrement-wgc8o0', 'help_article_more_tools': 'https://lasuite.crisp.help/fr/article/visio-tools-bvxj23'}"
|
||||
FRONTEND_TRANSCRIPT: "{'form_beta_users': 'https://grist.numerique.gouv.fr/o/docs/forms/3fFfvJoTBEQ6ZiMi8zsQwX/17'}"
|
||||
FRONTEND_FEEDBACK: "{'url': 'https://grist.numerique.gouv.fr/o/docs/cbMv4G7pLY3Z/USER-RESEARCH-or-LA-SUITE/f/26'}"
|
||||
AWS_S3_ENDPOINT_URL: http://minio.meet.svc.cluster.local:9000
|
||||
AWS_S3_ACCESS_KEY_ID: meet
|
||||
@@ -68,7 +69,7 @@ backend:
|
||||
SUMMARY_SERVICE_API_TOKEN: password
|
||||
SCREEN_RECORDING_BASE_URL: https://meet.127.0.0.1.nip.io/recordings
|
||||
ROOM_TELEPHONY_ENABLED: True
|
||||
SSL_CERT_FILE: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
SSL_CERT_FILE: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem
|
||||
|
||||
|
||||
migrate:
|
||||
@@ -76,13 +77,6 @@ backend:
|
||||
- "/bin/sh"
|
||||
- "-c"
|
||||
- |
|
||||
while ! python manage.py check --database default > /dev/null 2>&1
|
||||
do
|
||||
echo "Database not ready"
|
||||
sleep 2
|
||||
done
|
||||
echo "Database is ready"
|
||||
|
||||
python manage.py migrate --no-input &&
|
||||
python manage.py create_demo --force
|
||||
restartPolicy: Never
|
||||
@@ -99,20 +93,13 @@ backend:
|
||||
- "/bin/sh"
|
||||
- "-c"
|
||||
- |
|
||||
while ! python manage.py check --database default > /dev/null 2>&1
|
||||
do
|
||||
echo "Database not ready"
|
||||
sleep 2
|
||||
done
|
||||
echo "Database is ready"
|
||||
|
||||
python manage.py createsuperuser --email admin@example.com --password admin
|
||||
restartPolicy: Never
|
||||
|
||||
# Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false
|
||||
extraVolumeMounts:
|
||||
- name: certs
|
||||
mountPath: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
mountPath: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem
|
||||
subPath: cacert.pem
|
||||
|
||||
# Extra volumes to manage our local custom CA and avoid to set ssl_verify: false
|
||||
|
||||
@@ -95,7 +95,7 @@ backend:
|
||||
key: BREVO_API_KEY
|
||||
BREVO_API_CONTACT_LIST_IDS: 8
|
||||
ROOM_TELEPHONY_ENABLED: True
|
||||
SSL_CERT_FILE: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
SSL_CERT_FILE: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem
|
||||
|
||||
|
||||
migrate:
|
||||
@@ -103,13 +103,6 @@ backend:
|
||||
- "/bin/sh"
|
||||
- "-c"
|
||||
- |
|
||||
while ! python manage.py check --database default > /dev/null 2>&1
|
||||
do
|
||||
echo "Database not ready"
|
||||
sleep 2
|
||||
done
|
||||
echo "Database is ready"
|
||||
|
||||
python manage.py migrate --no-input &&
|
||||
python manage.py create_demo --force
|
||||
restartPolicy: Never
|
||||
@@ -126,20 +119,13 @@ backend:
|
||||
- "/bin/sh"
|
||||
- "-c"
|
||||
- |
|
||||
while ! python manage.py check --database default > /dev/null 2>&1
|
||||
do
|
||||
echo "Database not ready"
|
||||
sleep 2
|
||||
done
|
||||
echo "Database is ready"
|
||||
|
||||
python manage.py createsuperuser --email admin@example.com --password admin
|
||||
restartPolicy: Never
|
||||
|
||||
# Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false
|
||||
extraVolumeMounts:
|
||||
- name: certs
|
||||
mountPath: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
mountPath: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem
|
||||
subPath: cacert.pem
|
||||
|
||||
# Extra volumes to manage our local custom CA and avoid to set ssl_verify: false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v2
|
||||
type: application
|
||||
name: meet
|
||||
version: 0.0.9
|
||||
version: 0.0.8
|
||||
|
||||
+92
-95
@@ -4,104 +4,101 @@
|
||||
|
||||
### General configuration
|
||||
|
||||
| Name | Description | Value |
|
||||
| ---------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| `image.repository` | Repository to use to pull meet's container image | `lasuite/meet-backend` |
|
||||
| `image.tag` | meet's container tag | `latest` |
|
||||
| `image.pullPolicy` | Container image pull policy | `IfNotPresent` |
|
||||
| `image.credentials.username` | Username for container registry authentication | |
|
||||
| `image.credentials.password` | Password for container registry authentication | |
|
||||
| `image.credentials.registry` | Registry url for which the credentials are specified | |
|
||||
| `image.credentials.name` | Name of the generated secret for imagePullSecrets | |
|
||||
| `nameOverride` | Override the chart name | `""` |
|
||||
| `fullnameOverride` | Override the full application name | `""` |
|
||||
| `ingress.enabled` | whether to enable the Ingress or not | `false` |
|
||||
| `ingress.className` | IngressClass to use for the Ingress | `nil` |
|
||||
| `ingress.host` | Host for the Ingress | `meet.example.com` |
|
||||
| `ingress.path` | Path to use for the Ingress | `/` |
|
||||
| `ingress.hosts` | Additional host to configure for the Ingress | `[]` |
|
||||
| `ingress.tls.enabled` | Weather to enable TLS for the Ingress | `true` |
|
||||
| `ingress.tls.additional[].secretName` | Secret name for additional TLS config | |
|
||||
| `ingress.tls.additional[].hosts[]` | Hosts for additional TLS config | |
|
||||
| `ingress.customBackends` | Add custom backends to ingress | `[]` |
|
||||
| `ingressAdmin.enabled` | whether to enable the Ingress or not | `false` |
|
||||
| `ingressAdmin.className` | IngressClass to use for the Ingress | `nil` |
|
||||
| `ingressAdmin.host` | Host for the Ingress | `meet.example.com` |
|
||||
| `ingressAdmin.path` | Path to use for the Ingress | `/admin` |
|
||||
| `ingressAdmin.hosts` | Additional host to configure for the Ingress | `[]` |
|
||||
| `ingressAdmin.tls.enabled` | Weather to enable TLS for the Ingress | `true` |
|
||||
| `ingressAdmin.tls.additional[].secretName` | Secret name for additional TLS config | |
|
||||
| `ingressAdmin.tls.additional[].hosts[]` | Hosts for additional TLS config | |
|
||||
| `ingressMedia.enabled` | whether to enable the Ingress or not | `false` |
|
||||
| `ingressMedia.className` | IngressClass to use for the Ingress | `nil` |
|
||||
| `ingressMedia.host` | Host for the Ingress | `meet.example.com` |
|
||||
| `ingressMedia.path` | Path to use for the Ingress | `/media/(.*)` |
|
||||
| `ingressMedia.hosts` | Additional host to configure for the Ingress | `[]` |
|
||||
| `ingressMedia.tls.enabled` | Weather to enable TLS for the Ingress | `true` |
|
||||
| `ingressMedia.tls.secretName` | Secret name for TLS config | `nil` |
|
||||
| `ingressMedia.tls.additional[].secretName` | Secret name for additional TLS config | |
|
||||
| `ingressMedia.tls.additional[].hosts[]` | Hosts for additional TLS config | |
|
||||
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-url` | | `https://meet.example.com/api/v1.0/recordings/media-auth/` |
|
||||
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-response-headers` | | `Authorization, X-Amz-Date, X-Amz-Content-SHA256` |
|
||||
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/upstream-vhost` | | `minio.meet.svc.cluster.local:9000` |
|
||||
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/configuration-snippet` | | `add_header Content-Security-Policy "default-src 'none'" always;
|
||||
` |
|
||||
| `serviceMedia.host` | | `minio.meet.svc.cluster.local` |
|
||||
| `serviceMedia.port` | | `9000` |
|
||||
| `serviceMedia.annotations` | | `{}` |
|
||||
| Name | Description | Value |
|
||||
| ---------------------------------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| `image.repository` | Repository to use to pull meet's container image | `lasuite/meet-backend` |
|
||||
| `image.tag` | meet's container tag | `latest` |
|
||||
| `image.pullPolicy` | Container image pull policy | `IfNotPresent` |
|
||||
| `image.credentials.username` | Username for container registry authentication | |
|
||||
| `image.credentials.password` | Password for container registry authentication | |
|
||||
| `image.credentials.registry` | Registry url for which the credentials are specified | |
|
||||
| `image.credentials.name` | Name of the generated secret for imagePullSecrets | |
|
||||
| `nameOverride` | Override the chart name | `""` |
|
||||
| `fullnameOverride` | Override the full application name | `""` |
|
||||
| `ingress.enabled` | whether to enable the Ingress or not | `false` |
|
||||
| `ingress.className` | IngressClass to use for the Ingress | `nil` |
|
||||
| `ingress.host` | Host for the Ingress | `meet.example.com` |
|
||||
| `ingress.path` | Path to use for the Ingress | `/` |
|
||||
| `ingress.hosts` | Additional host to configure for the Ingress | `[]` |
|
||||
| `ingress.tls.enabled` | Weather to enable TLS for the Ingress | `true` |
|
||||
| `ingress.tls.additional[].secretName` | Secret name for additional TLS config | |
|
||||
| `ingress.tls.additional[].hosts[]` | Hosts for additional TLS config | |
|
||||
| `ingress.customBackends` | Add custom backends to ingress | `[]` |
|
||||
| `ingressAdmin.enabled` | whether to enable the Ingress or not | `false` |
|
||||
| `ingressAdmin.className` | IngressClass to use for the Ingress | `nil` |
|
||||
| `ingressAdmin.host` | Host for the Ingress | `meet.example.com` |
|
||||
| `ingressAdmin.path` | Path to use for the Ingress | `/admin` |
|
||||
| `ingressAdmin.hosts` | Additional host to configure for the Ingress | `[]` |
|
||||
| `ingressAdmin.tls.enabled` | Weather to enable TLS for the Ingress | `true` |
|
||||
| `ingressAdmin.tls.additional[].secretName` | Secret name for additional TLS config | |
|
||||
| `ingressAdmin.tls.additional[].hosts[]` | Hosts for additional TLS config | |
|
||||
| `ingressMedia.enabled` | whether to enable the Ingress or not | `false` |
|
||||
| `ingressMedia.className` | IngressClass to use for the Ingress | `nil` |
|
||||
| `ingressMedia.host` | Host for the Ingress | `meet.example.com` |
|
||||
| `ingressMedia.path` | Path to use for the Ingress | `/media/(.*)` |
|
||||
| `ingressMedia.hosts` | Additional host to configure for the Ingress | `[]` |
|
||||
| `ingressMedia.tls.enabled` | Weather to enable TLS for the Ingress | `true` |
|
||||
| `ingressMedia.tls.secretName` | Secret name for TLS config | `nil` |
|
||||
| `ingressMedia.tls.additional[].secretName` | Secret name for additional TLS config | |
|
||||
| `ingressMedia.tls.additional[].hosts[]` | Hosts for additional TLS config | |
|
||||
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-url` | | `https://meet.example.com/api/v1.0/recordings/media-auth/` |
|
||||
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/auth-response-headers` | | `Authorization, X-Amz-Date, X-Amz-Content-SHA256` |
|
||||
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/upstream-vhost` | | `minio.meet.svc.cluster.local:9000` |
|
||||
| `ingressMedia.annotations.nginx.ingress.kubernetes.io/configuration-snippet` | | `add_header Content-Security-Policy "default-src 'none'" always;` |
|
||||
| `serviceMedia.host` | | `minio.meet.svc.cluster.local` |
|
||||
| `serviceMedia.port` | | `9000` |
|
||||
| `serviceMedia.annotations` | | `{}` |
|
||||
|
||||
### backend
|
||||
|
||||
| Name | Description | Value |
|
||||
| ----------------------------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `backend.dpAnnotations` | Annotations to add to the backend Deployment | `{}` |
|
||||
| `backend.command` | Override the backend container command | `[]` |
|
||||
| `backend.args` | Override the backend container args | `[]` |
|
||||
| `backend.replicas` | Amount of backend replicas | `3` |
|
||||
| `backend.shareProcessNamespace` | Enable share process namespace between containers | `false` |
|
||||
| `backend.sidecars` | Add sidecars containers to backend deployment | `[]` |
|
||||
| `backend.migrateJobAnnotations` | Annotations for the migrate job | `{}` |
|
||||
| `backend.jobs.ttlSecondsAfterFinished` | Period to wait before remove jobs | `30` |
|
||||
| `backend.jobs.backoffLimit` | Numbers of jobs retries | `2` |
|
||||
| `backend.securityContext` | Configure backend Pod security context | `nil` |
|
||||
| `backend.envVars` | Configure backend container environment variables | `undefined` |
|
||||
| `backend.envVars.BY_VALUE` | Example environment variable by setting value directly | |
|
||||
| `backend.envVars.FROM_CONFIGMAP.configMapKeyRef.name` | Name of a ConfigMap when configuring env vars from a ConfigMap | |
|
||||
| `backend.envVars.FROM_CONFIGMAP.configMapKeyRef.key` | Key within a ConfigMap when configuring env vars from a ConfigMap | |
|
||||
| `backend.envVars.FROM_SECRET.secretKeyRef.name` | Name of a Secret when configuring env vars from a Secret | |
|
||||
| `backend.envVars.FROM_SECRET.secretKeyRef.key` | Key within a Secret when configuring env vars from a Secret | |
|
||||
| `backend.podAnnotations` | Annotations to add to the backend Pod | `{}` |
|
||||
| `backend.service.type` | backend Service type | `ClusterIP` |
|
||||
| `backend.service.port` | backend Service listening port | `80` |
|
||||
| `backend.service.targetPort` | backend container listening port | `8000` |
|
||||
| `backend.service.annotations` | Annotations to add to the backend Service | `{}` |
|
||||
| `backend.migrate.command` | backend migrate command | `["/bin/sh","-c","while ! python manage.py check --database default > /dev/null 2>&1\ndo\n echo \"Database not ready\"\n sleep 2\ndone\necho \"Database is ready\"\n\npython manage.py migrate --no-input\n"]` |
|
||||
| `backend.migrate.restartPolicy` | backend migrate job restart policy | `Never` |
|
||||
| `backend.createsuperuser.command` | backend migrate command | `["/bin/sh","-c","while ! python manage.py check --database default > /dev/null 2>&1\ndo\n echo \"Database not ready\"\n sleep 2\ndone\necho \"Database is ready\"\n\npython manage.py createsuperuser --email $DJANGO_SUPERUSER_EMAIL --password $DJANGO_SUPERUSER_PASSWORD\n"]` |
|
||||
| `backend.createsuperuser.restartPolicy` | backend migrate job restart policy | `Never` |
|
||||
| `backend.probes.liveness.path` | Configure path for backend HTTP liveness probe | `/__heartbeat__` |
|
||||
| `backend.probes.liveness.targetPort` | Configure port for backend HTTP liveness probe | `undefined` |
|
||||
| `backend.probes.liveness.initialDelaySeconds` | Configure initial delay for backend liveness probe | `30` |
|
||||
| `backend.probes.liveness.initialDelaySeconds` | Configure timeout for backend liveness probe | `30` |
|
||||
| `backend.probes.startup.path` | Configure path for backend HTTP startup probe | `undefined` |
|
||||
| `backend.probes.startup.targetPort` | Configure port for backend HTTP startup probe | `undefined` |
|
||||
| `backend.probes.startup.initialDelaySeconds` | Configure initial delay for backend startup probe | `undefined` |
|
||||
| `backend.probes.startup.initialDelaySeconds` | Configure timeout for backend startup probe | `undefined` |
|
||||
| `backend.probes.readiness.path` | Configure path for backend HTTP readiness probe | `/__lbheartbeat__` |
|
||||
| `backend.probes.readiness.targetPort` | Configure port for backend HTTP readiness probe | `undefined` |
|
||||
| `backend.probes.readiness.initialDelaySeconds` | Configure initial delay for backend readiness probe | `30` |
|
||||
| `backend.probes.readiness.initialDelaySeconds` | Configure timeout for backend readiness probe | `30` |
|
||||
| `backend.resources` | Resource requirements for the backend container | `{}` |
|
||||
| `backend.nodeSelector` | Node selector for the backend Pod | `{}` |
|
||||
| `backend.tolerations` | Tolerations for the backend Pod | `[]` |
|
||||
| `backend.affinity` | Affinity for the backend Pod | `{}` |
|
||||
| `backend.persistence` | Additional volumes to create and mount on the backend. Used for debugging purposes | `{}` |
|
||||
| `backend.persistence.volume-name.size` | Size of the additional volume | |
|
||||
| `backend.persistence.volume-name.type` | Type of the additional volume, persistentVolumeClaim or emptyDir | |
|
||||
| `backend.persistence.volume-name.mountPath` | Path where the volume should be mounted to | |
|
||||
| `backend.extraVolumeMounts` | Additional volumes to mount on the backend. | `[]` |
|
||||
| `backend.extraVolumes` | Additional volumes to mount on the backend. | `[]` |
|
||||
| `backend.pdb.enabled` | Enable pdb on backend | `true` |
|
||||
| Name | Description | Value |
|
||||
| ----------------------------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `backend.dpAnnotations` | Annotations to add to the backend Deployment | `{}` |
|
||||
| `backend.command` | Override the backend container command | `[]` |
|
||||
| `backend.args` | Override the backend container args | `[]` |
|
||||
| `backend.replicas` | Amount of backend replicas | `3` |
|
||||
| `backend.shareProcessNamespace` | Enable share process namespace between containers | `false` |
|
||||
| `backend.sidecars` | Add sidecars containers to backend deployment | `[]` |
|
||||
| `backend.migrateJobAnnotations` | Annotations for the migrate job | `{}` |
|
||||
| `backend.securityContext` | Configure backend Pod security context | `nil` |
|
||||
| `backend.envVars` | Configure backend container environment variables | `undefined` |
|
||||
| `backend.envVars.BY_VALUE` | Example environment variable by setting value directly | |
|
||||
| `backend.envVars.FROM_CONFIGMAP.configMapKeyRef.name` | Name of a ConfigMap when configuring env vars from a ConfigMap | |
|
||||
| `backend.envVars.FROM_CONFIGMAP.configMapKeyRef.key` | Key within a ConfigMap when configuring env vars from a ConfigMap | |
|
||||
| `backend.envVars.FROM_SECRET.secretKeyRef.name` | Name of a Secret when configuring env vars from a Secret | |
|
||||
| `backend.envVars.FROM_SECRET.secretKeyRef.key` | Key within a Secret when configuring env vars from a Secret | |
|
||||
| `backend.podAnnotations` | Annotations to add to the backend Pod | `{}` |
|
||||
| `backend.service.type` | backend Service type | `ClusterIP` |
|
||||
| `backend.service.port` | backend Service listening port | `80` |
|
||||
| `backend.service.targetPort` | backend container listening port | `8000` |
|
||||
| `backend.service.annotations` | Annotations to add to the backend Service | `{}` |
|
||||
| `backend.migrate.command` | backend migrate command | `["python","manage.py","migrate","--no-input"]` |
|
||||
| `backend.migrate.restartPolicy` | backend migrate job restart policy | `Never` |
|
||||
| `backend.createsuperuser.command` | backend migrate command | `["/bin/sh","-c","python manage.py createsuperuser --email $DJANGO_SUPERUSER_EMAIL --password $DJANGO_SUPERUSER_PASSWORD\n"]` |
|
||||
| `backend.createsuperuser.restartPolicy` | backend migrate job restart policy | `Never` |
|
||||
| `backend.probes.liveness.path` | Configure path for backend HTTP liveness probe | `/__heartbeat__` |
|
||||
| `backend.probes.liveness.targetPort` | Configure port for backend HTTP liveness probe | `undefined` |
|
||||
| `backend.probes.liveness.initialDelaySeconds` | Configure initial delay for backend liveness probe | `30` |
|
||||
| `backend.probes.liveness.initialDelaySeconds` | Configure timeout for backend liveness probe | `30` |
|
||||
| `backend.probes.startup.path` | Configure path for backend HTTP startup probe | `undefined` |
|
||||
| `backend.probes.startup.targetPort` | Configure port for backend HTTP startup probe | `undefined` |
|
||||
| `backend.probes.startup.initialDelaySeconds` | Configure initial delay for backend startup probe | `undefined` |
|
||||
| `backend.probes.startup.initialDelaySeconds` | Configure timeout for backend startup probe | `undefined` |
|
||||
| `backend.probes.readiness.path` | Configure path for backend HTTP readiness probe | `/__lbheartbeat__` |
|
||||
| `backend.probes.readiness.targetPort` | Configure port for backend HTTP readiness probe | `undefined` |
|
||||
| `backend.probes.readiness.initialDelaySeconds` | Configure initial delay for backend readiness probe | `30` |
|
||||
| `backend.probes.readiness.initialDelaySeconds` | Configure timeout for backend readiness probe | `30` |
|
||||
| `backend.resources` | Resource requirements for the backend container | `{}` |
|
||||
| `backend.nodeSelector` | Node selector for the backend Pod | `{}` |
|
||||
| `backend.tolerations` | Tolerations for the backend Pod | `[]` |
|
||||
| `backend.affinity` | Affinity for the backend Pod | `{}` |
|
||||
| `backend.persistence` | Additional volumes to create and mount on the backend. Used for debugging purposes | `{}` |
|
||||
| `backend.persistence.volume-name.size` | Size of the additional volume | |
|
||||
| `backend.persistence.volume-name.type` | Type of the additional volume, persistentVolumeClaim or emptyDir | |
|
||||
| `backend.persistence.volume-name.mountPath` | Path where the volume should be mounted to | |
|
||||
| `backend.extraVolumeMounts` | Additional volumes to mount on the backend. | `[]` |
|
||||
| `backend.extraVolumes` | Additional volumes to mount on the backend. | `[]` |
|
||||
| `backend.pdb.enabled` | Enable pdb on backend | `true` |
|
||||
|
||||
### frontend
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ metadata:
|
||||
labels:
|
||||
{{- include "meet.common.labels" (list . $component) | nindent 4 }}
|
||||
spec:
|
||||
ttlSecondsAfterFinished: {{ .Values.backend.jobs.ttlSecondsAfterFinished }}
|
||||
backoffLimit: {{ .Values.backend.jobs.backoffLimit }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
|
||||
@@ -14,8 +14,6 @@ metadata:
|
||||
labels:
|
||||
{{- include "meet.common.labels" (list . $component) | nindent 4 }}
|
||||
spec:
|
||||
ttlSecondsAfterFinished: {{ .Values.backend.jobs.ttlSecondsAfterFinished }}
|
||||
backoffLimit: {{ .Values.backend.jobs.backoffLimit }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
|
||||
@@ -136,12 +136,6 @@ backend:
|
||||
## @param backend.migrateJobAnnotations Annotations for the migrate job
|
||||
migrateJobAnnotations: {}
|
||||
|
||||
## @param backend.jobs.ttlSecondsAfterFinished Period to wait before remove jobs
|
||||
## @param backend.jobs.backoffLimit Numbers of jobs retries
|
||||
jobs:
|
||||
ttlSecondsAfterFinished: 30
|
||||
backoffLimit: 2
|
||||
|
||||
## @param backend.securityContext Configure backend Pod security context
|
||||
securityContext: null
|
||||
|
||||
@@ -172,17 +166,10 @@ backend:
|
||||
## @param backend.migrate.restartPolicy backend migrate job restart policy
|
||||
migrate:
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-c"
|
||||
- |
|
||||
while ! python manage.py check --database default > /dev/null 2>&1
|
||||
do
|
||||
echo "Database not ready"
|
||||
sleep 2
|
||||
done
|
||||
echo "Database is ready"
|
||||
|
||||
python manage.py migrate --no-input
|
||||
- "python"
|
||||
- "manage.py"
|
||||
- "migrate"
|
||||
- "--no-input"
|
||||
restartPolicy: Never
|
||||
|
||||
## @param backend.createsuperuser.command backend migrate command
|
||||
@@ -192,13 +179,6 @@ backend:
|
||||
- "/bin/sh"
|
||||
- "-c"
|
||||
- |
|
||||
while ! python manage.py check --database default > /dev/null 2>&1
|
||||
do
|
||||
echo "Database not ready"
|
||||
sleep 2
|
||||
done
|
||||
echo "Database is ready"
|
||||
|
||||
python manage.py createsuperuser --email $DJANGO_SUPERUSER_EMAIL --password $DJANGO_SUPERUSER_PASSWORD
|
||||
restartPolicy: Never
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.26",
|
||||
"version": "0.1.23",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.26",
|
||||
"version": "0.1.23",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@html-to/text-cli": "0.5.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.26",
|
||||
"version": "0.1.23",
|
||||
"description": "An util to generate html and text django's templates from mjml templates",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sdk",
|
||||
"version": "0.1.26",
|
||||
"version": "0.1.23",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sdk",
|
||||
"version": "0.1.26",
|
||||
"version": "0.1.23",
|
||||
"license": "ISC",
|
||||
"workspaces": [
|
||||
"./library",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sdk",
|
||||
"version": "0.1.26",
|
||||
"version": "0.1.23",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
FROM python:3.13-slim AS base
|
||||
|
||||
FROM base AS builder
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -8,7 +6,7 @@ COPY pyproject.toml .
|
||||
|
||||
RUN pip3 install --no-cache-dir .
|
||||
|
||||
FROM base AS production
|
||||
FROM python:3.12-slim AS production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
|
||||
[project]
|
||||
name = "summary"
|
||||
version = "0.1.26"
|
||||
version = "0.1.23"
|
||||
dependencies = [
|
||||
"fastapi[standard]>=0.105.0",
|
||||
"uvicorn>=0.24.0",
|
||||
"pydantic>=2.5.0",
|
||||
"pydantic-settings>=2.1.0",
|
||||
"celery==5.5.3",
|
||||
"celery==5.4.0",
|
||||
"redis==5.2.1",
|
||||
"minio==7.2.15",
|
||||
"openai==1.91.0",
|
||||
"requests==2.32.4",
|
||||
"sentry-sdk[fastapi, celery]==2.30.0",
|
||||
"openai==1.68.2",
|
||||
"requests==2.32.3",
|
||||
"sentry-sdk[fastapi, celery]==2.24.1",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"ruff==0.12.0",
|
||||
"ruff==0.11.2",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
|
||||
Reference in New Issue
Block a user