Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dfd1d2c827 | |||
| 9b9a2f883c |
+1
-1
@@ -4,7 +4,7 @@ __pycache__
|
||||
**/__pycache__
|
||||
**/*.pyc
|
||||
venv
|
||||
**/.venv
|
||||
.venv
|
||||
|
||||
# System-specific files
|
||||
.DS_Store
|
||||
|
||||
@@ -12,9 +12,6 @@ on:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
DOCKER_USER: 1001:127
|
||||
DOCKER_CONTAINER_REGISTRY_HOSTNAME: docker.io
|
||||
@@ -23,8 +20,6 @@ env:
|
||||
jobs:
|
||||
build-and-push-backend:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
@@ -48,12 +43,12 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
with:
|
||||
docker-build-args: '--target backend-production -f Dockerfile'
|
||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-backend:${{ github.sha }}'
|
||||
# -
|
||||
# name: Run trivy scan
|
||||
# uses: numerique-gouv/action-trivy-cache@main
|
||||
# with:
|
||||
# docker-build-args: '--target backend-production -f Dockerfile'
|
||||
# docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-backend:${{ github.sha }}'
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -68,8 +63,6 @@ jobs:
|
||||
|
||||
build-and-push-frontend-generic:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
@@ -93,12 +86,12 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
with:
|
||||
docker-build-args: '-f src/frontend/Dockerfile --target frontend-production'
|
||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend:${{ github.sha }}'
|
||||
# -
|
||||
# name: Run trivy scan
|
||||
# uses: numerique-gouv/action-trivy-cache@main
|
||||
# with:
|
||||
# docker-build-args: '-f src/frontend/Dockerfile --target frontend-production'
|
||||
# docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend:${{ github.sha }}'
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -114,8 +107,6 @@ jobs:
|
||||
|
||||
build-and-push-frontend-dinum:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
@@ -139,12 +130,12 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
with:
|
||||
docker-build-args: '-f docker/dinum-frontend/Dockerfile --target frontend-production'
|
||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend-dinum:${{ github.sha }}'
|
||||
# -
|
||||
# name: Run trivy scan
|
||||
# uses: numerique-gouv/action-trivy-cache@main
|
||||
# with:
|
||||
# docker-build-args: '-f docker/dinum-frontend/Dockerfile --target frontend-production'
|
||||
# docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend-dinum:${{ github.sha }}'
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -160,8 +151,6 @@ jobs:
|
||||
|
||||
build-and-push-summary:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
@@ -185,13 +174,13 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
continue-on-error: true
|
||||
with:
|
||||
docker-build-args: '-f src/summary/Dockerfile --target production'
|
||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-summary:${{ github.sha }}'
|
||||
# -
|
||||
# name: Run trivy scan
|
||||
# uses: numerique-gouv/action-trivy-cache@main
|
||||
# continue-on-error: true
|
||||
# with:
|
||||
# docker-build-args: '-f src/summary/Dockerfile --target production'
|
||||
# docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-summary:${{ github.sha }}'
|
||||
docker-context: './src/summary'
|
||||
-
|
||||
name: Build and push
|
||||
@@ -208,8 +197,6 @@ jobs:
|
||||
|
||||
build-and-push-agents:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
@@ -233,14 +220,14 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
continue-on-error: true
|
||||
with:
|
||||
docker-build-args: '-f src/agents/Dockerfile --target production'
|
||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-agents:${{ github.sha }}'
|
||||
docker-context: './src/agents'
|
||||
# -
|
||||
# name: Run trivy scan
|
||||
# uses: numerique-gouv/action-trivy-cache@main
|
||||
# continue-on-error: true
|
||||
# with:
|
||||
# docker-build-args: '-f src/agents/Dockerfile --target production'
|
||||
# docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-agents:${{ github.sha }}'
|
||||
# docker-context: './src/agents'
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -255,8 +242,6 @@ jobs:
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
notify-argocd:
|
||||
permissions:
|
||||
contents: read
|
||||
needs:
|
||||
- build-and-push-frontend-generic
|
||||
- build-and-push-frontend-dinum
|
||||
|
||||
+12
-54
@@ -124,17 +124,15 @@ jobs:
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
- name: Install the project
|
||||
run: uv sync --locked --all-extras
|
||||
|
||||
cache: "pip"
|
||||
- name: Install development dependencies
|
||||
run: pip install --user .[dev]
|
||||
- name: Check code formatting with ruff
|
||||
run: uv run ruff format . --diff
|
||||
run: ~/.local/bin/ruff format . --diff
|
||||
- name: Lint code with ruff
|
||||
run: uv run ruff check .
|
||||
run: ~/.local/bin/ruff check .
|
||||
- name: Lint code with pylint
|
||||
run: uv run pylint meet demo core
|
||||
run: ~/.local/bin/pylint meet demo core
|
||||
|
||||
lint-agents:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -281,10 +279,10 @@ jobs:
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
- name: Install the dependencies
|
||||
run: uv sync --locked --all-extras
|
||||
cache: "pip"
|
||||
|
||||
- name: Install development dependencies
|
||||
run: pip install --user .[dev]
|
||||
|
||||
- name: Install gettext (required to compile messages)
|
||||
run: |
|
||||
@@ -292,50 +290,10 @@ jobs:
|
||||
sudo apt-get install -y gettext
|
||||
|
||||
- name: Generate a MO file from strings extracted from the project
|
||||
run: uv run python manage.py compilemessages
|
||||
run: python manage.py compilemessages
|
||||
|
||||
- name: Run tests
|
||||
run: uv run pytest -n 2
|
||||
|
||||
test-summary:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
defaults:
|
||||
run:
|
||||
working-directory: src/summary
|
||||
|
||||
env:
|
||||
APP_API_TOKEN: "test-api-token"
|
||||
AWS_STORAGE_BUCKET_NAME: "http://meet-media-storage"
|
||||
AWS_S3_ENDPOINT_URL: "minio:9000"
|
||||
AWS_S3_ACCESS_KEY_ID: "meet"
|
||||
AWS_S3_SECRET_ACCESS_KEY: "password"
|
||||
WHISPERX_BASE_URL: "https://configure-your-url.com"
|
||||
WHISPERX_ASR_MODEL: "large-v2"
|
||||
WHISPERX_API_KEY: "test-whisperx-secret"
|
||||
WHISPERX_DEFAULT_LANGUAGE: "fr"
|
||||
LLM_BASE_URL: "https://configure-your-url.com"
|
||||
LLM_API_KEY: "test-llm-secret"
|
||||
LLM_MODEL: "test-llm-model"
|
||||
WEBHOOK_API_TOKEN: "test-webhook-secret"
|
||||
WEBHOOK_URL: "https://configure-your-url.com"
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.13"
|
||||
cache: "pip"
|
||||
|
||||
- name: Install development dependencies
|
||||
run: pip install --user .[dev]
|
||||
|
||||
- name: Run summary tests
|
||||
run: ~/.local/bin/pytest
|
||||
run: ~/.local/bin/pytest -n 2
|
||||
|
||||
lint-front:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -31,7 +31,6 @@ MANIFEST
|
||||
|
||||
# Translations # Translations
|
||||
*.pot
|
||||
*.mo
|
||||
|
||||
# Environments
|
||||
.env
|
||||
|
||||
+2
-40
@@ -8,64 +8,26 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- ⚡️(helm) Add pod and container securityContext #4805
|
||||
- ♻️(backend) configurable SESSION_ENGINE #1038 #1154
|
||||
- ♿️(frontend) fix sidepanel accessibility aria-label #1182
|
||||
- ♿️(frontend) fix more tools heading hierarchy #1181
|
||||
- ♿️(fronted) improve button descriptions for More tools actions #1184
|
||||
- 💄(spinner) enforce spinner height #1183
|
||||
- 💄(custom-background) add upload indicator with preview #1183
|
||||
- ♿️(backend) improve logo accessibility in recording email notification #1092
|
||||
- ♿️(summary) improve accessibility of transcription download link #1187
|
||||
- 💄(frontend) show OS-specific shortcut in participant tile hint #1193
|
||||
- ⬆️(frontend) bump flatted from 3.3.1 to 3.4.2 in /src/frontend #1188
|
||||
- ⬆️️️(frontend) bump undici from 6.23.0 to 6.24.1 in /src/frontend
|
||||
- ⬆️️️(frontend) bump hono from 4.12.2 to 4.12.7 in /src/frontend
|
||||
- ⬆️️️(frontend) bump dompurify from 3.3.1 to 3.3.2 in /src/frontend
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(frontend) disable personal custom background while deleting #1183
|
||||
- 🐛(frontend) auto-select new custom background when not logged in #1183
|
||||
- 🐛(frontend) fix device selection not applying during conference #1156
|
||||
|
||||
## [1.11.0] - 2026-03-19
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(helm) support celery with our Django backend #1124
|
||||
- ✨(helm) support ingress for custom background image #1124
|
||||
- ✨(backend) add authenticated user rate throttling on request-entry #1129
|
||||
- ✨(backend) expose `is_active` field for Application in Django admin #1133
|
||||
- ✨(file-upload) disable by default & limit count by user #1141
|
||||
- ✨(frontend) custom background #1067
|
||||
|
||||
### Changed
|
||||
|
||||
- ♿️(frontend) Caption text size setting for accessibility #1062
|
||||
- ♿️(frontend) sync html lang attribute with i18n for screen readers #1111
|
||||
- ♿️(frontend) improve MoreLink a11y and UX on home page #1112
|
||||
- ♿️(frontend) improve chat toast a11y for screen readers #1109
|
||||
- ♿️(frontend) improve ui and aria labels for help article links #1108
|
||||
- ♿(frontend) improve chat toast a11y for screen readers #1109
|
||||
- ♿(frontend) improve ui and aria labels for help article links #1108
|
||||
- 🌐(frontend) improve German translation #1125
|
||||
- 🔨(python-env) migrate meet main app to UV #1120
|
||||
- ♻️(backend) align Application model field with `is_active` convention #1133
|
||||
- 🔐(backend) avoids revealing the inactive status of an application #1135
|
||||
- ⚡️(helm) reduce initialDelaySeconds and add periods seconds #1139
|
||||
- 🔒️(backend) avoid information exposure through exception messages #1144
|
||||
- ⬆️(dependencies) update PyJWT to v2.12.0 [SECURITY] #1151
|
||||
- 📌(agents) unpin OpenSSL and related dependencies #1167
|
||||
- ♿️(frontend) add caption font and background color customization #1122
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(frontend) fix hand icon and queue position alignment and position #1119
|
||||
- 🩹(backend) add page_size to pagination for room endpoints #1131
|
||||
- 🐛(backend) refactor lobby throttling to use participant id #1129
|
||||
- 🩹(backend) ignore non-recording uploads in storage webhook handler #1142
|
||||
- 🐛(frontend) fix dimension mismatch in BackgroundCustomProcessor #1116
|
||||
|
||||
## [1.10.0] - 2026-03-05
|
||||
|
||||
|
||||
+23
-39
@@ -13,28 +13,14 @@ RUN apk update && \
|
||||
# ---- Back-end builder image ----
|
||||
FROM base AS back-builder
|
||||
|
||||
WORKDIR /builder
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1
|
||||
ENV UV_LINK_MODE=copy
|
||||
# Copy required python dependencies
|
||||
COPY ./src/backend /builder
|
||||
|
||||
# Disable Python downloads, because we want to use the system interpreter
|
||||
# across both images. If using a managed Python version, it needs to be
|
||||
# copied from the build image into the final image;
|
||||
ENV UV_PYTHON_DOWNLOADS=0
|
||||
RUN mkdir /install && \
|
||||
pip install --prefix=/install .
|
||||
|
||||
# install uv
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.10.9 /uv /uvx /bin/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=src/backend/uv.lock,target=uv.lock \
|
||||
--mount=type=bind,source=src/backend/pyproject.toml,target=pyproject.toml \
|
||||
uv sync --locked --no-install-project --no-dev
|
||||
COPY src/backend /app
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --locked --no-dev
|
||||
|
||||
# ---- mails ----
|
||||
FROM node:20 AS mail-builder
|
||||
@@ -44,7 +30,7 @@ COPY ./src/mail /mail/app
|
||||
WORKDIR /mail/app
|
||||
|
||||
RUN yarn install --frozen-lockfile && \
|
||||
yarn build
|
||||
yarn build
|
||||
|
||||
|
||||
# ---- static link collector ----
|
||||
@@ -56,17 +42,17 @@ RUN apk add \
|
||||
libmagic \
|
||||
rdfind
|
||||
|
||||
# Copy installed python dependencies
|
||||
COPY --from=back-builder /install /usr/local
|
||||
|
||||
# Copy Meet application (see .dockerignore)
|
||||
COPY ./src/backend /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the application from the builder
|
||||
COPY --from=back-builder /app /app
|
||||
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
|
||||
# collectstatic
|
||||
RUN DJANGO_CONFIGURATION=Build DJANGO_JWT_PRIVATE_SIGNING_KEY=Dummy \
|
||||
python manage.py collectstatic --noinput
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
# Replace duplicated file by a symlink to decrease the overall size of the
|
||||
# final image
|
||||
@@ -95,17 +81,14 @@ COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
|
||||
# docker user (see entrypoint).
|
||||
RUN chmod g=u /etc/passwd
|
||||
|
||||
# Copy the application from the builder
|
||||
COPY --from=back-builder /app /app
|
||||
# Copy installed python dependencies
|
||||
COPY --from=back-builder /install /usr/local
|
||||
|
||||
# Copy Meet application (see .dockerignore)
|
||||
COPY ./src/backend /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
# Generate compiled translation messages
|
||||
RUN DJANGO_CONFIGURATION=Build \
|
||||
python manage.py compilemessages --ignore=".venv/**/*"
|
||||
|
||||
# We wrap commands run in this container by the following entrypoint that
|
||||
# creates a user on-the-fly with the container user ID (see USER) and root group
|
||||
# ID.
|
||||
@@ -120,9 +103,10 @@ USER root:root
|
||||
# Install psql
|
||||
RUN apk add postgresql-client
|
||||
|
||||
# Install development dependencies
|
||||
RUN --mount=from=ghcr.io/astral-sh/uv:0.10.9,source=/uv,target=/bin/uv \
|
||||
uv sync --all-extras --locked
|
||||
# Uninstall Meet and re-install it in editable mode along with development
|
||||
# dependencies
|
||||
RUN pip uninstall -y meet
|
||||
RUN pip install -e .[dev]
|
||||
|
||||
# Restore the un-privileged user running the application
|
||||
ARG DOCKER_USER
|
||||
@@ -131,7 +115,7 @@ USER ${DOCKER_USER}
|
||||
# Target database host (e.g. database engine following docker compose services
|
||||
# name) & port
|
||||
ENV DB_HOST=postgresql \
|
||||
DB_PORT=5432
|
||||
DB_PORT=5432
|
||||
|
||||
# Run django development server
|
||||
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||
|
||||
@@ -191,7 +191,6 @@ lint-pylint: ## lint back-end python sources with pylint only on changed files f
|
||||
|
||||
test: ## run project tests
|
||||
@$(MAKE) test-back-parallel
|
||||
@$(MAKE) test-summary
|
||||
.PHONY: test
|
||||
|
||||
test-back: ## run back-end tests
|
||||
@@ -204,11 +203,6 @@ test-back-parallel: ## run all back-end tests in parallel
|
||||
bin/pytest -n auto $${args:-${1}}
|
||||
.PHONY: test-back-parallel
|
||||
|
||||
test-summary: ## run summary tests
|
||||
@args="$(filter-out $@,$(MAKECMDGOALS))" && \
|
||||
bin/pytest-summary $${args:-${1}}
|
||||
.PHONY: test-summary
|
||||
|
||||
makemigrations: ## run django makemigrations for the Meet project.
|
||||
@echo "$(BOLD)Running makemigrations$(RESET)"
|
||||
@$(COMPOSE) up -d postgresql
|
||||
@@ -229,7 +223,7 @@ superuser: ## Create an admin superuser with password "admin"
|
||||
.PHONY: superuser
|
||||
|
||||
back-i18n-compile: ## compile the gettext files
|
||||
@$(MANAGE) compilemessages --ignore=".venv/**/*"
|
||||
@$(MANAGE) compilemessages --ignore="venv/**/*"
|
||||
.PHONY: back-i18n-compile
|
||||
|
||||
back-i18n-generate: ## create the .pot files used for i18n
|
||||
@@ -360,13 +354,13 @@ install-external-secrets: ## install the kubernetes secrets from Vaultwarden
|
||||
.PHONY: build-k8s-cluster
|
||||
|
||||
start-tilt: ## start the kubernetes cluster using kind
|
||||
tilt up --namespace=meet -f ./bin/Tiltfile
|
||||
tilt up -f ./bin/Tiltfile
|
||||
.PHONY: build-k8s-cluster
|
||||
|
||||
start-tilt-keycloak: ## start the kubernetes cluster using kind, without Pro Connect for authentication, use keycloak
|
||||
DEV_ENV=dev-keycloak tilt up --namespace=meet -f ./bin/Tiltfile
|
||||
DEV_ENV=dev-keycloak tilt up -f ./bin/Tiltfile
|
||||
.PHONY: build-k8s-cluster
|
||||
|
||||
start-tilt-dinum: ## start the kubernetes cluster using kind, without Pro Connect for authentication, but with DINUM styles
|
||||
DEV_ENV=dev-dinum tilt up --namespace=meet -f ./bin/Tiltfile
|
||||
DEV_ENV=dev-dinum tilt up -f ./bin/Tiltfile
|
||||
.PHONY: build-k8s-cluster
|
||||
|
||||
@@ -103,7 +103,6 @@ k8s_resource('meet-celery-backend', resource_deps=['redis'])
|
||||
k8s_resource('meet-celery-summarize', resource_deps=['redis'])
|
||||
k8s_resource('meet-celery-transcribe', resource_deps=['redis'])
|
||||
k8s_resource('meet-backend-migrate', resource_deps=['meet-backend'])
|
||||
k8s_resource('livekit-livekit-server', resource_deps=['redis'])
|
||||
k8s_resource('livekit-livekit-server-test-connection', resource_deps=['livekit-livekit-server'])
|
||||
k8s_resource('keycloak', resource_deps=['kc-postgresql'])
|
||||
k8s_resource('meet-backend-createsuperuser', resource_deps=['meet-backend-migrate'])
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/_config.sh"
|
||||
|
||||
_dc_run \
|
||||
app-summary-dev \
|
||||
python -m pytest "$@"
|
||||
+1
-3
@@ -58,7 +58,7 @@ services:
|
||||
/usr/bin/mc admin config set meet notify_webhook:meet-webhook endpoint='http://app-dev:8000/api/v1.0/recordings/storage-hook/' auth_token='Bearer password' &&
|
||||
/usr/bin/mc admin service restart meet --wait --json &&
|
||||
sleep 15 &&
|
||||
/usr/bin/mc event add meet/meet-media-storage arn:minio:sqs::meet-webhook:webhook --event put --prefix "recordings" &&
|
||||
/usr/bin/mc event add meet/meet-media-storage arn:minio:sqs::meet-webhook:webhook --event put &&
|
||||
exit 0;"
|
||||
|
||||
app-dev:
|
||||
@@ -80,7 +80,6 @@ services:
|
||||
volumes:
|
||||
- ./src/backend:/app
|
||||
- ./data/static:/data/static
|
||||
- /app/.venv
|
||||
depends_on:
|
||||
- postgresql
|
||||
- mailcatcher
|
||||
@@ -106,7 +105,6 @@ services:
|
||||
volumes:
|
||||
- ./src/backend:/app
|
||||
- ./data/static:/data/static
|
||||
- /app/.venv
|
||||
depends_on:
|
||||
- app-dev
|
||||
|
||||
|
||||
@@ -61,10 +61,11 @@ services:
|
||||
`docker compose up -d`
|
||||
```
|
||||
|
||||
Your keycloak instance is now available on https://id.yourdomain.tld
|
||||
Your keycloak instance is now available on https://doc.yourdomain.tld
|
||||
|
||||
> [!CAUTION]
|
||||
> Version of the images are set to latest, you should pin it to the desired version to avoid unwanted upgrades when pulling latest image. You can find available versions on [Keycloak registry](https://quay.io/repository/keycloak/keycloak?tab=tags).
|
||||
```
|
||||
|
||||
## Creating an OIDC Client for Meet Application
|
||||
|
||||
@@ -75,7 +76,7 @@ Your keycloak instance is now available on https://id.yourdomain.tld
|
||||
3. Enter the name of the realm - `meet`.
|
||||
4. Click "Create".
|
||||
|
||||
### Step 2: Create a New Client
|
||||
#### Step 2: Create a New Client
|
||||
|
||||
1. Navigate to the "Clients" tab.
|
||||
2. Click on the "Create client" button.
|
||||
@@ -85,7 +86,7 @@ Your keycloak instance is now available on https://id.yourdomain.tld
|
||||
1. Set the "Web Origins" to the URL of your meet application - e.g. `https://meet.example.com`.
|
||||
1. Click "Save".
|
||||
|
||||
### Step 3: Get Client Credentials
|
||||
#### Step 3: Get Client Credentials
|
||||
|
||||
1. Go to the "Credentials" tab.
|
||||
2. Copy the client ID (`meet` in this example) and the client secret.
|
||||
|
||||
@@ -71,7 +71,7 @@ backend:
|
||||
# Extra volume to manage our local custom CA and avoid to set ssl_verify: false
|
||||
extraVolumeMounts:
|
||||
- name: certs
|
||||
mountPath: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
mountPath: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem
|
||||
subPath: cacert.pem
|
||||
|
||||
# Extra volume to manage our local custom CA and avoid to set ssl_verify: false
|
||||
|
||||
@@ -27,8 +27,7 @@ AWS_S3_DOMAIN_REPLACE=http://localhost:9000
|
||||
AWS_S3_ENDPOINT_URL=http://minio:9000
|
||||
AWS_S3_ACCESS_KEY_ID=meet
|
||||
AWS_S3_SECRET_ACCESS_KEY=password
|
||||
MEDIA_BASE_URL=http://localhost:3000
|
||||
FILE_UPLOAD_ENABLED=True
|
||||
MEDIA_BASE_URL=http://localhost:8083
|
||||
|
||||
# OIDC
|
||||
OIDC_OP_JWKS_ENDPOINT=http://nginx:8083/realms/meet/protocol/openid-connect/certs
|
||||
|
||||
@@ -4,6 +4,8 @@ FROM python:3.13-slim AS base
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libglib2.0-0 \
|
||||
libgobject-2.0-0 \
|
||||
"openssl=3.5.4-1~deb13u2" \
|
||||
"libssl3t64=3.5.4-1~deb13u2" \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
FROM base AS builder
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
[project]
|
||||
name = "agents"
|
||||
version = "1.11.0"
|
||||
version = "1.10.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"livekit-agents==1.3.10",
|
||||
|
||||
@@ -308,7 +308,7 @@ class ApplicationAdmin(admin.ModelAdmin):
|
||||
|
||||
form = ApplicationAdminForm
|
||||
|
||||
list_display = ("id", "name", "client_id", "get_scopes_display", "is_active")
|
||||
list_display = ("id", "name", "client_id", "get_scopes_display")
|
||||
fields = [
|
||||
"name",
|
||||
"id",
|
||||
@@ -317,7 +317,6 @@ class ApplicationAdmin(admin.ModelAdmin):
|
||||
"scopes",
|
||||
"client_id",
|
||||
"client_secret",
|
||||
"is_active",
|
||||
]
|
||||
readonly_fields = ["id", "created_at", "updated_at"]
|
||||
inlines = [ApplicationDomainInline]
|
||||
|
||||
@@ -43,21 +43,6 @@ def get_frontend_configuration(request):
|
||||
"expiration_days": settings.RECORDING_EXPIRATION_DAYS,
|
||||
"max_duration": settings.RECORDING_MAX_DURATION,
|
||||
},
|
||||
"background_image": {
|
||||
"upload_is_enabled": settings.FILE_UPLOAD_ENABLED,
|
||||
"max_count_by_user": settings.FILE_UPLOAD_RESTRICTIONS["background_image"][
|
||||
"max_count_by_user"
|
||||
],
|
||||
"max_size": settings.FILE_UPLOAD_RESTRICTIONS["background_image"][
|
||||
"max_size"
|
||||
],
|
||||
"allowed_extensions": settings.FILE_UPLOAD_RESTRICTIONS["background_image"][
|
||||
"allowed_extensions"
|
||||
],
|
||||
"allowed_mimetypes": settings.FILE_UPLOAD_RESTRICTIONS["background_image"][
|
||||
"allowed_mimetypes"
|
||||
],
|
||||
},
|
||||
"telephony": {
|
||||
"enabled": settings.ROOM_TELEPHONY_ENABLED,
|
||||
"phone_number": settings.ROOM_TELEPHONY_PHONE_NUMBER
|
||||
|
||||
@@ -13,7 +13,6 @@ class FeatureFlag:
|
||||
"recording": "RECORDING_ENABLE",
|
||||
"storage_event": "RECORDING_STORAGE_EVENT_ENABLE",
|
||||
"subtitle": "ROOM_SUBTITLE_ENABLED",
|
||||
"file_upload": "FILE_UPLOAD_ENABLED",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Permission handlers for the Meet core app."""
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import Http404
|
||||
|
||||
from rest_framework import permissions
|
||||
@@ -117,13 +116,6 @@ class FilePermission(IsAuthenticated):
|
||||
Handling soft deletions specificities
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
"""Allow access only to authenticated users."""
|
||||
if not settings.FILE_UPLOAD_ENABLED:
|
||||
raise Http404
|
||||
|
||||
return super().has_permission(request, view)
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
"""
|
||||
Return a 404 on deleted files or if the user is not the owner
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""API endpoints"""
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
import re
|
||||
import uuid
|
||||
from logging import getLogger
|
||||
from urllib.parse import unquote, urlparse
|
||||
@@ -11,7 +12,6 @@ from django.db.models import Q
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django_filters import rest_framework as django_filters
|
||||
from rest_framework import (
|
||||
@@ -33,12 +33,10 @@ from rest_framework import (
|
||||
|
||||
from core import enums, models, utils
|
||||
from core.api.filters import ListFileFilter
|
||||
from core.enums import MEDIA_STORAGE_URL_PATTERN
|
||||
from core.recording.enums import FileExtension
|
||||
from core.recording.event.authentication import StorageEventAuthentication
|
||||
from core.recording.event.exceptions import (
|
||||
InvalidBucketError,
|
||||
InvalidFilepathError,
|
||||
InvalidFileTypeError,
|
||||
ParsingEventDataError,
|
||||
)
|
||||
@@ -80,6 +78,17 @@ from .feature_flag import FeatureFlag
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
FILE_FOLDER = settings.FILE_UPLOAD_PATH
|
||||
UUID_REGEX = (
|
||||
r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
|
||||
)
|
||||
FILE_EXT_REGEX = r"[\d\w]+"
|
||||
MEDIA_STORAGE_URL_PATTERN = re.compile(
|
||||
f"{settings.MEDIA_URL:s}"
|
||||
rf"(?P<key>{FILE_FOLDER:s}/(?P<pk>{UUID_REGEX:s})/\.{FILE_EXT_REGEX:s})$"
|
||||
)
|
||||
|
||||
|
||||
class NestedGenericViewSet(viewsets.GenericViewSet):
|
||||
"""
|
||||
A generic Viewset aims to be used in a nested route context.
|
||||
@@ -488,7 +497,9 @@ class RoomViewSet(
|
||||
if status_code == drf_status.HTTP_500_INTERNAL_SERVER_ERROR:
|
||||
raise e
|
||||
|
||||
return drf_response.Response({"status": "error"}, status=status_code)
|
||||
return drf_response.Response(
|
||||
{"status": "error", "message": str(e)}, status=status_code
|
||||
)
|
||||
|
||||
@decorators.action(
|
||||
detail=False,
|
||||
@@ -755,19 +766,14 @@ class RecordingViewSet(
|
||||
recording_id = parser.get_recording_id(request.data)
|
||||
|
||||
except ParsingEventDataError as e:
|
||||
raise drf_exceptions.PermissionDenied("Invalid request data.") from e
|
||||
raise drf_exceptions.PermissionDenied(f"Invalid request data: {e}") from e
|
||||
|
||||
except InvalidBucketError as e:
|
||||
raise drf_exceptions.PermissionDenied("Invalid bucket specified.") from e
|
||||
raise drf_exceptions.PermissionDenied("Invalid bucket specified") from e
|
||||
|
||||
except InvalidFilepathError:
|
||||
except InvalidFileTypeError as e:
|
||||
return drf_response.Response(
|
||||
{"message": "Notification ignored."},
|
||||
)
|
||||
|
||||
except InvalidFileTypeError:
|
||||
return drf_response.Response(
|
||||
{"message": "Notification ignored."},
|
||||
{"message": f"Ignore this file type, {e}"},
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -967,26 +973,6 @@ class FileViewSet(
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""Set the current user as creator of the newly created file."""
|
||||
|
||||
if settings.FILE_UPLOAD_APPLY_RESTRICTIONS:
|
||||
file_type = serializer.validated_data["type"]
|
||||
config_for_file_type = settings.FILE_UPLOAD_RESTRICTIONS[file_type]
|
||||
|
||||
count = models.File.objects.filter(
|
||||
creator=self.request.user,
|
||||
deleted_at__isnull=True,
|
||||
type=file_type,
|
||||
).count()
|
||||
|
||||
if count >= config_for_file_type["max_count_by_user"]:
|
||||
logger.info(
|
||||
"create_item: user reached max files per user for type %s",
|
||||
file_type,
|
||||
)
|
||||
raise serializers.PermissionDenied(
|
||||
_("You have reached the maximum number of files for this type.")
|
||||
)
|
||||
|
||||
serializer.save(creator=self.request.user)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
@@ -994,7 +980,6 @@ class FileViewSet(
|
||||
instance.soft_delete()
|
||||
|
||||
@decorators.action(detail=True, methods=["post"], url_path="upload-ended")
|
||||
@FeatureFlag.require("file_upload")
|
||||
def upload_ended(self, request, *args, **kwargs):
|
||||
"""
|
||||
Check the actual uploaded file and mark it as ready.
|
||||
@@ -1177,7 +1162,6 @@ class FileViewSet(
|
||||
return url_params, request.user.id, file
|
||||
|
||||
@decorators.action(detail=False, methods=["get"], url_path="media-auth")
|
||||
@FeatureFlag.require("file_upload")
|
||||
def media_auth(self, request, *args, **kwargs):
|
||||
"""
|
||||
This view is used by an Nginx subrequest to control access to an file's
|
||||
|
||||
@@ -14,15 +14,9 @@ FILE_EXT_REGEX = r"[a-zA-Z0-9]{1,10}"
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
RECORDING_STORAGE_URL_PATTERN = re.compile(
|
||||
rf"{settings.MEDIA_URL:s}{settings.RECORDING_OUTPUT_FOLDER}/(?P<recording_id>{UUID_REGEX:s})\.(?P<extension>{FILE_EXT_REGEX:s})"
|
||||
f"/media/{settings.RECORDING_OUTPUT_FOLDER}/(?P<recording_id>{UUID_REGEX:s}).(?P<extension>{FILE_EXT_REGEX:s})"
|
||||
)
|
||||
|
||||
MEDIA_STORAGE_URL_PATTERN = re.compile(
|
||||
f"{settings.MEDIA_URL:s}"
|
||||
rf"(?P<key>{settings.FILE_UPLOAD_PATH:s}/(?P<pk>{UUID_REGEX:s})\.{FILE_EXT_REGEX:s})$"
|
||||
)
|
||||
|
||||
|
||||
# Django sets `LANGUAGES` by default with all supported languages. We can use it for
|
||||
# the choice of languages which should not be limited to the few languages active in
|
||||
# the app.
|
||||
|
||||
@@ -203,7 +203,7 @@ class ApplicationJWTAuthentication(BaseJWTAuthentication):
|
||||
logger.warning("Application not found: %s", client_id)
|
||||
raise exceptions.AuthenticationFailed("Application not found.") from e
|
||||
|
||||
if not application.is_active:
|
||||
if not application.active:
|
||||
logger.warning(
|
||||
"Inactive application attempted authentication: %s", client_id
|
||||
)
|
||||
|
||||
@@ -61,12 +61,12 @@ class ApplicationViewSet(viewsets.ViewSet):
|
||||
except models.Application.DoesNotExist as e:
|
||||
raise drf_exceptions.AuthenticationFailed("Invalid credentials") from e
|
||||
|
||||
if not application.active:
|
||||
raise drf_exceptions.AuthenticationFailed("Application is inactive")
|
||||
|
||||
if not check_password(client_secret, application.client_secret):
|
||||
raise drf_exceptions.AuthenticationFailed("Invalid credentials")
|
||||
|
||||
if not application.is_active:
|
||||
raise drf_exceptions.AuthenticationFailed("Application is inactive")
|
||||
|
||||
email = serializer.validated_data["scope"]
|
||||
try:
|
||||
validate_email(email)
|
||||
|
||||
@@ -129,7 +129,7 @@ class ApplicationFactory(factory.django.DjangoModelFactory):
|
||||
model = models.Application
|
||||
|
||||
name = factory.Faker("company")
|
||||
is_active = True
|
||||
active = True
|
||||
client_id = factory.LazyFunction(utils.generate_client_id)
|
||||
client_secret = factory.LazyFunction(utils.generate_client_secret)
|
||||
scopes = []
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.2.12 on 2026-03-11 14:39
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0017_file'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='application',
|
||||
old_name='active',
|
||||
new_name='is_active',
|
||||
),
|
||||
]
|
||||
@@ -759,7 +759,7 @@ class Application(BaseModel):
|
||||
verbose_name=_("Application name"),
|
||||
help_text=_("Descriptive name for this application."),
|
||||
)
|
||||
is_active = models.BooleanField(default=True)
|
||||
active = models.BooleanField(default=True)
|
||||
client_id = models.CharField(
|
||||
max_length=100, unique=True, default=utils.generate_client_id
|
||||
)
|
||||
@@ -952,7 +952,7 @@ class File(BaseModel):
|
||||
_, extension = splitext(self.filename)
|
||||
# We store only the extension in the storage system to avoid
|
||||
# leaking Personal Information in logs, etc.
|
||||
return f"{self.key_base}{extension!s}"
|
||||
return f"{self.key_base}/{extension!s}"
|
||||
|
||||
def get_abilities(self, user):
|
||||
"""
|
||||
|
||||
@@ -9,8 +9,6 @@ from typing import Any, Dict, Optional, Protocol
|
||||
from django.conf import settings
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
from core.enums import FILE_EXT_REGEX, UUID_REGEX
|
||||
|
||||
from .exceptions import (
|
||||
InvalidBucketError,
|
||||
InvalidFilepathError,
|
||||
@@ -88,7 +86,7 @@ class MinioParser:
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
self._filepath_regex = re.compile(
|
||||
rf"(?P<url_encoded_folder_path>(?:[^%]+%2F)+)?{settings.RECORDING_OUTPUT_FOLDER}%2F(?P<recording_id>{UUID_REGEX})\.(?P<extension>{FILE_EXT_REGEX})"
|
||||
r"(?P<url_encoded_folder_path>(?:[^%]+%2F)+)?(?P<recording_id>[0-9a-fA-F\-]{36})\.(?P<extension>[a-zA-Z0-9]+)"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -118,7 +118,7 @@ def test_api_files_create_file_authenticated_success():
|
||||
|
||||
assert policy_parsed.scheme == "http"
|
||||
assert policy_parsed.netloc == "localhost:9000"
|
||||
assert policy_parsed.path == f"/meet-media-storage/files/{file.id!s}.png"
|
||||
assert policy_parsed.path == f"/meet-media-storage/files/{file.id!s}/.png"
|
||||
|
||||
query_params = parse_qs(policy_parsed.query)
|
||||
|
||||
@@ -174,26 +174,6 @@ def test_api_files_create_file_authenticated_extension_case_insensitive():
|
||||
assert file.title == "file"
|
||||
|
||||
|
||||
def test_api_files_create_file_disabled(settings):
|
||||
"""
|
||||
Creating a file is denied if file upload is disabled
|
||||
"""
|
||||
settings.FILE_UPLOAD_ENABLED = False
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
"/api/v1.0/files/",
|
||||
{
|
||||
"type": FileTypeChoices.BACKGROUND_IMAGE,
|
||||
"filename": "file.JPG",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 404
|
||||
assert not File.objects.exists()
|
||||
|
||||
|
||||
def test_api_files_create_file_authenticated_not_checking_extension(settings):
|
||||
"""
|
||||
Creating a file with an extension not allowed should not fail when restrictions are disabled.
|
||||
@@ -259,48 +239,6 @@ def test_api_files_create_file_authenticated_hidden_file_but_checking_extension_
|
||||
assert response.json() == {"filename": ["This file extension is not allowed."]}
|
||||
|
||||
|
||||
def test_api_files_create_file_too_many(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
Creating a file is forbidden if above user limit.
|
||||
"""
|
||||
settings.FILE_UPLOAD_APPLY_RESTRICTIONS = True
|
||||
settings.FILE_UPLOAD_RESTRICTIONS = {
|
||||
"background_image": {
|
||||
**settings.FILE_UPLOAD_RESTRICTIONS["background_image"],
|
||||
"max_count_by_user": 1,
|
||||
},
|
||||
}
|
||||
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
"/api/v1.0/files/",
|
||||
{
|
||||
"type": FileTypeChoices.BACKGROUND_IMAGE,
|
||||
"filename": "1.png",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
response = client.post(
|
||||
"/api/v1.0/files/",
|
||||
{
|
||||
"type": FileTypeChoices.BACKGROUND_IMAGE,
|
||||
"filename": "2.png",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {
|
||||
"detail": "You have reached the maximum number of files for this type."
|
||||
}
|
||||
assert File.objects.count() == 1
|
||||
|
||||
|
||||
def test_api_files_create_force_id_success():
|
||||
"""It should be possible to force the item ID when creating a item."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
@@ -39,7 +39,7 @@ def test_api_files_update_anonymous_forbidden():
|
||||
|
||||
def test_api_files_update_description_and_title():
|
||||
"""
|
||||
Test the description and title of a file can be updated.
|
||||
Test the description and title of an file can be updated.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ def valid_minio_event():
|
||||
"s3": {
|
||||
"bucket": {"name": "test-bucket"},
|
||||
"object": {
|
||||
"key": "recordings%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
|
||||
"key": "recording%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
|
||||
"contentType": "audio/ogg",
|
||||
},
|
||||
}
|
||||
@@ -51,7 +51,7 @@ def test_parse_valid_event(minio_parser, valid_minio_event):
|
||||
"""Test parsing a valid Minio event."""
|
||||
event = minio_parser.parse(valid_minio_event)
|
||||
assert isinstance(event, StorageEvent)
|
||||
assert event.filepath == "recordings%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg"
|
||||
assert event.filepath == "recording%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg"
|
||||
assert event.filetype == "audio/ogg"
|
||||
assert event.bucket_name == "test-bucket"
|
||||
assert event.metadata is None
|
||||
@@ -130,13 +130,11 @@ def test_validate_invalid_filetype(minio_parser):
|
||||
"invalid_filepath",
|
||||
[
|
||||
"invalid_filepath", # totally invalid string
|
||||
"recordings/46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
|
||||
"recordings/46d1a121-2426-484d-8fb3-09b5d886f7a8", # missing extension
|
||||
"recording/46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
|
||||
"recording/46d1a121-2426-484d-8fb3-09b5d886f7a8", # missing extension
|
||||
"46d1a121-2426-484d-8fb3-09b5d886f7a8", # missing url_encoded_folder_path and extension
|
||||
"", # empty string
|
||||
"46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg", # no folder at all
|
||||
"uploads%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg", # wrong folder name
|
||||
"folder%2Fuploads%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg", # nested but no recordings/
|
||||
"recording%2F46d1a1212426484d8fb309b5d886f7a8.ogg",
|
||||
],
|
||||
)
|
||||
def test_validate_invalid_filepath(invalid_filepath, minio_parser):
|
||||
@@ -154,7 +152,7 @@ def test_validate_invalid_filepath(invalid_filepath, minio_parser):
|
||||
def test_validate_valid_event(minio_parser):
|
||||
"""Test validation with valid event data."""
|
||||
event = StorageEvent(
|
||||
filepath="recordings%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
|
||||
filepath="recording%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
|
||||
filetype="audio/ogg",
|
||||
bucket_name="test-bucket",
|
||||
metadata=None,
|
||||
@@ -172,7 +170,7 @@ def test_get_recording_id_success(minio_parser, valid_minio_event):
|
||||
def test_validate_filepath_with_folder(minio_parser):
|
||||
"""Test validation of filepath with folder structure."""
|
||||
event = StorageEvent(
|
||||
filepath="parent_folder%2Frecordings%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
|
||||
filepath="parent_folder%2Ffolder%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
|
||||
filetype="audio/ogg",
|
||||
bucket_name="test-bucket",
|
||||
metadata=None,
|
||||
@@ -221,7 +219,7 @@ def test_validate_custom_filetypes():
|
||||
parser = MinioParser(bucket_name="test-bucket", allowed_filetypes={"audio/mp3"})
|
||||
|
||||
event = StorageEvent(
|
||||
filepath="parent_folder%2Frecordings%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
|
||||
filepath="parent_folder%2Ffolder%2F46d1a121-2426-484d-8fb3-09b5d886f7a8.ogg",
|
||||
filetype="audio/mp3",
|
||||
bucket_name="test-bucket",
|
||||
metadata=None,
|
||||
|
||||
@@ -14,7 +14,6 @@ from ...factories import RecordingFactory
|
||||
from ...models import Recording, RecordingStatusChoices
|
||||
from ...recording.event.exceptions import (
|
||||
InvalidBucketError,
|
||||
InvalidFilepathError,
|
||||
InvalidFileTypeError,
|
||||
ParsingEventDataError,
|
||||
)
|
||||
@@ -95,7 +94,7 @@ def test_save_recording_parsing_error(recording_settings, mock_get_parser, clien
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {"detail": "Invalid request data."}
|
||||
assert response.json() == {"detail": "Invalid request data: Error message"}
|
||||
|
||||
|
||||
def test_save_recording_bucket_error(recording_settings, mock_get_parser, client):
|
||||
@@ -112,7 +111,7 @@ def test_save_recording_bucket_error(recording_settings, mock_get_parser, client
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {"detail": "Invalid bucket specified."}
|
||||
assert response.json() == {"detail": "Invalid bucket specified"}
|
||||
|
||||
|
||||
def test_save_recording_filetype_error(recording_settings, mock_get_parser):
|
||||
@@ -133,28 +132,7 @@ def test_save_recording_filetype_error(recording_settings, mock_get_parser):
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"message": "Notification ignored."}
|
||||
|
||||
|
||||
def test_save_recording_filepath_error(recording_settings, mock_get_parser):
|
||||
"""Test handling of unsupported filepath in recording event data."""
|
||||
|
||||
mock_parser = mock.Mock()
|
||||
mock_parser.get_recording_id.side_effect = InvalidFilepathError(
|
||||
"Invalid filepath structure: parent/folder/recording.jpeg"
|
||||
)
|
||||
mock_get_parser.return_value = mock_parser
|
||||
|
||||
client = APIClient()
|
||||
|
||||
response = client.post(
|
||||
"/api/v1.0/recordings/storage-hook/",
|
||||
{"recording_data": "valid-data"},
|
||||
HTTP_AUTHORIZATION="Bearer testAuthToken",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"message": "Notification ignored."}
|
||||
assert response.json() == {"message": "Ignore this file type, unsupported '.json'"}
|
||||
|
||||
|
||||
def test_save_recording_unknown_recording(recording_settings, mock_get_parser, client):
|
||||
|
||||
@@ -77,6 +77,7 @@ def test_missing_auth_header(client, serialized_event_data, mock_livekit_config)
|
||||
assert response.status_code == 401
|
||||
assert response.json() == {
|
||||
"status": "error",
|
||||
"message": "Authorization header missing",
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +91,7 @@ def test_invalid_payload(client, auth_token, mock_livekit_config):
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"status": "error"}
|
||||
assert response.json() == {"status": "error", "message": "Invalid webhook payload"}
|
||||
|
||||
|
||||
def test_unknown_event_type(client, mock_livekit_config):
|
||||
@@ -115,6 +116,7 @@ def test_unknown_event_type(client, mock_livekit_config):
|
||||
assert response.status_code == 422
|
||||
assert response.json() == {
|
||||
"status": "error",
|
||||
"message": "Unknown webhook type: unknown_event_type",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -904,7 +904,7 @@ def test_api_rooms_token_unknown_application(settings):
|
||||
|
||||
def test_api_rooms_token_inactive_application(settings):
|
||||
"""Token for inactive application should be rejected."""
|
||||
application = ApplicationFactory(is_active=False)
|
||||
application = ApplicationFactory(active=False)
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
payload = {
|
||||
|
||||
@@ -23,7 +23,7 @@ def test_api_applications_generate_token_success(settings):
|
||||
"""Valid credentials should return a JWT token."""
|
||||
UserFactory(email="User.Family@example.com")
|
||||
application = ApplicationFactory(
|
||||
is_active=True,
|
||||
active=True,
|
||||
scopes=[ApplicationScope.ROOMS_LIST, ApplicationScope.ROOMS_CREATE],
|
||||
)
|
||||
|
||||
@@ -79,7 +79,7 @@ def test_api_applications_generate_token_invalid_client_id():
|
||||
def test_api_applications_generate_token_invalid_client_secret():
|
||||
"""Invalid client_secret should return 401."""
|
||||
user = UserFactory(email="user@example.com")
|
||||
application = ApplicationFactory(is_active=True)
|
||||
application = ApplicationFactory(active=True)
|
||||
|
||||
client = APIClient()
|
||||
response = client.post(
|
||||
@@ -100,7 +100,7 @@ def test_api_applications_generate_token_invalid_client_secret():
|
||||
def test_api_applications_generate_token_inactive_application():
|
||||
"""Inactive application should return 401."""
|
||||
user = UserFactory(email="user@example.com")
|
||||
application = ApplicationFactory(is_active=False)
|
||||
application = ApplicationFactory(active=False)
|
||||
|
||||
plain_secret = "test-secret-123"
|
||||
application.client_secret = plain_secret
|
||||
@@ -122,31 +122,9 @@ def test_api_applications_generate_token_inactive_application():
|
||||
assert "Application is inactive" in str(response.data)
|
||||
|
||||
|
||||
def test_api_applications_generate_token_inactive_application_wrong_secret():
|
||||
"""An inactive application with a wrong secret should return 401."""
|
||||
user = UserFactory(email="user@example.com")
|
||||
application = ApplicationFactory(is_active=False)
|
||||
|
||||
client = APIClient()
|
||||
response = client.post(
|
||||
"/external-api/v1.0/application/token/",
|
||||
{
|
||||
"client_id": application.client_id,
|
||||
"client_secret": "wrong-secret",
|
||||
"grant_type": "client_credentials",
|
||||
"scope": user.email,
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
assert "Invalid credentials" in str(response.data)
|
||||
assert "inactive" not in str(response.data).lower()
|
||||
|
||||
|
||||
def test_api_applications_generate_token_invalid_email_format():
|
||||
"""Invalid email format should return 400."""
|
||||
application = ApplicationFactory(is_active=True)
|
||||
application = ApplicationFactory(active=True)
|
||||
|
||||
plain_secret = "test-secret-123"
|
||||
application.client_secret = plain_secret
|
||||
@@ -171,7 +149,7 @@ def test_api_applications_generate_token_invalid_email_format():
|
||||
def test_api_applications_generate_token_domain_not_authorized():
|
||||
"""Application without domain authorization should return 403."""
|
||||
user = UserFactory(email="user@denied.com")
|
||||
application = ApplicationFactory(is_active=True)
|
||||
application = ApplicationFactory(active=True)
|
||||
ApplicationDomainFactory(application=application, domain="allowed.com")
|
||||
|
||||
plain_secret = "test-secret-123"
|
||||
@@ -198,7 +176,7 @@ def test_api_applications_generate_token_domain_authorized():
|
||||
"""Application with domain authorization should succeed."""
|
||||
user = UserFactory(email="user@allowed.com")
|
||||
application = ApplicationFactory(
|
||||
is_active=True,
|
||||
active=True,
|
||||
scopes=[ApplicationScope.ROOMS_LIST],
|
||||
)
|
||||
ApplicationDomainFactory(application=application, domain="allowed.com")
|
||||
@@ -225,7 +203,7 @@ def test_api_applications_generate_token_domain_authorized():
|
||||
|
||||
def test_api_applications_generate_token_user_not_found():
|
||||
"""Non-existent user should return 404."""
|
||||
application = ApplicationFactory(is_active=True)
|
||||
application = ApplicationFactory(active=True)
|
||||
|
||||
plain_secret = "test-secret-123"
|
||||
application.client_secret = plain_secret
|
||||
@@ -253,7 +231,7 @@ def test_api_applications_token_payload_structure(settings):
|
||||
user = UserFactory(email="user@example.com")
|
||||
|
||||
application = ApplicationFactory(
|
||||
is_active=True,
|
||||
active=True,
|
||||
scopes=[ApplicationScope.ROOMS_LIST, ApplicationScope.ROOMS_CREATE],
|
||||
)
|
||||
|
||||
@@ -306,7 +284,7 @@ def test_api_applications_token_new_user(settings):
|
||||
assert len(User.objects.all()) == 0
|
||||
|
||||
application = ApplicationFactory(
|
||||
is_active=True,
|
||||
active=True,
|
||||
scopes=[ApplicationScope.ROOMS_LIST, ApplicationScope.ROOMS_CREATE],
|
||||
)
|
||||
|
||||
@@ -364,7 +342,7 @@ def test_api_applications_token_existing_user(settings):
|
||||
assert len(User.objects.all()) == 1
|
||||
|
||||
application = ApplicationFactory(
|
||||
is_active=True,
|
||||
active=True,
|
||||
scopes=[ApplicationScope.ROOMS_LIST, ApplicationScope.ROOMS_CREATE],
|
||||
)
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ def test_models_application_name_maxlength():
|
||||
def test_models_application_active_default():
|
||||
"""An application should be active by default."""
|
||||
application = Application.objects.create(name="Test App")
|
||||
assert application.is_active is True
|
||||
assert application.active is True
|
||||
|
||||
|
||||
def test_models_application_scopes_default():
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:46+0000\n"
|
||||
"POT-Creation-Date: 2026-03-11 17:31+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -76,11 +76,11 @@ msgstr "%(count)s Aufnahme(n) erfolgreich als ‚Fehler beim Stoppen‘ markiert
|
||||
msgid "Skipped %(count)s recording(s) with an ineligible status."
|
||||
msgstr "%(count)s abgelaufene Aufnahme(n) übersprungen."
|
||||
|
||||
#: core/admin.py:342
|
||||
#: core/admin.py:341
|
||||
msgid "No scopes"
|
||||
msgstr "Keine Scopes"
|
||||
|
||||
#: core/admin.py:344
|
||||
#: core/admin.py:343
|
||||
msgid "Scopes"
|
||||
msgstr "Scopes"
|
||||
|
||||
@@ -98,10 +98,6 @@ msgstr ""
|
||||
msgid "This file extension is not allowed."
|
||||
msgstr "Diese Dateiendung ist nicht erlaubt."
|
||||
|
||||
#: core/api/serializers.py:533
|
||||
msgid "You have reached the maximum number of files for this type."
|
||||
msgstr "Sie haben die maximale Anzahl an Dateien dieses Typs erreicht."
|
||||
|
||||
#: core/models.py:37
|
||||
msgid "Member"
|
||||
msgstr "Mitglied"
|
||||
@@ -604,18 +600,18 @@ msgstr ""
|
||||
" Wenn Sie Fragen haben oder Unterstützung benötigen, wenden Sie sich bitte "
|
||||
"an unser Support-Team unter %(support_email)s. "
|
||||
|
||||
#: meet/settings.py:223
|
||||
#: meet/settings.py:215
|
||||
msgid "English"
|
||||
msgstr "Englisch"
|
||||
|
||||
#: meet/settings.py:224
|
||||
#: meet/settings.py:216
|
||||
msgid "French"
|
||||
msgstr "Französisch"
|
||||
|
||||
#: meet/settings.py:225
|
||||
#: meet/settings.py:217
|
||||
msgid "Dutch"
|
||||
msgstr "Niederländisch"
|
||||
|
||||
#: meet/settings.py:226
|
||||
#: meet/settings.py:218
|
||||
msgid "German"
|
||||
msgstr "Deutsch"
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:46+0000\n"
|
||||
"POT-Creation-Date: 2026-03-11 17:31+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -76,11 +76,11 @@ msgstr "%(count)s recording(s) successfully marked as 'Failed to Stop'."
|
||||
msgid "Skipped %(count)s recording(s) with an ineligible status."
|
||||
msgstr "Skipped %(count)s expired recording(s)."
|
||||
|
||||
#: core/admin.py:342
|
||||
#: core/admin.py:341
|
||||
msgid "No scopes"
|
||||
msgstr "No scopes"
|
||||
|
||||
#: core/admin.py:344
|
||||
#: core/admin.py:343
|
||||
msgid "Scopes"
|
||||
msgstr "Scopes"
|
||||
|
||||
@@ -96,10 +96,6 @@ msgstr "You must be administrator or owner of a room to add accesses to it."
|
||||
msgid "This file extension is not allowed."
|
||||
msgstr "This file extension is not allowed."
|
||||
|
||||
#: core/api/serializers.py:533
|
||||
msgid "You have reached the maximum number of files for this type."
|
||||
msgstr "You have reached the maximum number of files for this type."
|
||||
|
||||
#: core/models.py:37
|
||||
msgid "Member"
|
||||
msgstr "Member"
|
||||
@@ -600,18 +596,18 @@ msgstr ""
|
||||
" If you have any questions or need assistance, please contact our support "
|
||||
"team at %(support_email)s. "
|
||||
|
||||
#: meet/settings.py:223
|
||||
#: meet/settings.py:215
|
||||
msgid "English"
|
||||
msgstr "English"
|
||||
|
||||
#: meet/settings.py:224
|
||||
#: meet/settings.py:216
|
||||
msgid "French"
|
||||
msgstr "French"
|
||||
|
||||
#: meet/settings.py:225
|
||||
#: meet/settings.py:217
|
||||
msgid "Dutch"
|
||||
msgstr "Dutch"
|
||||
|
||||
#: meet/settings.py:226
|
||||
#: meet/settings.py:218
|
||||
msgid "German"
|
||||
msgstr "German"
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:46+0000\n"
|
||||
"POT-Creation-Date: 2026-03-11 17:31+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: antoine.lebaud@mail.numerique.gouv.fr\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -68,8 +68,7 @@ msgstr "Marquer les enregistrements sélectionnés comme « Échec d’arrêt »
|
||||
#: core/admin.py:218
|
||||
#, python-format
|
||||
msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'."
|
||||
msgstr ""
|
||||
"%(count)s enregistrement(s) marqué(s) avec succès comme « Échec d’arrêt »."
|
||||
msgstr "%(count)s enregistrement(s) marqué(s) avec succès comme « Échec d’arrêt »."
|
||||
|
||||
#: core/admin.py:226
|
||||
#, fuzzy, python-format
|
||||
@@ -77,11 +76,11 @@ msgstr ""
|
||||
msgid "Skipped %(count)s recording(s) with an ineligible status."
|
||||
msgstr "%(count)s enregistrement(s) avec un statut inéligible ignoré(s)."
|
||||
|
||||
#: core/admin.py:342
|
||||
#: core/admin.py:341
|
||||
msgid "No scopes"
|
||||
msgstr "Aucun scopes"
|
||||
|
||||
#: core/admin.py:344
|
||||
#: core/admin.py:343
|
||||
msgid "Scopes"
|
||||
msgstr "Scopes"
|
||||
|
||||
@@ -99,10 +98,6 @@ msgstr ""
|
||||
msgid "This file extension is not allowed."
|
||||
msgstr "Cette extension n'est pas autorisée"
|
||||
|
||||
#: core/api/serializers.py:533
|
||||
msgid "You have reached the maximum number of files for this type."
|
||||
msgstr "Vous avez atteint le nombre maximum de fichiers de ce type"
|
||||
|
||||
#: core/models.py:37
|
||||
msgid "Member"
|
||||
msgstr "Membre"
|
||||
@@ -606,18 +601,18 @@ msgstr ""
|
||||
" Si vous avez des questions ou besoin d'assistance, veuillez contacter notre "
|
||||
"équipe d'assistance à %(support_email)s. "
|
||||
|
||||
#: meet/settings.py:223
|
||||
#: meet/settings.py:215
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
||||
|
||||
#: meet/settings.py:224
|
||||
#: meet/settings.py:216
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
#: meet/settings.py:225
|
||||
#: meet/settings.py:217
|
||||
msgid "Dutch"
|
||||
msgstr "Néerlandais"
|
||||
|
||||
#: meet/settings.py:226
|
||||
#: meet/settings.py:218
|
||||
msgid "German"
|
||||
msgstr "Allemand"
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:46+0000\n"
|
||||
"POT-Creation-Date: 2026-03-11 17:31+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -76,11 +76,11 @@ msgstr "%(count)s opname(s) succesvol gemarkeerd als 'Mislukt bij stoppen'."
|
||||
msgid "Skipped %(count)s recording(s) with an ineligible status."
|
||||
msgstr "%(count)s opname(s) met een niet-toegestane status overgeslagen."
|
||||
|
||||
#: core/admin.py:342
|
||||
#: core/admin.py:341
|
||||
msgid "No scopes"
|
||||
msgstr "Geen scopes"
|
||||
|
||||
#: core/admin.py:344
|
||||
#: core/admin.py:343
|
||||
msgid "Scopes"
|
||||
msgstr "Scopes"
|
||||
|
||||
@@ -97,10 +97,6 @@ msgstr ""
|
||||
msgid "This file extension is not allowed."
|
||||
msgstr "Deze bestandsextensie is niet toegestaan."
|
||||
|
||||
#: core/api/serializers.py:533
|
||||
msgid "You have reached the maximum number of files for this type."
|
||||
msgstr "Het maximale aantal bestanden voor dit type is bereikt."
|
||||
|
||||
#: core/models.py:37
|
||||
msgid "Member"
|
||||
msgstr "Lid"
|
||||
@@ -599,18 +595,18 @@ msgstr ""
|
||||
" Als je vragen hebt of hulp nodig hebt, neem dan contact op met ons support "
|
||||
"team via %(support_email)s. "
|
||||
|
||||
#: meet/settings.py:223
|
||||
#: meet/settings.py:215
|
||||
msgid "English"
|
||||
msgstr "Engels"
|
||||
|
||||
#: meet/settings.py:224
|
||||
#: meet/settings.py:216
|
||||
msgid "French"
|
||||
msgstr "Frans"
|
||||
|
||||
#: meet/settings.py:225
|
||||
#: meet/settings.py:217
|
||||
msgid "Dutch"
|
||||
msgstr "Nederlands"
|
||||
|
||||
#: meet/settings.py:226
|
||||
#: meet/settings.py:218
|
||||
msgid "German"
|
||||
msgstr "Duits"
|
||||
|
||||
@@ -176,13 +176,6 @@ class Base(Configuration):
|
||||
environ_prefix=None,
|
||||
)
|
||||
|
||||
FILE_UPLOAD_ENABLED = values.BooleanValue(
|
||||
# False to avoid a breaking change for now
|
||||
default=False,
|
||||
environ_name="FILE_UPLOAD_ENABLED",
|
||||
environ_prefix=None,
|
||||
)
|
||||
|
||||
FILE_UPLOAD_PATH = values.Value(
|
||||
"files", environ_name="FILE_UPLOAD_PATH", environ_prefix=None
|
||||
)
|
||||
@@ -195,7 +188,6 @@ class Base(Configuration):
|
||||
{
|
||||
"background_image": {
|
||||
"max_size": 2 * MB,
|
||||
"max_count_by_user": 10,
|
||||
"allowed_extensions": [".jpeg", ".jpg", ".png"],
|
||||
"allowed_mimetypes": ["image/jpeg", "image/png"],
|
||||
},
|
||||
@@ -452,11 +444,7 @@ class Base(Configuration):
|
||||
CELERY_BROKER_TRANSPORT_OPTIONS = values.DictValue({}, environ_prefix=None)
|
||||
|
||||
# Session
|
||||
SESSION_ENGINE = values.Value(
|
||||
default="django.contrib.sessions.backends.cache",
|
||||
environ_name="SESSION_ENGINE",
|
||||
environ_prefix=None,
|
||||
)
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
SESSION_CACHE_ALIAS = "default"
|
||||
SESSION_COOKIE_AGE = values.PositiveIntegerValue(
|
||||
default=60 * 60 * 12, environ_name="SESSION_COOKIE_AGE", environ_prefix=None
|
||||
@@ -985,7 +973,6 @@ class Test(Base):
|
||||
APPLICATION_JWT_AUDIENCE = "Test inc."
|
||||
|
||||
CELERY_TASK_ALWAYS_EAGER = True
|
||||
FILE_UPLOAD_ENABLED = True
|
||||
|
||||
def __init__(self):
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
+13
-13
@@ -2,12 +2,12 @@
|
||||
# Meet package
|
||||
#
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.10.9,<0.11.0"]
|
||||
build-backend = "uv_build"
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "meet"
|
||||
version = "1.11.0"
|
||||
version = "1.10.0"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -21,7 +21,8 @@ classifiers = [
|
||||
]
|
||||
description = "A simple video and phone conferencing tool, powered by LiveKit"
|
||||
keywords = ["Django", "Contacts", "Templates", "RBAC"]
|
||||
license = "MIT"
|
||||
license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"boto3==1.42.49",
|
||||
@@ -52,7 +53,7 @@ dependencies = [
|
||||
"nested-multipart-parser==1.6.0",
|
||||
"psycopg[binary]==3.3.2",
|
||||
"pydantic==2.12.4",
|
||||
"PyJWT==2.12.0",
|
||||
"PyJWT==2.11.0",
|
||||
"python-frontmatter==1.1.0",
|
||||
"python-magic==0.4.27",
|
||||
"requests==2.32.5",
|
||||
@@ -69,7 +70,7 @@ dependencies = [
|
||||
"Homepage" = "https://github.com/suitenumerique/meet"
|
||||
"Repository" = "https://github.com/suitenumerique/meet"
|
||||
|
||||
[dependency-groups]
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"django-extensions==4.1",
|
||||
"drf-spectacular-sidecar==2026.1.1",
|
||||
@@ -89,13 +90,12 @@ dev = [
|
||||
"types-requests==2.32.4.20260107",
|
||||
]
|
||||
|
||||
[tool.uv.build-backend]
|
||||
module-root = ""
|
||||
source-exclude = [
|
||||
"**/tests/**",
|
||||
"**/test_*.py",
|
||||
"**/tests.py",
|
||||
]
|
||||
[tool.setuptools]
|
||||
packages = { find = { where = ["."], exclude = ["tests"] } }
|
||||
zip-safe = true
|
||||
|
||||
[tool.distutils.bdist_wheel]
|
||||
universal = true
|
||||
|
||||
[tool.ruff]
|
||||
exclude = [
|
||||
|
||||
Generated
-2365
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,14 @@
|
||||
FROM node:20-alpine AS frontend-deps
|
||||
|
||||
USER node
|
||||
|
||||
WORKDIR /home/frontend/
|
||||
|
||||
COPY --chown=node:node ./src/frontend/package.json ./package.json
|
||||
COPY --chown=node:node ./src/frontend/package-lock.json ./package-lock.json
|
||||
COPY ./src/frontend/package.json ./package.json
|
||||
COPY ./src/frontend/package-lock.json ./package-lock.json
|
||||
|
||||
RUN npm ci
|
||||
|
||||
COPY --chown=node:node .dockerignore ./.dockerignore
|
||||
COPY --chown=node:node ./src/frontend/ .
|
||||
COPY .dockerignore ./.dockerignore
|
||||
COPY ./src/frontend/ .
|
||||
|
||||
### ---- Front-end builder image ----
|
||||
FROM frontend-deps AS meet
|
||||
@@ -19,8 +17,6 @@ WORKDIR /home/frontend
|
||||
|
||||
FROM frontend-deps AS meet-dev
|
||||
|
||||
USER node
|
||||
|
||||
WORKDIR /home/frontend
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
Generated
+15
-19
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"version": "1.11.0",
|
||||
"version": "1.10.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "meet",
|
||||
"version": "1.11.0",
|
||||
"version": "1.10.0",
|
||||
"dependencies": {
|
||||
"@fontsource-variable/material-symbols-outlined": "5.2.34",
|
||||
"@fontsource/material-icons-outlined": "5.2.6",
|
||||
@@ -6269,13 +6269,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz",
|
||||
"integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==",
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz",
|
||||
"integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
@@ -7161,11 +7158,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
|
||||
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.3",
|
||||
@@ -7632,9 +7628,9 @@
|
||||
"integrity": "sha512-Rf4YVNYpKjZ6ASAmibcwTNciQ5Co5Ztq6iZPEykHpkoflnD/K5ryE/rHehFsTm4NJj8nKDhbi3eKBWGogmNnkg=="
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.12.8",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.8.tgz",
|
||||
"integrity": "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==",
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.2.tgz",
|
||||
"integrity": "sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -11111,9 +11107,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.24.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
|
||||
"integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
|
||||
"version": "6.23.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
|
||||
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"private": true,
|
||||
"version": "1.11.0",
|
||||
"version": "1.10.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "panda codegen && vite",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 11 KiB |
@@ -15,19 +15,7 @@ export const fetchApi = async <T = Record<string, unknown>>(
|
||||
...options?.headers,
|
||||
},
|
||||
})
|
||||
|
||||
let result: T
|
||||
if (response.status === 204) {
|
||||
result = undefined as T
|
||||
} else {
|
||||
const contentType = response.headers.get('content-type') ?? ''
|
||||
if (!contentType.includes('application/json')) {
|
||||
result = undefined as T
|
||||
} else {
|
||||
result = (await response.json()) as T
|
||||
}
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new ApiError(response.status, result)
|
||||
}
|
||||
|
||||
@@ -5,5 +5,4 @@ export const keys = {
|
||||
requestEntry: 'requestEntry',
|
||||
waitingParticipants: 'waitingParticipants',
|
||||
roomCreationCallback: 'roomCreationCallback',
|
||||
files: 'files',
|
||||
}
|
||||
|
||||
@@ -30,13 +30,6 @@ export interface ApiConfig {
|
||||
expiration_days?: number
|
||||
max_duration?: number
|
||||
}
|
||||
background_image: {
|
||||
upload_is_enabled: boolean
|
||||
max_size: number
|
||||
max_count_by_user: number
|
||||
allowed_extensions: string[]
|
||||
allowed_mimetypes: string[]
|
||||
}
|
||||
subtitle: {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import { fetchApi } from '@/api/fetchApi'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { ApiFileItem } from '@/features/files/api/types.ts'
|
||||
import { keys } from '@/api/queryKeys.ts'
|
||||
import { queryClient } from '@/api/queryClient.ts'
|
||||
|
||||
/**
|
||||
* Upload a file, using XHR so we can report on progress through a handler.
|
||||
*
|
||||
* @param url The URL to PUT the file to.
|
||||
* @param file The file to upload.
|
||||
* @param progressHandler A handler that receives progress updates as a single integer `0 <= x <= 100`.
|
||||
*/
|
||||
export const uploadFile = (
|
||||
url: string,
|
||||
file: File,
|
||||
progressHandler: (progress: number) => void
|
||||
) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.open('PUT', url)
|
||||
xhr.setRequestHeader('X-amz-acl', 'private')
|
||||
xhr.setRequestHeader('Content-Type', file.type)
|
||||
|
||||
xhr.addEventListener('error', reject)
|
||||
xhr.addEventListener('abort', reject)
|
||||
|
||||
xhr.addEventListener('readystatechange', () => {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
// Make sure to always set the progress to 100% when the upload is done.
|
||||
// Because 'progress' event listener is not called when the file size is 0.
|
||||
progressHandler(100)
|
||||
return resolve(true)
|
||||
}
|
||||
reject(new Error(`Failed to perform the upload on ${url}.`))
|
||||
}
|
||||
})
|
||||
|
||||
xhr.upload.addEventListener('progress', (progressEvent) => {
|
||||
if (progressEvent.lengthComputable) {
|
||||
progressHandler(
|
||||
Math.floor((progressEvent.loaded / progressEvent.total) * 100)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
xhr.send(file)
|
||||
})
|
||||
|
||||
/**
|
||||
* Asynchronously creates a new file and uploads it to the server.
|
||||
*
|
||||
* @param {object} params - The parameters for the file creation and upload process.
|
||||
* @param {File} params.file - The file object to be uploaded.
|
||||
* @param {function} params.onProgress - A callback function that receives the upload progress as a number (0 to 100).
|
||||
* @returns {Promise<ApiFileItem>} A promise that resolves when the file has been successfully uploaded and the server process is completed.
|
||||
*/
|
||||
export const createFile = async ({
|
||||
file,
|
||||
onProgress,
|
||||
}: {
|
||||
file: File
|
||||
onProgress: (progress: number) => void
|
||||
}): Promise<ApiFileItem> => {
|
||||
const res = await fetchApi<ApiFileItem>(`/files/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ filename: file.name, type: 'background_image' }),
|
||||
})
|
||||
if (res.upload_state !== 'pending') {
|
||||
throw new Error('State should be pending right after creation')
|
||||
}
|
||||
const policy = res.policy
|
||||
await uploadFile(policy, file, onProgress)
|
||||
const createdFile = await fetchApi<ApiFileItem>(
|
||||
`/files/${res.id}/upload-ended/`,
|
||||
{
|
||||
method: 'POST',
|
||||
}
|
||||
)
|
||||
|
||||
// We invalidate the files query to make sure the new file is immediately available.
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [keys.files],
|
||||
})
|
||||
return createdFile
|
||||
}
|
||||
|
||||
export const useCreateFile = () => {
|
||||
return useMutation({
|
||||
mutationFn: createFile,
|
||||
})
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { fetchApi } from '@/api/fetchApi'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { keys } from '@/api/queryKeys.ts'
|
||||
|
||||
/**
|
||||
* Deletes a file specified by its unique identifier.
|
||||
*
|
||||
* @param {Object} params - The parameters required for deleting the file.
|
||||
* @param {string} params.fileId - The unique identifier of the file to be deleted.
|
||||
* @returns {Promise<void>} A promise that resolves when the file is successfully deleted.
|
||||
*/
|
||||
export const deleteFile = async ({
|
||||
fileId,
|
||||
}: {
|
||||
fileId: string
|
||||
}): Promise<void> => {
|
||||
await fetchApi<void>(`/files/${fileId}/`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteFile = () => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: deleteFile,
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [keys.files],
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import { fetchApi } from '@/api/fetchApi'
|
||||
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
||||
import { keys } from '@/api/queryKeys'
|
||||
import {
|
||||
ApiFileItem,
|
||||
ApiFileType,
|
||||
ApiFileUploadState,
|
||||
} from '@/features/files/api/types.ts'
|
||||
import { useUser } from '@/features/auth'
|
||||
import { useConfig } from '@/api/useConfig.ts'
|
||||
|
||||
type ListFilesResponse = {
|
||||
count: number
|
||||
next: string | null
|
||||
previous: string | null
|
||||
results: ApiFileItem[]
|
||||
}
|
||||
|
||||
type ListFilesFilters = {
|
||||
is_creator_me?: boolean
|
||||
type?: ApiFileType
|
||||
upload_state?: ApiFileUploadState
|
||||
is_deleted?: boolean
|
||||
}
|
||||
|
||||
export type ListFilesParams = {
|
||||
filters?: ListFilesFilters
|
||||
pagination: {
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
}
|
||||
|
||||
export const listMyFiles = async ({
|
||||
filters = {},
|
||||
pagination: { page, pageSize },
|
||||
}: ListFilesParams): Promise<ListFilesResponse> => {
|
||||
const query = new URLSearchParams()
|
||||
query.append('page', page.toString())
|
||||
query.append('page_size', pageSize.toString())
|
||||
if (filters?.is_creator_me ?? true) {
|
||||
query.append('is_creator_me', 'true')
|
||||
}
|
||||
if (filters?.type) {
|
||||
query.append('type', filters.type)
|
||||
}
|
||||
if (filters?.upload_state) {
|
||||
query.append('upload_state', filters.upload_state)
|
||||
}
|
||||
if (typeof filters?.is_deleted === 'boolean') {
|
||||
query.append('is_deleted', filters.is_deleted ? 'true' : 'false')
|
||||
}
|
||||
|
||||
return fetchApi<ListFilesResponse>(`/files?${query.toString()}`, {
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export const useListMyFiles = (params: Parameters<typeof listMyFiles>[0]) => {
|
||||
const { isLoggedIn } = useUser()
|
||||
const { data: appConfig } = useConfig()
|
||||
return useQuery({
|
||||
queryKey: [keys.files, params],
|
||||
queryFn: () => listMyFiles(params),
|
||||
refetchOnMount: 'always',
|
||||
placeholderData: keepPreviousData,
|
||||
enabled:
|
||||
isLoggedIn && appConfig?.background_image?.upload_is_enabled === true,
|
||||
})
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
export type ApiFileCreator = {
|
||||
id: string // UUID
|
||||
full_name: string | null
|
||||
short_name: string | null
|
||||
}
|
||||
|
||||
export type ApiFileType = 'background_image'
|
||||
export type ApiFileUploadState = 'pending' | 'ready'
|
||||
|
||||
export type ApiFileItem = {
|
||||
id: string // UUID
|
||||
created_at: string // ISO datetime string
|
||||
updated_at: string // ISO datetime string
|
||||
title: string
|
||||
type: ApiFileType
|
||||
creator: ApiFileCreator
|
||||
deleted_at: string | null
|
||||
hard_deleted_at: string | null
|
||||
filename: string
|
||||
upload_state: ApiFileUploadState
|
||||
mimetype: string // e.g. "image/png"
|
||||
size: number // file size in bytes
|
||||
description: string | null
|
||||
} & (
|
||||
| {
|
||||
upload_state: 'ready'
|
||||
url: string
|
||||
}
|
||||
| {
|
||||
upload_state: 'pending'
|
||||
policy: string
|
||||
url: null
|
||||
}
|
||||
)
|
||||
@@ -215,8 +215,8 @@ export const Conference = ({
|
||||
audio={userConfig.audioEnabled}
|
||||
video={
|
||||
userConfig.videoEnabled && {
|
||||
processor: BackgroundProcessorFactory.fromProcessorConfig(
|
||||
userConfig.processorConfig
|
||||
processor: BackgroundProcessorFactory.deserializeProcessor(
|
||||
userConfig.processorSerialized
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import { css } from '@/styled-system/css'
|
||||
import { Screen } from '@/layout/Screen'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
createLocalAudioTrack,
|
||||
createLocalVideoTrack,
|
||||
createLocalAudioTrack,
|
||||
LocalAudioTrack,
|
||||
LocalVideoTrack,
|
||||
Track,
|
||||
} from 'livekit-client'
|
||||
import { H } from '@/primitives/H'
|
||||
import { Field } from '@/primitives/Field'
|
||||
import { Button, Dialog, Form, Text } from '@/primitives'
|
||||
import { Button, Dialog, Text, Form } from '@/primitives'
|
||||
import { VStack } from '@/styled-system/jsx'
|
||||
import { Heading } from 'react-aria-components'
|
||||
import { RiImageCircleAiFill } from '@remixicon/react'
|
||||
@@ -44,7 +44,8 @@ const onError = (e: Error) => console.error('ERROR', e)
|
||||
|
||||
const Effects = ({
|
||||
videoTrack,
|
||||
}: Pick<EffectsConfigurationProps, 'videoTrack'>) => {
|
||||
onSubmit,
|
||||
}: Pick<EffectsConfigurationProps, 'videoTrack' | 'onSubmit'>) => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'join.effects' })
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||
const openDialog = () => setIsDialogOpen(true)
|
||||
@@ -80,7 +81,7 @@ const Effects = ({
|
||||
>
|
||||
{t('subTitle')}
|
||||
</Text>
|
||||
<EffectsConfiguration videoTrack={videoTrack} />
|
||||
<EffectsConfiguration videoTrack={videoTrack} onSubmit={onSubmit} />
|
||||
</Dialog>
|
||||
<Button
|
||||
variant="whiteCircle"
|
||||
@@ -110,7 +111,7 @@ export const Join = ({
|
||||
audioDeviceId,
|
||||
audioOutputDeviceId,
|
||||
videoDeviceId,
|
||||
processorConfig,
|
||||
processorSerialized,
|
||||
username,
|
||||
},
|
||||
saveAudioInputEnabled,
|
||||
@@ -119,6 +120,7 @@ export const Join = ({
|
||||
saveAudioInputDeviceId,
|
||||
saveVideoInputDeviceId,
|
||||
saveUsername,
|
||||
saveProcessorSerialized,
|
||||
} = usePersistentUserChoices()
|
||||
|
||||
const initialUserChoices = useRef<LocalUserChoices | null>(null)
|
||||
@@ -130,7 +132,7 @@ export const Join = ({
|
||||
audioDeviceId,
|
||||
audioOutputDeviceId,
|
||||
videoDeviceId,
|
||||
processorConfig,
|
||||
processorSerialized,
|
||||
username,
|
||||
}
|
||||
}
|
||||
@@ -144,8 +146,8 @@ export const Join = ({
|
||||
video: !!initialUserChoices.current &&
|
||||
initialUserChoices.current?.videoEnabled && {
|
||||
deviceId: initialUserChoices.current.videoDeviceId,
|
||||
processor: BackgroundProcessorFactory.fromProcessorConfig(
|
||||
initialUserChoices.current.processorConfig
|
||||
processor: BackgroundProcessorFactory.deserializeProcessor(
|
||||
initialUserChoices.current.processorSerialized
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -184,7 +186,9 @@ export const Join = ({
|
||||
const track = await createLocalVideoTrack({
|
||||
deviceId: { exact: videoDeviceId },
|
||||
processor:
|
||||
BackgroundProcessorFactory.fromProcessorConfig(processorConfig),
|
||||
BackgroundProcessorFactory.deserializeProcessor(
|
||||
processorSerialized
|
||||
),
|
||||
})
|
||||
setDynamicVideoTrack(track)
|
||||
} catch (error) {
|
||||
@@ -203,7 +207,7 @@ export const Join = ({
|
||||
}, [
|
||||
videoEnabled,
|
||||
videoDeviceId,
|
||||
processorConfig,
|
||||
processorSerialized,
|
||||
previewVideoTrack,
|
||||
dynamicVideoTrack,
|
||||
])
|
||||
@@ -686,7 +690,12 @@ export const Join = ({
|
||||
zIndex: '1',
|
||||
})}
|
||||
>
|
||||
<Effects videoTrack={videoTrack} />
|
||||
<Effects
|
||||
videoTrack={videoTrack}
|
||||
onSubmit={(processor) =>
|
||||
saveProcessorSerialized(processor?.serialize())
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,8 +31,6 @@ import { FullScreenShareWarning } from './FullScreenShareWarning'
|
||||
import { ParticipantName } from './ParticipantName'
|
||||
import { getParticipantName } from '@/features/rooms/utils/getParticipantName'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getShortcutDescriptorById } from '@/features/shortcuts/catalog'
|
||||
import { formatShortcutLabel } from '@/features/shortcuts/formatLabels'
|
||||
import { KeyboardShortcutHint } from './KeyboardShortcutHint'
|
||||
|
||||
export function TrackRefContextIfNeeded(
|
||||
@@ -239,13 +237,7 @@ export const ParticipantTile: (
|
||||
)}
|
||||
</ParticipantContextIfNeeded>
|
||||
</TrackRefContextIfNeeded>
|
||||
<KeyboardShortcutHint>
|
||||
{t('toolbarHint', {
|
||||
shortcut: formatShortcutLabel(
|
||||
getShortcutDescriptorById('open-shortcuts')?.shortcut
|
||||
),
|
||||
})}
|
||||
</KeyboardShortcutHint>
|
||||
<KeyboardShortcutHint>{t('toolbarHint')}</KeyboardShortcutHint>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -149,12 +149,11 @@ export const SidePanel = () => {
|
||||
activeSubPanelId,
|
||||
} = useSidePanel()
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'sidePanel' })
|
||||
const title = t(`heading.${activeSubPanelId || activePanelId}`)
|
||||
|
||||
return (
|
||||
<StyledSidePanel
|
||||
title={title}
|
||||
ariaLabel={t('ariaLabel', { title })}
|
||||
title={t(`heading.${activeSubPanelId || activePanelId}`)}
|
||||
ariaLabel={t('ariaLabel')}
|
||||
onClose={() => {
|
||||
layoutStore.activePanelId = null
|
||||
layoutStore.activeSubPanelId = null
|
||||
|
||||
@@ -65,7 +65,7 @@ const ToolButton = ({
|
||||
<div>
|
||||
<Text
|
||||
margin={false}
|
||||
as="h2"
|
||||
as="h3"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
gap: 0.25,
|
||||
|
||||
+27
-16
@@ -11,7 +11,11 @@ import {
|
||||
TIMEOUT_TICK,
|
||||
timerWorkerScript,
|
||||
} from './TimerWorker'
|
||||
import { BackgroundProcessorInterface, ProcessorConfig, ProcessorType } from '.'
|
||||
import {
|
||||
BackgroundProcessorInterface,
|
||||
BackgroundOptions,
|
||||
ProcessorType,
|
||||
} from '.'
|
||||
|
||||
const PROCESSING_WIDTH = 256
|
||||
const PROCESSING_HEIGHT = 144
|
||||
@@ -29,7 +33,7 @@ const DEFAULT_BLUR = '10'
|
||||
* MediaStreamTrackProcessor.
|
||||
*/
|
||||
export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
||||
options: ProcessorConfig
|
||||
options: BackgroundOptions
|
||||
name: string
|
||||
processedTrack?: MediaStreamTrack | undefined
|
||||
|
||||
@@ -60,10 +64,15 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
||||
type: ProcessorType
|
||||
virtualBackgroundImage?: HTMLImageElement
|
||||
|
||||
constructor(opts: ProcessorConfig) {
|
||||
constructor(opts: BackgroundOptions) {
|
||||
this.name = 'blur'
|
||||
this.options = opts
|
||||
this.type = opts.type
|
||||
|
||||
if (this.options.blurRadius) {
|
||||
this.type = ProcessorType.BLUR
|
||||
} else {
|
||||
this.type = ProcessorType.VIRTUAL
|
||||
}
|
||||
}
|
||||
|
||||
static get isSupported() {
|
||||
@@ -98,12 +107,6 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
||||
}
|
||||
|
||||
_initVirtualBackgroundImage() {
|
||||
if (this.options.type !== 'virtual') {
|
||||
throw new Error(
|
||||
'Virtual background is only supported for virtual background'
|
||||
)
|
||||
}
|
||||
|
||||
const needsUpdate =
|
||||
this.options.imagePath &&
|
||||
this.virtualBackgroundImage &&
|
||||
@@ -115,7 +118,7 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async update(opts: ProcessorConfig): Promise<void> {
|
||||
async update(opts: BackgroundOptions): Promise<void> {
|
||||
this.options = opts
|
||||
this._initVirtualBackgroundImage()
|
||||
}
|
||||
@@ -184,7 +187,7 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
||||
0,
|
||||
0,
|
||||
PROCESSING_WIDTH,
|
||||
PROCESSING_HEIGHT
|
||||
PROCESSING_WIDTH
|
||||
)
|
||||
}
|
||||
|
||||
@@ -209,9 +212,6 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
||||
* TODO: future improvement with WebGL.
|
||||
*/
|
||||
async blur() {
|
||||
if (this.options.type !== 'blur') {
|
||||
throw new Error('Blurring is only supported for blur background')
|
||||
}
|
||||
const mask = this.imageSegmenterResult!.categoryMask!.getAsUint8Array()
|
||||
for (let i = 0; i < mask.length; ++i) {
|
||||
this.segmentationMask!.data[i * 4 + 3] = 255 - mask[i]
|
||||
@@ -293,7 +293,7 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
||||
await this.sizeSource()
|
||||
await this.segment()
|
||||
|
||||
if (this.options.type === 'blur') {
|
||||
if (this.options.blurRadius) {
|
||||
await this.blur()
|
||||
} else {
|
||||
await this.drawVirtualBackground()
|
||||
@@ -354,4 +354,15 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
||||
this.timerWorker?.terminate()
|
||||
this.imageSegmenter?.close()
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new BackgroundCustomProcessor(this.options)
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return {
|
||||
type: this.type,
|
||||
options: this.options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,4 +332,15 @@ export class FaceLandmarksProcessor implements TrackProcessor<Track.Kind> {
|
||||
this.timerWorker?.terminate()
|
||||
this.faceLandmarker?.close()
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new FaceLandmarksProcessor(this.options)
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return {
|
||||
type: this.type,
|
||||
options: this.options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-15
@@ -1,23 +1,28 @@
|
||||
import { ProcessorOptions, Track } from 'livekit-client'
|
||||
import {
|
||||
BackgroundBlur,
|
||||
BackgroundTransformer,
|
||||
ProcessorWrapper,
|
||||
VirtualBackground,
|
||||
} from '@livekit/track-processors'
|
||||
import { ProcessorConfig, BackgroundProcessorInterface, ProcessorType } from '.'
|
||||
import {
|
||||
BackgroundOptions,
|
||||
BackgroundProcessorInterface,
|
||||
ProcessorType,
|
||||
} from '.'
|
||||
|
||||
export class UnifiedBackgroundTrackProcessor implements BackgroundProcessorInterface {
|
||||
processor: ProcessorWrapper<{ imagePath?: string; blurRadius?: number }>
|
||||
opts: ProcessorConfig
|
||||
processor: ProcessorWrapper<BackgroundOptions>
|
||||
opts: BackgroundOptions
|
||||
processorType: ProcessorType
|
||||
|
||||
constructor(opts: ProcessorConfig) {
|
||||
constructor(opts: BackgroundOptions) {
|
||||
this.opts = opts
|
||||
|
||||
if (opts.type === 'virtual') {
|
||||
if (opts.imagePath) {
|
||||
this.processorType = ProcessorType.VIRTUAL
|
||||
this.processor = VirtualBackground(opts.imagePath)
|
||||
} else if (opts.type === 'blur') {
|
||||
} else if (opts.blurRadius !== undefined) {
|
||||
this.processorType = ProcessorType.BLUR
|
||||
this.processor = BackgroundBlur(opts.blurRadius)
|
||||
} else {
|
||||
@@ -39,23 +44,24 @@ export class UnifiedBackgroundTrackProcessor implements BackgroundProcessorInter
|
||||
return this.processor.destroy()
|
||||
}
|
||||
|
||||
async update(opts: ProcessorConfig): Promise<void> {
|
||||
this.opts = opts
|
||||
|
||||
const newProcessorType =
|
||||
opts.type === 'virtual' ? ProcessorType.VIRTUAL : ProcessorType.BLUR
|
||||
async update(opts: BackgroundOptions): Promise<void> {
|
||||
const newProcessorType = opts.imagePath
|
||||
? ProcessorType.VIRTUAL
|
||||
: ProcessorType.BLUR
|
||||
|
||||
let processedOpts = opts
|
||||
if (newProcessorType !== this.processorType) {
|
||||
this.processorType = newProcessorType
|
||||
if (newProcessorType === ProcessorType.VIRTUAL) {
|
||||
this.processor.name = 'virtual-background'
|
||||
processedOpts = { ...opts, blurRadius: undefined }
|
||||
} else {
|
||||
this.processor.name = 'background-blur'
|
||||
processedOpts = { ...opts, imagePath: undefined }
|
||||
}
|
||||
}
|
||||
await this.processor.updateTransformerOptions(
|
||||
opts as { imagePath?: string; blurRadius?: number }
|
||||
)
|
||||
await this.processor.updateTransformerOptions(processedOpts)
|
||||
this.opts = processedOpts
|
||||
}
|
||||
|
||||
get name() {
|
||||
@@ -67,6 +73,17 @@ export class UnifiedBackgroundTrackProcessor implements BackgroundProcessorInter
|
||||
}
|
||||
|
||||
get options() {
|
||||
return this.opts
|
||||
return (this.processor.transformer as BackgroundTransformer).options
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new UnifiedBackgroundTrackProcessor(this.options || this.opts)
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return {
|
||||
type: this.processorType,
|
||||
options: this.options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,23 @@ import { ProcessorWrapper } from '@livekit/track-processors'
|
||||
import { Track, TrackProcessor } from 'livekit-client'
|
||||
import { BackgroundCustomProcessor } from './BackgroundCustomProcessor'
|
||||
import { UnifiedBackgroundTrackProcessor } from './UnifiedBackgroundTrackProcessor'
|
||||
import { FaceLandmarksOptions } from './FaceLandmarksProcessor'
|
||||
|
||||
export type BackgroundOptions = {
|
||||
blurRadius?: number
|
||||
imagePath?: string
|
||||
}
|
||||
|
||||
export interface ProcessorSerialized {
|
||||
type: ProcessorType
|
||||
options: BackgroundOptions
|
||||
}
|
||||
|
||||
export interface BackgroundProcessorInterface extends TrackProcessor<Track.Kind> {
|
||||
update(opts: BackgroundOptions): Promise<void>
|
||||
options: BackgroundOptions
|
||||
clone(): BackgroundProcessorInterface
|
||||
serialize(): ProcessorSerialized
|
||||
}
|
||||
|
||||
export enum ProcessorType {
|
||||
BLUR = 'blur',
|
||||
@@ -10,16 +26,6 @@ export enum ProcessorType {
|
||||
FACE_LANDMARKS = 'faceLandmarks',
|
||||
}
|
||||
|
||||
export type ProcessorConfig =
|
||||
| { type: ProcessorType.BLUR; blurRadius: number }
|
||||
| { type: ProcessorType.VIRTUAL; imagePath: string; fileId?: string }
|
||||
| ({ type: ProcessorType.FACE_LANDMARKS } & FaceLandmarksOptions)
|
||||
|
||||
export interface BackgroundProcessorInterface extends TrackProcessor<Track.Kind> {
|
||||
update(opts: ProcessorConfig): Promise<void>
|
||||
options: ProcessorConfig
|
||||
}
|
||||
|
||||
export class BackgroundProcessorFactory {
|
||||
static hasModernApiSupport() {
|
||||
return ProcessorWrapper.hasModernApiSupport
|
||||
@@ -30,27 +36,28 @@ export class BackgroundProcessorFactory {
|
||||
}
|
||||
|
||||
static getProcessor(
|
||||
config: ProcessorConfig
|
||||
type: ProcessorType,
|
||||
opts: BackgroundOptions
|
||||
): BackgroundProcessorInterface | undefined {
|
||||
const isBlur = config.type === ProcessorType.BLUR
|
||||
const isVirtual = config.type === ProcessorType.VIRTUAL
|
||||
const isBlur = type === ProcessorType.BLUR
|
||||
const isVirtual = type === ProcessorType.VIRTUAL
|
||||
|
||||
if (!isBlur && !isVirtual) return undefined
|
||||
|
||||
if (ProcessorWrapper.isSupported) {
|
||||
return new UnifiedBackgroundTrackProcessor(config)
|
||||
return new UnifiedBackgroundTrackProcessor(opts)
|
||||
}
|
||||
|
||||
if (BackgroundCustomProcessor.isSupported) {
|
||||
return new BackgroundCustomProcessor(config)
|
||||
return new BackgroundCustomProcessor(opts)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
static fromProcessorConfig(data?: ProcessorConfig) {
|
||||
if (data) {
|
||||
return BackgroundProcessorFactory.getProcessor(data)
|
||||
static deserializeProcessor(data?: ProcessorSerialized) {
|
||||
if (data?.type) {
|
||||
return BackgroundProcessorFactory.getProcessor(data?.type, data?.options)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -69,10 +69,10 @@ const SelectDevicePermissions = <T extends string | number>({
|
||||
iconComponent={iconComponent}
|
||||
placeholder={items.length === 0 ? t('loading') : t('select')}
|
||||
selectedKey={selectedKey}
|
||||
onSelectionChange={async (key) => {
|
||||
onSelectionChange={(key) => {
|
||||
if (key === selectedKey) return
|
||||
await setActiveMediaDevice(key as string)
|
||||
onSubmit?.(key as string)
|
||||
setActiveMediaDevice(key as string)
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
+2
-2
@@ -82,8 +82,8 @@ export const VideoDeviceControl = ({
|
||||
*
|
||||
* See https://github.com/numerique-gouv/meet/pull/309#issuecomment-2622404121
|
||||
*/
|
||||
const processor = BackgroundProcessorFactory.fromProcessorConfig(
|
||||
userChoices.processorConfig
|
||||
const processor = BackgroundProcessorFactory.deserializeProcessor(
|
||||
userChoices.processorSerialized
|
||||
)
|
||||
|
||||
const toggle = trackProps.toggle as (
|
||||
|
||||
@@ -2,12 +2,14 @@ import { useLocalParticipant } from '@livekit/components-react'
|
||||
import { LocalVideoTrack } from 'livekit-client'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { EffectsConfiguration } from './EffectsConfiguration'
|
||||
import { usePersistentUserChoices } from '../../hooks/usePersistentUserChoices'
|
||||
import { useCanPublishTrack } from '@/features/rooms/livekit/hooks/useCanPublishTrack'
|
||||
import { TrackSource } from '@livekit/protocol'
|
||||
|
||||
export const Effects = () => {
|
||||
const { cameraTrack } = useLocalParticipant()
|
||||
const localCameraTrack = cameraTrack?.track as LocalVideoTrack
|
||||
const { saveProcessorSerialized } = usePersistentUserChoices()
|
||||
|
||||
const canPublishCamera = useCanPublishTrack(TrackSource.CAMERA)
|
||||
|
||||
@@ -22,6 +24,9 @@ export const Effects = () => {
|
||||
isDisabled={!canPublishCamera}
|
||||
videoTrack={localCameraTrack}
|
||||
layout="vertical"
|
||||
onSubmit={(processor) =>
|
||||
saveProcessorSerialized(processor?.serialize())
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
+240
-694
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ export const FunnyEffects = ({
|
||||
showFrench: false,
|
||||
}
|
||||
}
|
||||
return { ...processor.options }
|
||||
return processor.serialize().options
|
||||
}
|
||||
|
||||
const options = getOptions()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { userChoicesStore } from '@/stores/userChoices'
|
||||
import type { VideoResolution } from '@/stores/userChoices'
|
||||
import { ProcessorConfig } from '@/features/rooms/livekit/components/blur'
|
||||
import { ProcessorSerialized } from '@/features/rooms/livekit/components/blur'
|
||||
import type { VideoQuality } from 'livekit-client'
|
||||
|
||||
export function usePersistentUserChoices() {
|
||||
@@ -36,8 +36,10 @@ export function usePersistentUserChoices() {
|
||||
saveNoiseReductionEnabled: (enabled: boolean) => {
|
||||
userChoicesStore.noiseReductionEnabled = enabled
|
||||
},
|
||||
saveProcessorConfig: (processorConfig: ProcessorConfig | undefined) => {
|
||||
userChoicesStore.processorConfig = processorConfig
|
||||
saveProcessorSerialized: (
|
||||
processorSerialized: ProcessorSerialized | undefined
|
||||
) => {
|
||||
userChoicesStore.processorSerialized = processorSerialized
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const VideoTab = ({ id }: VideoTabProps) => {
|
||||
const {
|
||||
userChoices: {
|
||||
videoDeviceId,
|
||||
processorConfig,
|
||||
processorSerialized,
|
||||
videoPublishResolution,
|
||||
videoSubscribeQuality,
|
||||
},
|
||||
@@ -78,7 +78,7 @@ export const VideoTab = ({ id }: VideoTabProps) => {
|
||||
resolution: VideoPresets[key].resolution,
|
||||
deviceId: { exact: videoDeviceId },
|
||||
processor:
|
||||
BackgroundProcessorFactory.fromProcessorConfig(processorConfig),
|
||||
BackgroundProcessorFactory.deserializeProcessor(processorSerialized),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,8 @@ import { useSnapshot } from 'valtio'
|
||||
import {
|
||||
accessibilityStore,
|
||||
type CaptionTextSize,
|
||||
type CaptionColor,
|
||||
CAPTION_TEXT_SIZE_OPTIONS,
|
||||
CAPTION_COLOR_OPTIONS,
|
||||
} from '@/stores/accessibility'
|
||||
import { useAreSubtitlesAvailable } from '../hooks/useAreSubtitlesAvailable'
|
||||
|
||||
export const CaptionsSettings = () => {
|
||||
const { t } = useTranslation('settings', {
|
||||
@@ -27,27 +24,6 @@ export const CaptionsSettings = () => {
|
||||
[t]
|
||||
)
|
||||
|
||||
const captionFontColorItems = useMemo(
|
||||
() =>
|
||||
CAPTION_COLOR_OPTIONS.map((color) => ({
|
||||
value: color,
|
||||
label: t(`fontColor.options.${color}`),
|
||||
})),
|
||||
[t]
|
||||
)
|
||||
|
||||
const captionBackgroundColorItems = useMemo(
|
||||
() =>
|
||||
CAPTION_COLOR_OPTIONS.map((color) => ({
|
||||
value: color,
|
||||
label: t(`backgroundColor.options.${color}`),
|
||||
})),
|
||||
[t]
|
||||
)
|
||||
|
||||
const areSubtitlesAvailable = useAreSubtitlesAvailable()
|
||||
if (!areSubtitlesAvailable) return null
|
||||
|
||||
return (
|
||||
<li>
|
||||
<H
|
||||
@@ -58,44 +34,16 @@ export const CaptionsSettings = () => {
|
||||
>
|
||||
{t('heading')}
|
||||
</H>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '0.75rem',
|
||||
})}
|
||||
>
|
||||
<Field
|
||||
type="select"
|
||||
label={t('textSize.label')}
|
||||
items={captionTextSizeItems}
|
||||
selectedKey={snap.captionTextSize}
|
||||
onSelectionChange={(key) => {
|
||||
accessibilityStore.captionTextSize = key as CaptionTextSize
|
||||
}}
|
||||
wrapperProps={{ noMargin: true, fullWidth: true }}
|
||||
/>
|
||||
<Field
|
||||
type="select"
|
||||
label={t('fontColor.label')}
|
||||
items={captionFontColorItems}
|
||||
selectedKey={snap.captionFontColor}
|
||||
onSelectionChange={(key) => {
|
||||
accessibilityStore.captionFontColor = key as CaptionColor
|
||||
}}
|
||||
wrapperProps={{ noMargin: true, fullWidth: true }}
|
||||
/>
|
||||
<Field
|
||||
type="select"
|
||||
label={t('backgroundColor.label')}
|
||||
items={captionBackgroundColorItems}
|
||||
selectedKey={snap.captionBackgroundColor}
|
||||
onSelectionChange={(key) => {
|
||||
accessibilityStore.captionBackgroundColor = key as CaptionColor
|
||||
}}
|
||||
wrapperProps={{ noMargin: true, fullWidth: true }}
|
||||
/>
|
||||
</div>
|
||||
<Field
|
||||
type="select"
|
||||
label={t('textSize.label')}
|
||||
items={captionTextSizeItems}
|
||||
selectedKey={snap.captionTextSize}
|
||||
onSelectionChange={(key) => {
|
||||
accessibilityStore.captionTextSize = key as CaptionTextSize
|
||||
}}
|
||||
wrapperProps={{ noMargin: true, fullWidth: true }}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ import { useSnapshot } from 'valtio'
|
||||
import {
|
||||
accessibilityStore,
|
||||
CAPTION_TEXT_SIZE_OPTIONS,
|
||||
CAPTION_FONT_COLOR_VALUES,
|
||||
CAPTION_BACKGROUND_COLOR_VALUES,
|
||||
type CaptionTextSize,
|
||||
} from '@/stores/accessibility'
|
||||
|
||||
@@ -94,14 +92,10 @@ const useTranscriptionState = () => {
|
||||
}
|
||||
|
||||
const Transcription = ({ row }: { row: TranscriptionRow }) => {
|
||||
const { captionTextSize, captionFontColor, captionBackgroundColor } =
|
||||
useSnapshot(accessibilityStore)
|
||||
const { captionTextSize } = useSnapshot(accessibilityStore)
|
||||
const participantColor = getParticipantColor(row.participant)
|
||||
const participantName = getParticipantName(row.participant)
|
||||
const { fontSize, lineHeight } = CAPTION_FONT_SIZES[captionTextSize]
|
||||
const fontColor = CAPTION_FONT_COLOR_VALUES[captionFontColor]
|
||||
const backgroundColor =
|
||||
CAPTION_BACKGROUND_COLOR_VALUES[captionBackgroundColor]
|
||||
|
||||
const getDisplayText = (row: TranscriptionRow): string => {
|
||||
return row.segments
|
||||
@@ -134,9 +128,9 @@ const Transcription = ({ row }: { row: TranscriptionRow }) => {
|
||||
/>
|
||||
<div
|
||||
className={css({
|
||||
color: 'white',
|
||||
width: '100%',
|
||||
})}
|
||||
style={{ color: fontColor }}
|
||||
>
|
||||
<Text variant="h3" margin={false}>
|
||||
{participantName}
|
||||
@@ -144,10 +138,8 @@ const Transcription = ({ row }: { row: TranscriptionRow }) => {
|
||||
<p
|
||||
className={css({
|
||||
fontWeight: '400',
|
||||
borderRadius: '4px',
|
||||
padding: '0.125rem 0.25rem',
|
||||
})}
|
||||
style={{ fontSize, lineHeight, backgroundColor }}
|
||||
style={{ fontSize, lineHeight }}
|
||||
>
|
||||
{displayText}
|
||||
</p>
|
||||
|
||||
@@ -273,40 +273,17 @@
|
||||
"title": "Virtueller Hintergrund",
|
||||
"selectedLabel": "Hintergrund angewendet:",
|
||||
"apply": "Ersetze deinen Hintergrund:",
|
||||
"personal": {
|
||||
"title": "Meine Hintergründe",
|
||||
"selectFileTooltip": "Wähle ein Bild aus, das als persönlicher Hintergrund verwendet werden soll",
|
||||
"notLoggedInWarning": "Du bist nicht angemeldet, der persönliche Hintergrund wird nicht von einem Meeting zum anderen gespeichert.",
|
||||
"warningUploadDisabled": "Persönliche Hintergründe werden derzeit nicht von einem Meeting zum anderen gespeichert.",
|
||||
"uploadLimitReached": "Du kannst keine weiteren persönlichen Hintergründe hinzufügen.",
|
||||
"uploadInProgress": "Datei wird hochgeladen…",
|
||||
"errors": {
|
||||
"close": "Schließen",
|
||||
"file_too_large": {
|
||||
"title": "Datei zu groß",
|
||||
"description": "Die Datei ist zu groß. Bitte wähle eine Datei mit weniger als {{maxSize, number}} MB."
|
||||
},
|
||||
"invalid_file_type": {
|
||||
"title": "Ungültiger Dateityp",
|
||||
"description": "Der Dateityp wird nicht unterstützt. Bitte wähle eine {{allowedExtension}}-Datei."
|
||||
}
|
||||
}
|
||||
},
|
||||
"presets": {
|
||||
"title": "Vorschläge",
|
||||
"descriptions": {
|
||||
"0": "Gerilltes Holzmöbel",
|
||||
"1": "Besprechungsraum",
|
||||
"2": "Loft mit schwarzer Glaswand",
|
||||
"3": "Esszimmer",
|
||||
"4": "Holzregale",
|
||||
"5": "Holztreppe",
|
||||
"6": "Graue Bibliothek",
|
||||
"7": "Kaffeetheke"
|
||||
}
|
||||
"descriptions": {
|
||||
"0": "Gerilltes Holzmöbel",
|
||||
"1": "Besprechungsraum",
|
||||
"2": "Loft mit schwarzer Glaswand",
|
||||
"3": "Esszimmer",
|
||||
"4": "Holzregale",
|
||||
"5": "Holztreppe",
|
||||
"6": "Graue Bibliothek",
|
||||
"7": "Kaffeetheke"
|
||||
}
|
||||
},
|
||||
|
||||
"faceLandmarks": {
|
||||
"title": "Visuelle Effekte",
|
||||
"glasses": {
|
||||
@@ -320,7 +297,7 @@
|
||||
}
|
||||
},
|
||||
"sidePanel": {
|
||||
"ariaLabel": "Seitenleiste - {{title}}",
|
||||
"ariaLabel": "Seitenleiste",
|
||||
"backToTools": "Zurück zu den Meeting-Tools",
|
||||
"heading": {
|
||||
"participants": "Teilnehmende",
|
||||
@@ -355,11 +332,11 @@
|
||||
"tools": {
|
||||
"transcript": {
|
||||
"title": "Transkribieren",
|
||||
"body": "Wandelt Meetings in Text um."
|
||||
"body": "Das Gespräch aufzeichnen."
|
||||
},
|
||||
"screenRecording": {
|
||||
"title": "Aufnehmen",
|
||||
"body": "Speichert Meetings als Video."
|
||||
"title": "Aufzeichnen",
|
||||
"body": "Das Meeting aufzeichnen."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -620,7 +597,7 @@
|
||||
},
|
||||
"participantTileFocus": {
|
||||
"containerLabel": "Optionen für {{name}}",
|
||||
"toolbarHint": "{{shortcut}}: Direkt auf die Tastenkürzel zugreifen.",
|
||||
"toolbarHint": "Ctrl+Shift+/: Direkt auf die Tastenkürzel zugreifen.",
|
||||
"pin": {
|
||||
"enable": "Anheften",
|
||||
"disable": "Lösen"
|
||||
|
||||
@@ -126,34 +126,6 @@
|
||||
"medium": "Mittel",
|
||||
"large": "Groß"
|
||||
}
|
||||
},
|
||||
"fontColor": {
|
||||
"label": "Schriftfarbe",
|
||||
"options": {
|
||||
"default": "Standard",
|
||||
"white": "Weiß",
|
||||
"black": "Schwarz",
|
||||
"blue": "Blau",
|
||||
"green": "Grün",
|
||||
"red": "Rot",
|
||||
"yellow": "Gelb",
|
||||
"cyan": "Cyan",
|
||||
"magenta": "Magenta"
|
||||
}
|
||||
},
|
||||
"backgroundColor": {
|
||||
"label": "Hintergrundfarbe",
|
||||
"options": {
|
||||
"default": "Standard",
|
||||
"white": "Weiß",
|
||||
"black": "Schwarz",
|
||||
"blue": "Blau",
|
||||
"green": "Grün",
|
||||
"red": "Rot",
|
||||
"yellow": "Gelb",
|
||||
"cyan": "Cyan",
|
||||
"magenta": "Magenta"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -270,40 +270,18 @@
|
||||
}
|
||||
},
|
||||
"virtual": {
|
||||
"title": "Virtual backgrounds",
|
||||
"title": "Virtual background",
|
||||
"selectedLabel": "Background applied:",
|
||||
"apply": "Replace your background:",
|
||||
"personal": {
|
||||
"title": "My backgrounds",
|
||||
"selectFileTooltip": "Select an image file to use as a personal background",
|
||||
"notLoggedInWarning": "You are not logged-in, personal backgrounds won't be saved from one meeting to the other.",
|
||||
"warningUploadDisabled": "Personal backgrounds are currently not saved from one meeting to the other.",
|
||||
"uploadLimitReached": "You cannot upload more personal backgrounds.",
|
||||
"uploadInProgress": "Image is being uploaded…",
|
||||
"errors": {
|
||||
"close": "Close",
|
||||
"file_too_large": {
|
||||
"title": "File too large",
|
||||
"description": "The file is too large. Please choose a file smaller than {{maxSize, number}} MB."
|
||||
},
|
||||
"invalid_file_type": {
|
||||
"title": "Invalid file type",
|
||||
"description": "The file type is not supported. Please choose a {{allowedExtension, list(type: 'disjunction')}} file."
|
||||
}
|
||||
}
|
||||
},
|
||||
"presets": {
|
||||
"title": "Suggestions",
|
||||
"descriptions": {
|
||||
"0": "Fluted wooden furniture",
|
||||
"1": "Meeting room",
|
||||
"2": "Loft with black glass partition",
|
||||
"3": "Dining room",
|
||||
"4": "Wooden shelves",
|
||||
"5": "Wooden staircase",
|
||||
"6": "Gray library",
|
||||
"7": "Coffee counter"
|
||||
}
|
||||
"descriptions": {
|
||||
"0": "Fluted wooden furniture",
|
||||
"1": "Meeting room",
|
||||
"2": "Loft with black glass partition",
|
||||
"3": "Dining room",
|
||||
"4": "Wooden shelves",
|
||||
"5": "Wooden staircase",
|
||||
"6": "Gray library",
|
||||
"7": "Coffee counter"
|
||||
}
|
||||
},
|
||||
"faceLandmarks": {
|
||||
@@ -319,7 +297,7 @@
|
||||
}
|
||||
},
|
||||
"sidePanel": {
|
||||
"ariaLabel": "Sidepanel - {{title}}",
|
||||
"ariaLabel": "Sidepanel",
|
||||
"backToTools": "Back to meeting tools",
|
||||
"heading": {
|
||||
"participants": "Participants",
|
||||
@@ -354,11 +332,11 @@
|
||||
"tools": {
|
||||
"transcript": {
|
||||
"title": "Transcribe",
|
||||
"body": "Turn meetings into text."
|
||||
"body": "Record the conversation."
|
||||
},
|
||||
"screenRecording": {
|
||||
"title": "Record",
|
||||
"body": "Save meetings as video."
|
||||
"body": "Record the meeting."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -619,7 +597,7 @@
|
||||
},
|
||||
"participantTileFocus": {
|
||||
"containerLabel": "Options for {{name}}",
|
||||
"toolbarHint": "{{shortcut}}: access shortcuts directly.",
|
||||
"toolbarHint": "Ctrl+Shift+/: access shortcuts directly.",
|
||||
"pin": {
|
||||
"enable": "Pin",
|
||||
"disable": "Unpin"
|
||||
|
||||
@@ -126,34 +126,6 @@
|
||||
"medium": "Medium",
|
||||
"large": "Large"
|
||||
}
|
||||
},
|
||||
"fontColor": {
|
||||
"label": "Font color",
|
||||
"options": {
|
||||
"default": "Default",
|
||||
"white": "White",
|
||||
"black": "Black",
|
||||
"blue": "Blue",
|
||||
"green": "Green",
|
||||
"red": "Red",
|
||||
"yellow": "Yellow",
|
||||
"cyan": "Cyan",
|
||||
"magenta": "Magenta"
|
||||
}
|
||||
},
|
||||
"backgroundColor": {
|
||||
"label": "Background color",
|
||||
"options": {
|
||||
"default": "Default",
|
||||
"white": "White",
|
||||
"black": "Black",
|
||||
"blue": "Blue",
|
||||
"green": "Green",
|
||||
"red": "Red",
|
||||
"yellow": "Yellow",
|
||||
"cyan": "Cyan",
|
||||
"magenta": "Magenta"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -270,40 +270,18 @@
|
||||
}
|
||||
},
|
||||
"virtual": {
|
||||
"title": "Arrière-plans virtuels",
|
||||
"title": "Arrière-plan virtuel",
|
||||
"selectedLabel": "Arrière-plan appliqué :",
|
||||
"apply": "Remplacer votre arrière plan :",
|
||||
"personal": {
|
||||
"title": "Mes arrière-plans",
|
||||
"selectFileTooltip": "Sélectionnez une image à utiliser comme arrière-plan",
|
||||
"notLoggedInWarning": "Vous n'êtes pas connecté, l'arrière-plan personnel ne sera pas sauvegardé d'une réunion à l'autre.",
|
||||
"warningUploadDisabled": "Les arrière-plans personnels ne sont actuellement pas sauvegardés d'une réunion à l'autre.",
|
||||
"uploadLimitReached": "Vous ne pouvez pas ajouter plus d'arrière-plans personnels.",
|
||||
"uploadInProgress": "Image en cours d'envoi…",
|
||||
"errors": {
|
||||
"close": "Fermer",
|
||||
"file_too_large": {
|
||||
"title": "Fichier trop volumineux",
|
||||
"description": "Le fichier est trop volumineux. Veuillez choisir un fichier de moins de {{maxSize, number}} Mo."
|
||||
},
|
||||
"invalid_file_type": {
|
||||
"title": "Type de fichier non valide",
|
||||
"description": "Le type de fichier n'est pas pris en charge. Veuillez choisir un fichier {{allowedExtension}}."
|
||||
}
|
||||
}
|
||||
},
|
||||
"presets": {
|
||||
"title": "Suggestions",
|
||||
"descriptions": {
|
||||
"0": "Meuble cannelé en bois",
|
||||
"1": "Salle de réunion",
|
||||
"2": "Loft avec verrière noire",
|
||||
"3": "Salle à manger",
|
||||
"4": "Étagères en bois",
|
||||
"5": "Escalier en bois",
|
||||
"6": "Bibliothèque grise",
|
||||
"7": "Comptoir de café"
|
||||
}
|
||||
"descriptions": {
|
||||
"0": "Meuble cannelé en bois",
|
||||
"1": "Salle de réunion",
|
||||
"2": "Loft avec verrière noire",
|
||||
"3": "Salle à manger",
|
||||
"4": "Étagères en bois",
|
||||
"5": "Escalier en bois",
|
||||
"6": "Bibliothèque grise",
|
||||
"7": "Comptoir de café"
|
||||
}
|
||||
},
|
||||
"faceLandmarks": {
|
||||
@@ -319,7 +297,7 @@
|
||||
}
|
||||
},
|
||||
"sidePanel": {
|
||||
"ariaLabel": "Panneau latéral - {{title}}",
|
||||
"ariaLabel": "Panneau latéral",
|
||||
"backToTools": "Retour aux outils de réunion",
|
||||
"heading": {
|
||||
"participants": "Participants",
|
||||
@@ -354,11 +332,11 @@
|
||||
"tools": {
|
||||
"transcript": {
|
||||
"title": "Transcrire",
|
||||
"body": "Transcrire la réunion."
|
||||
"body": "Enregistrer la conversation."
|
||||
},
|
||||
"screenRecording": {
|
||||
"title": "Enregistrer",
|
||||
"body": "Enregistrer la réunion en vidéo."
|
||||
"body": "Enregistrer la réunion."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -619,7 +597,7 @@
|
||||
},
|
||||
"participantTileFocus": {
|
||||
"containerLabel": "Options pour {{name}}",
|
||||
"toolbarHint": "{{shortcut}} : accéder directement aux raccourcis.",
|
||||
"toolbarHint": "Ctrl+Shift+/ : accéder directement aux raccourcis.",
|
||||
"pin": {
|
||||
"enable": "Épingler",
|
||||
"disable": "Annuler l'épinglage"
|
||||
|
||||
@@ -126,34 +126,6 @@
|
||||
"medium": "Moyen",
|
||||
"large": "Grand"
|
||||
}
|
||||
},
|
||||
"fontColor": {
|
||||
"label": "Couleur du texte",
|
||||
"options": {
|
||||
"default": "Par défaut",
|
||||
"white": "Blanc",
|
||||
"black": "Noir",
|
||||
"blue": "Bleu",
|
||||
"green": "Vert",
|
||||
"red": "Rouge",
|
||||
"yellow": "Jaune",
|
||||
"cyan": "Cyan",
|
||||
"magenta": "Magenta"
|
||||
}
|
||||
},
|
||||
"backgroundColor": {
|
||||
"label": "Couleur de fond",
|
||||
"options": {
|
||||
"default": "Par défaut",
|
||||
"white": "Blanc",
|
||||
"black": "Noir",
|
||||
"blue": "Bleu",
|
||||
"green": "Vert",
|
||||
"red": "Rouge",
|
||||
"yellow": "Jaune",
|
||||
"cyan": "Cyan",
|
||||
"magenta": "Magenta"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -273,37 +273,15 @@
|
||||
"title": "Virtuele achtergrond",
|
||||
"selectedLabel": "Achtergrond toegepast:",
|
||||
"apply": "Vervang je achtergrond:",
|
||||
"personal": {
|
||||
"title": "Mijn achtergronden",
|
||||
"selectFileTooltip": "Selecteer een afbeeldingsbestand om te gebruiken als persoonlijke achtergrond",
|
||||
"notLoggedInWarning": "U bent niet ingelogd, persoonlijke achtergronden worden niet opgeslagen van de ene vergadering naar de andere.",
|
||||
"warningUploadDisabled": "Persoonlijke achtergronden worden momenteel niet opgeslagen van de ene vergadering naar de andere.",
|
||||
"uploadLimitReached": "U kunt geen persoonlijke achtergronden meer uploaden.",
|
||||
"uploadInProgress": "Afbeelding wordt geüpload…",
|
||||
"errors": {
|
||||
"close": "Sluiten",
|
||||
"file_too_large": {
|
||||
"title": "Bestand te groot",
|
||||
"description": "Het bestand is te groot. Kies een bestand kleiner dan {{maxSize, number}} MB."
|
||||
},
|
||||
"invalid_file_type": {
|
||||
"title": "Ongeldig bestandstype",
|
||||
"description": "Het bestandstype wordt niet ondersteund. Kies een {{allowedExtension, list(type: 'disjunction')}} bestand."
|
||||
}
|
||||
}
|
||||
},
|
||||
"presets": {
|
||||
"title": "Suggesties",
|
||||
"descriptions": {
|
||||
"0": "Geprofileerd houten meubel",
|
||||
"1": "Vergaderruimte",
|
||||
"2": "Loft met zwarte glaswand",
|
||||
"3": "Eetkamer",
|
||||
"4": "Houten planken",
|
||||
"5": "Houten trap",
|
||||
"6": "Grijze bibliotheek",
|
||||
"7": "Koffiebar"
|
||||
}
|
||||
"descriptions": {
|
||||
"0": "Geprofileerd houten meubel",
|
||||
"1": "Vergaderruimte",
|
||||
"2": "Loft met zwarte glaswand",
|
||||
"3": "Eetkamer",
|
||||
"4": "Houten planken",
|
||||
"5": "Houten trap",
|
||||
"6": "Grijze bibliotheek",
|
||||
"7": "Koffiebar"
|
||||
}
|
||||
},
|
||||
"faceLandmarks": {
|
||||
@@ -319,7 +297,7 @@
|
||||
}
|
||||
},
|
||||
"sidePanel": {
|
||||
"ariaLabel": "Zijbalk - {{title}}",
|
||||
"ariaLabel": "Zijbalk",
|
||||
"backToTools": "Terug naar vergadertools",
|
||||
"heading": {
|
||||
"participants": "Deelnemers",
|
||||
@@ -354,11 +332,11 @@
|
||||
"tools": {
|
||||
"transcript": {
|
||||
"title": "Transcriberen",
|
||||
"body": "Zet vergaderingen om in tekst."
|
||||
"body": "Het gesprek opnemen."
|
||||
},
|
||||
"screenRecording": {
|
||||
"title": "Opnemen",
|
||||
"body": "Sla vergaderingen op als video."
|
||||
"body": "De vergadering opnemen."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -619,7 +597,7 @@
|
||||
},
|
||||
"participantTileFocus": {
|
||||
"containerLabel": "Opties voor {{name}}",
|
||||
"toolbarHint": "{{shortcut}}: direct toegang tot de sneltoetsen.",
|
||||
"toolbarHint": "Ctrl+Shift+/: direct toegang tot de sneltoetsen.",
|
||||
"pin": {
|
||||
"enable": "Pinnen",
|
||||
"disable": "Losmaken"
|
||||
|
||||
@@ -126,34 +126,6 @@
|
||||
"medium": "Gemiddeld",
|
||||
"large": "Groot"
|
||||
}
|
||||
},
|
||||
"fontColor": {
|
||||
"label": "Tekstkleur",
|
||||
"options": {
|
||||
"default": "Standaard",
|
||||
"white": "Wit",
|
||||
"black": "Zwart",
|
||||
"blue": "Blauw",
|
||||
"green": "Groen",
|
||||
"red": "Rood",
|
||||
"yellow": "Geel",
|
||||
"cyan": "Cyaan",
|
||||
"magenta": "Magenta"
|
||||
}
|
||||
},
|
||||
"backgroundColor": {
|
||||
"label": "Achtergrondkleur",
|
||||
"options": {
|
||||
"default": "Standaard",
|
||||
"white": "Wit",
|
||||
"black": "Zwart",
|
||||
"blue": "Blauw",
|
||||
"green": "Groen",
|
||||
"red": "Rood",
|
||||
"yellow": "Geel",
|
||||
"cyan": "Cyaan",
|
||||
"magenta": "Magenta"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -20,11 +20,7 @@ export const Spinner = ({
|
||||
const r = 14 - strokeWidth
|
||||
const c = 2 * r * Math.PI
|
||||
return (
|
||||
<ProgressBar
|
||||
aria-label="Loading..."
|
||||
value={30}
|
||||
style={{ height: size, width: size }}
|
||||
>
|
||||
<ProgressBar aria-label="Loading..." value={30}>
|
||||
{({ percentage }) => (
|
||||
<div
|
||||
className={css({
|
||||
|
||||
@@ -36,10 +36,6 @@ export const text = cva({
|
||||
textStyle: 'body',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
bodyXsMedium: {
|
||||
textStyle: 'body',
|
||||
fontWeight: 'medium',
|
||||
},
|
||||
body: {
|
||||
textStyle: 'body',
|
||||
},
|
||||
|
||||
@@ -10,65 +10,14 @@ export const CAPTION_TEXT_SIZE_OPTIONS: CaptionTextSize[] = [
|
||||
'large',
|
||||
]
|
||||
|
||||
export type CaptionColor =
|
||||
| 'default'
|
||||
| 'white'
|
||||
| 'black'
|
||||
| 'blue'
|
||||
| 'green'
|
||||
| 'red'
|
||||
| 'yellow'
|
||||
| 'cyan'
|
||||
| 'magenta'
|
||||
|
||||
export const CAPTION_COLOR_OPTIONS: CaptionColor[] = [
|
||||
'default',
|
||||
'white',
|
||||
'black',
|
||||
'blue',
|
||||
'green',
|
||||
'red',
|
||||
'yellow',
|
||||
'cyan',
|
||||
'magenta',
|
||||
]
|
||||
|
||||
export const CAPTION_FONT_COLOR_VALUES: Record<CaptionColor, string> = {
|
||||
default: '#FFFFFF',
|
||||
white: '#FFFFFF',
|
||||
black: '#000000',
|
||||
blue: '#0000FF',
|
||||
green: '#00FF00',
|
||||
red: '#FF0000',
|
||||
yellow: '#FFFF00',
|
||||
cyan: '#00FFFF',
|
||||
magenta: '#FF00FF',
|
||||
}
|
||||
|
||||
export const CAPTION_BACKGROUND_COLOR_VALUES: Record<CaptionColor, string> = {
|
||||
default: 'rgba(0, 0, 0, 0.75)',
|
||||
black: 'rgba(0, 0, 0, 0.75)',
|
||||
white: 'rgba(255, 255, 255, 0.75)',
|
||||
blue: 'rgba(0, 0, 255, 0.75)',
|
||||
green: 'rgba(0, 255, 0, 0.75)',
|
||||
red: 'rgba(255, 0, 0, 0.75)',
|
||||
yellow: 'rgba(255, 255, 0, 0.75)',
|
||||
cyan: 'rgba(0, 255, 255, 0.75)',
|
||||
magenta: 'rgba(255, 0, 255, 0.75)',
|
||||
}
|
||||
|
||||
type AccessibilityState = {
|
||||
announceReactions: boolean
|
||||
captionTextSize: CaptionTextSize
|
||||
captionFontColor: CaptionColor
|
||||
captionBackgroundColor: CaptionColor
|
||||
}
|
||||
|
||||
const DEFAULT_STATE: AccessibilityState = {
|
||||
announceReactions: false,
|
||||
captionTextSize: 'medium',
|
||||
captionFontColor: 'default',
|
||||
captionBackgroundColor: 'default',
|
||||
}
|
||||
|
||||
function getAccessibilityState(): AccessibilityState {
|
||||
@@ -76,21 +25,10 @@ function getAccessibilityState(): AccessibilityState {
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.ACCESSIBILITY)
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored)
|
||||
const captionTextSize = CAPTION_TEXT_SIZE_OPTIONS.includes(
|
||||
parsed.captionTextSize
|
||||
)
|
||||
const validCaptionSizes = CAPTION_TEXT_SIZE_OPTIONS
|
||||
const captionTextSize = validCaptionSizes.includes(parsed.captionTextSize)
|
||||
? parsed.captionTextSize
|
||||
: DEFAULT_STATE.captionTextSize
|
||||
const captionFontColor = CAPTION_COLOR_OPTIONS.includes(
|
||||
parsed.captionFontColor
|
||||
)
|
||||
? parsed.captionFontColor
|
||||
: DEFAULT_STATE.captionFontColor
|
||||
const captionBackgroundColor = CAPTION_COLOR_OPTIONS.includes(
|
||||
parsed.captionBackgroundColor
|
||||
)
|
||||
? parsed.captionBackgroundColor
|
||||
: DEFAULT_STATE.captionBackgroundColor
|
||||
return {
|
||||
...DEFAULT_STATE,
|
||||
...parsed,
|
||||
@@ -99,8 +37,6 @@ function getAccessibilityState(): AccessibilityState {
|
||||
? parsed.announceReactions
|
||||
: DEFAULT_STATE.announceReactions,
|
||||
captionTextSize,
|
||||
captionFontColor,
|
||||
captionBackgroundColor,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import { proxy, subscribe } from 'valtio'
|
||||
import {
|
||||
ProcessorConfig,
|
||||
ProcessorType,
|
||||
} from '@/features/rooms/livekit/components/blur'
|
||||
import { ProcessorSerialized } from '@/features/rooms/livekit/components/blur'
|
||||
import {
|
||||
loadUserChoices,
|
||||
LocalUserChoices as LocalUserChoicesLK,
|
||||
saveUserChoices,
|
||||
LocalUserChoices as LocalUserChoicesLK,
|
||||
} from '@livekit/components-core'
|
||||
import { VideoQuality } from 'livekit-client'
|
||||
|
||||
export type VideoResolution = 'h720' | 'h360' | 'h180'
|
||||
|
||||
export type LocalUserChoices = LocalUserChoicesLK & {
|
||||
processorConfig?: ProcessorConfig
|
||||
processorSerialized?: ProcessorSerialized
|
||||
noiseReductionEnabled?: boolean
|
||||
audioOutputDeviceId?: string
|
||||
videoPublishResolution?: VideoResolution
|
||||
@@ -31,33 +28,7 @@ function getUserChoicesState(): LocalUserChoices {
|
||||
}
|
||||
|
||||
export const userChoicesStore = proxy<LocalUserChoices>(getUserChoicesState())
|
||||
|
||||
subscribe(userChoicesStore, () => {
|
||||
saveUserChoices(userChoicesStore, false)
|
||||
})
|
||||
|
||||
// we run some logic on store loading to check if the processor config is still valid
|
||||
if (userChoicesStore.processorConfig?.type === ProcessorType.VIRTUAL) {
|
||||
if (userChoicesStore.processorConfig.imagePath.startsWith('blob:')) {
|
||||
// this happens when a not authenticated user had changed their background image
|
||||
// we restore clear the processor config to avoid displaying a black screen.
|
||||
userChoicesStore.processorConfig = undefined
|
||||
} else if (userChoicesStore.processorConfig.fileId) {
|
||||
// Checking if the image is still available / accessible
|
||||
await fetch(userChoicesStore.processorConfig.imagePath, {
|
||||
// We bypass the cache to ensure we have access
|
||||
cache: 'reload',
|
||||
})
|
||||
.then((response) => {
|
||||
// if we cannot fetch the image (likely a 401 from the backend because
|
||||
// the user is not logged in anymore, etc.),
|
||||
// we clear the processor config to avoid displaying a black screen.
|
||||
// This can happen when the user logs out for instance, etc.
|
||||
if (!response.ok) {
|
||||
userChoicesStore.processorConfig = undefined
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
userChoicesStore.processorConfig = undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,14 +95,3 @@ html:has(.lk-video-conference) {
|
||||
U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
|
||||
U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
.hoverGroup .hoverGroupChild {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hoverGroup:hover .hoverGroupChild,
|
||||
.hoverGroup:focus-within .hoverGroupChild {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@@ -14,14 +14,6 @@ export default defineConfig(({ mode }) => {
|
||||
port: parseInt(env.VITE_PORT) || 3000,
|
||||
host: env.VITE_HOST ?? 'localhost',
|
||||
allowedHosts: ['.nip.io'],
|
||||
// In a local dev setup, we proxy the media server ourselves to avoid CORS issues
|
||||
proxy: {
|
||||
'/media': {
|
||||
target: 'http://localhost:8083',
|
||||
changeOrigin: true,
|
||||
secure: false
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -41,15 +41,3 @@ autoscaling:
|
||||
|
||||
nodeSelector: {}
|
||||
resources: {}
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
@@ -28,16 +28,6 @@ livekit:
|
||||
urls:
|
||||
- https://meet.127.0.0.1.nip.io/api/v1.0/rooms/webhooks-livekit/
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 2000
|
||||
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
|
||||
loadBalancer:
|
||||
type: nginx
|
||||
|
||||
@@ -71,7 +71,7 @@ backend:
|
||||
SUMMARY_SERVICE_API_TOKEN: password
|
||||
RECORDING_DOWNLOAD_BASE_URL: https://meet.127.0.0.1.nip.io/recording
|
||||
ROOM_TELEPHONY_ENABLED: True
|
||||
SSL_CERT_FILE: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
SSL_CERT_FILE: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem
|
||||
|
||||
|
||||
migrate:
|
||||
@@ -100,7 +100,7 @@ backend:
|
||||
# Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false
|
||||
extraVolumeMounts:
|
||||
- name: certs
|
||||
mountPath: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
mountPath: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
subPath: cacert.pem
|
||||
|
||||
# Extra volumes to manage our local custom CA and avoid to set ssl_verify: false
|
||||
@@ -112,21 +112,6 @@ backend:
|
||||
- key: cacert.pem
|
||||
path: cacert.pem
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
frontend:
|
||||
|
||||
replicas: 1
|
||||
@@ -136,21 +121,6 @@ frontend:
|
||||
pullPolicy: Always
|
||||
tag: "latest"
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
host: meet.127.0.0.1.nip.io
|
||||
@@ -166,7 +136,7 @@ ingressWebhook:
|
||||
posthog:
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
|
||||
ingressAssets:
|
||||
enabled: false
|
||||
|
||||
@@ -190,27 +160,12 @@ summary:
|
||||
WEBHOOK_URL: https://www.mock-impress.com/webhook/
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
tag: "latest"
|
||||
|
||||
|
||||
command:
|
||||
- "uvicorn"
|
||||
- "summary.main:app"
|
||||
@@ -242,21 +197,6 @@ celeryTranscribe:
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
@@ -293,21 +233,6 @@ celerySummarize:
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
|
||||
@@ -44,15 +44,3 @@ autoscaling:
|
||||
|
||||
nodeSelector: {}
|
||||
resources: {}
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
@@ -28,16 +28,6 @@ livekit:
|
||||
urls:
|
||||
- https://meet.127.0.0.1.nip.io/api/v1.0/rooms/webhooks-livekit/
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 2000
|
||||
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
|
||||
loadBalancer:
|
||||
type: nginx
|
||||
|
||||
@@ -75,7 +75,7 @@ backend:
|
||||
ROOM_TELEPHONY_ENABLED: True
|
||||
ROOM_TELEPHONY_DEFAULT_COUNTRY: 'FR'
|
||||
ROOM_TELEPHONY_PHONE_NUMBER: '+33901020304'
|
||||
SSL_CERT_FILE: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
SSL_CERT_FILE: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
ROOM_SUBTITLE_ENABLED: True
|
||||
EXTERNAL_API_ENABLED: True
|
||||
APPLICATION_JWT_AUDIENCE: https://meet.127.0.0.1.nip.io/external-api/v1.0/
|
||||
@@ -86,20 +86,6 @@ backend:
|
||||
CELERY_ENABLED: True
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
migrate:
|
||||
command:
|
||||
@@ -127,7 +113,7 @@ backend:
|
||||
# Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false
|
||||
extraVolumeMounts:
|
||||
- name: certs
|
||||
mountPath: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
mountPath: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
subPath: cacert.pem
|
||||
|
||||
# Extra volumes to manage our local custom CA and avoid to set ssl_verify: false
|
||||
@@ -148,21 +134,6 @@ frontend:
|
||||
|
||||
replicas: 1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-frontend-generic
|
||||
pullPolicy: Always
|
||||
@@ -183,7 +154,7 @@ ingressWebhook:
|
||||
posthog:
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
|
||||
ingressAssets:
|
||||
enabled: false
|
||||
|
||||
@@ -209,27 +180,12 @@ summary:
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
tag: "latest"
|
||||
|
||||
|
||||
command:
|
||||
- "uvicorn"
|
||||
- "summary.main:app"
|
||||
@@ -261,22 +217,7 @@ celeryTranscribe:
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
@@ -315,21 +256,6 @@ celerySummarize:
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
@@ -357,21 +283,6 @@ agents:
|
||||
{{- end }}
|
||||
ENABLE_SILERO_VAD: "false"
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-agents
|
||||
pullPolicy: Always
|
||||
|
||||
@@ -41,15 +41,3 @@ autoscaling:
|
||||
|
||||
nodeSelector: {}
|
||||
resources: {}
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
@@ -28,17 +28,6 @@ livekit:
|
||||
urls:
|
||||
- https://meet.127.0.0.1.nip.io/api/v1.0/rooms/webhooks-livekit/
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 2000
|
||||
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
|
||||
loadBalancer:
|
||||
type: nginx
|
||||
annotations:
|
||||
|
||||
@@ -94,22 +94,8 @@ backend:
|
||||
key: BREVO_API_KEY
|
||||
BREVO_API_CONTACT_LIST_IDS: 8
|
||||
ROOM_TELEPHONY_ENABLED: True
|
||||
SSL_CERT_FILE: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
SSL_CERT_FILE: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
migrate:
|
||||
command:
|
||||
@@ -137,7 +123,7 @@ backend:
|
||||
# Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false
|
||||
extraVolumeMounts:
|
||||
- name: certs
|
||||
mountPath: /app/.venv/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
mountPath: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
|
||||
subPath: cacert.pem
|
||||
|
||||
# Extra volumes to manage our local custom CA and avoid to set ssl_verify: false
|
||||
@@ -158,21 +144,6 @@ frontend:
|
||||
|
||||
replicas: 1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-frontend
|
||||
pullPolicy: Always
|
||||
@@ -218,21 +189,6 @@ summary:
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
@@ -269,21 +225,6 @@ celeryTranscribe:
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
@@ -320,21 +261,6 @@ celerySummarize:
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
|
||||
@@ -112,7 +112,7 @@ spec:
|
||||
/usr/bin/mc mb meet/meet-media-storage && \
|
||||
exit 0
|
||||
restartPolicy: Never
|
||||
backoffLimit: 3
|
||||
backoffLimit: 1
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
@@ -132,7 +132,7 @@ spec:
|
||||
/usr/bin/mc admin config set meet notify_webhook:meet-webhook endpoint="https://meet.127.0.0.1.nip.io/api/v1.0/recordings/storage-hook/" auth_token="Bearer password" && \
|
||||
/usr/bin/mc admin service restart meet --wait --json && \
|
||||
sleep 15 && \
|
||||
/usr/bin/mc event add meet/meet-media-storage arn:minio:sqs::meet-webhook:webhook --event put --prefix "recordings" && \
|
||||
/usr/bin/mc event add meet/meet-media-storage arn:minio:sqs::meet-webhook:webhook --event put && \
|
||||
exit 0
|
||||
restartPolicy: Never
|
||||
backoffLimit: 3
|
||||
backoffLimit: 1
|
||||
|
||||
@@ -31,10 +31,6 @@ spec:
|
||||
- name: {{ include "meet.secret.dockerconfigjson.name" (dict "fullname" (include "meet.fullname" .) "imageCredentials" $.Values.image.credentials) }}
|
||||
{{- end }}
|
||||
shareProcessNamespace: {{ .Values.agents.shareProcessNamespace }}
|
||||
{{- with .Values.agents.podSecurityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
{{- with .Values.agents.sidecars }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user