(backend) compatible with summary v2 routes

* Track external process (transcribe) status through a webhook and
new Enum choices,
* Send v2 and v1 summary routes params so that both endpoint can still be used,
* Updated dev setup to make it work.
This commit is contained in:
Florent Chehab
2026-05-26 18:28:08 +02:00
parent 7e286fcb4b
commit acd7c51758
18 changed files with 785 additions and 345 deletions
-1
View File
@@ -305,7 +305,6 @@ jobs:
working-directory: src/summary
env:
V1_TENANT_ID: 'test-tenant'
AUTHORIZED_TENANTS: '[{"id": "test-tenant", "api_key": "test-api-token", "webhook_url": "https://example.com/webhook", "webhook_api_key": "test-webhook-api-key"}]'
AWS_STORAGE_BUCKET_NAME: "http://meet-media-storage"
AWS_S3_ENDPOINT_URL: "minio:9000"
+2 -1
View File
@@ -64,8 +64,9 @@ ALLOW_UNREGISTERED_ROOMS=False
RECORDING_ENABLE=True
RECORDING_STORAGE_EVENT_ENABLE=True
RECORDING_STORAGE_EVENT_TOKEN=password
SUMMARY_SERVICE_ENDPOINT=http://app-summary-dev:8000/api/v1/tasks/
SUMMARY_SERVICE_ENDPOINT=http://app-summary-dev:8000/api/v2/async-jobs/transcribe/
SUMMARY_SERVICE_API_TOKEN=password
SUMMARY_SERVICE_WEBHOOK_API_TOKEN=webhook-password
RECORDING_DOWNLOAD_BASE_URL=http://localhost:3000/recording
# Recording encoding (LiveKit Egress advanced options).
+45 -1
View File
@@ -39,7 +39,10 @@ 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.authentication import (
RecordingProcessWebhookAuthentication,
StorageEventAuthentication,
)
from core.recording.event.exceptions import (
InvalidBucketError,
InvalidFilepathError,
@@ -995,6 +998,47 @@ class RecordingViewSet(
{"message": "Event processed."},
)
@decorators.action(
detail=False,
methods=["post"],
url_path="external-process-hook",
authentication_classes=[RecordingProcessWebhookAuthentication],
)
def on_external_process_event_received(self, request, pk=None): # pylint: disable=unused-argument
"""Handle incoming external process events for recordings."""
logger.debug("Processing external process event %s", request.data)
data = request.data
if not data.get("job_id"):
raise drf_exceptions.ValidationError(detail="No job_id provided")
job_id = data["job_id"]
try:
recording = models.Recording.objects.get(external_process_id=job_id)
except models.Recording.DoesNotExist as e:
logger.warning("No recording found for job_id %s: %s", job_id, e)
recording = None
changed = False
if recording and data.get("type") == "transcript":
if data.get("status") == "success":
logger.info("External process received for recording %s", job_id)
recording.status = (
models.RecordingStatusChoices.EXTERNAL_PROCESS_SUCCESSFUL
)
changed = True
if data.get("status") == "failure":
recording.status = models.RecordingStatusChoices.EXTERNAL_PROCESS_FAILED
changed = True
if changed and recording:
recording.save()
else:
logger.info("No changes to save for external process id %s", job_id)
return drf_response.Response(
{"message": "Event processed."},
)
def _auth_get_original_url(self, request):
"""
Extracts and parses the original URL from the "HTTP_X_ORIGINAL_URL" header.
@@ -0,0 +1,23 @@
# Generated by Django 5.2.14 on 2026-05-26 14:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0018_rename_active_application_is_active'),
]
operations = [
migrations.AddField(
model_name='recording',
name='external_process_id',
field=models.CharField(blank=True, help_text='ID of the external process associated with the recording.', max_length=255, null=True, unique=True, verbose_name='External Process ID'),
),
migrations.AlterField(
model_name='recording',
name='status',
field=models.CharField(choices=[('initiated', 'Initiated'), ('active', 'Active'), ('stopped', 'Stopped'), ('saved', 'Saved'), ('aborted', 'Aborted'), ('failed_to_start', 'Failed to Start'), ('failed_to_stop', 'Failed to Stop'), ('notification_succeeded', 'Notification succeeded'), ('external_process_successful', 'External process successful'), ('external_process_failed', 'External process failed')], default='initiated', max_length=50),
),
]
+17
View File
@@ -60,6 +60,11 @@ class RecordingStatusChoices(models.TextChoices):
FAILED_TO_START = "failed_to_start", _("Failed to Start")
FAILED_TO_STOP = "failed_to_stop", _("Failed to Stop")
NOTIFICATION_SUCCEEDED = "notification_succeeded", _("Notification succeeded")
EXTERNAL_PROCESS_SUCCESSFUL = (
"external_process_successful",
_("External process successful"),
)
EXTERNAL_PROCESS_FAILED = "external_process_failed", _("External process failed")
@classmethod
def is_final(cls, status):
@@ -73,6 +78,8 @@ class RecordingStatusChoices(models.TextChoices):
cls.STOPPED,
cls.SAVED,
cls.ABORTED,
cls.EXTERNAL_PROCESS_SUCCESSFUL,
cls.EXTERNAL_PROCESS_FAILED,
cls.FAILED_TO_START,
cls.FAILED_TO_STOP,
}
@@ -591,6 +598,14 @@ class Recording(BaseModel):
verbose_name=_("Recording options"),
help_text=_("Recording options"),
)
external_process_id = models.CharField(
max_length=255,
null=True,
blank=True,
unique=True,
verbose_name=_("External Process ID"),
help_text=_("ID of the external process associated with the recording."),
)
class Meta:
db_table = "meet_recording"
@@ -646,6 +661,8 @@ class Recording(BaseModel):
return self.status in {
RecordingStatusChoices.NOTIFICATION_SUCCEEDED,
RecordingStatusChoices.SAVED,
RecordingStatusChoices.EXTERNAL_PROCESS_SUCCESSFUL,
RecordingStatusChoices.EXTERNAL_PROCESS_FAILED,
}
@property
@@ -14,9 +14,9 @@ logger = logging.getLogger(__name__)
class MachineUser:
"""Represent a non-interactive system user for automated storage operations."""
def __init__(self) -> None:
def __init__(self, username: str = "storage_event_user") -> None:
self.pk = None
self.username = "storage_event_user"
self.username = username
self.is_active = True
@property
@@ -91,3 +91,29 @@ class StorageEventAuthentication(BaseAuthentication):
def authenticate_header(self, request):
"""Return the WWW-Authenticate header value."""
return f"{self.TOKEN_TYPE} realm='Storage event API'"
class RecordingProcessWebhookAuthentication(BaseAuthentication):
"""
Custom authentication class for recording process webhook requests.
Validates the API key in the Authorization header.
"""
def authenticate(self, request):
"""
Authenticate the request and return a two-tuple of (user, token).
"""
logger.info("Authentificating")
authorization_header: str = request.headers.get("Authorization") or ""
if not secrets.compare_digest(
authorization_header.removeprefix("Bearer "),
settings.SUMMARY_SERVICE_WEBHOOK_API_TOKEN,
):
logger.warning(
"Authentication failed: Bad Authorization header (ip: %s)",
request.META.get("REMOTE_ADDR"),
)
raise AuthenticationFailed()
return MachineUser("external_process_user"), None
@@ -1,8 +1,10 @@
"""Service to notify external services when a new recording is ready."""
import asyncio
import json
import logging
import smtplib
import warnings
from datetime import datetime, timezone
from django.conf import settings
@@ -17,6 +19,7 @@ from asgiref.sync import async_to_sync
from livekit import api as livekit_api
from core import models, utils
from core.utils import generate_download_s3_file_url
logger = logging.getLogger(__name__)
@@ -215,18 +218,45 @@ class NotificationService:
)(recording.worker_id)
payload = {
# Legacy V1 params to avoid a breaking change
"owner_id": str(owner_access.user.id),
"recording_filename": recording.key,
"metadata_filename": metadata_filename,
"email": owner_access.user.email,
"sub": owner_access.user.sub,
"room": recording.room.name,
"language": recording.options.get("language"),
"owner_timezone": str(owner_access.user.timezone),
"download_link": f"{get_recording_download_base_url()}/{recording.id}",
"context_language": owner_access.user.language,
"recording_start_at": (started_at.isoformat() if started_at else None),
"recording_end_at": (ended_at.isoformat() if ended_at else None),
# V2 params
"user_sub": owner_access.user.sub,
"user_email": owner_access.user.email,
"cloud_storage_url": generate_download_s3_file_url(
recording.key, expires_in=60 * 60 * 24, override_domain=False
),
"language": recording.options.get("language", "fr"),
"context_language": owner_access.user.language,
"push_to_docs_config": {
"user_email": owner_access.user.email,
"title": "TODO",
"download_link": f"{get_recording_download_base_url()}/{recording.id}",
# For now the feature flag logic is handled on summary side
"auto_create_summary": True,
},
"metadata": (
{
"cloud_storage_url": generate_download_s3_file_url(
metadata_filename,
expires_in=60 * 60 * 24,
override_domain=False,
),
"started_at": started_at.isoformat(),
"ended_at": ended_at.isoformat(),
}
if (started_at and ended_at)
else None
),
}
headers = {
@@ -242,6 +272,31 @@ class NotificationService:
timeout=30,
)
response.raise_for_status()
is_v2_implementation = False
try:
response_json = response.json()
# We do not require a job_id to avoid a breaking change
if job_id := response_json.get("job_id"):
recording.external_process_id = job_id
recording.save()
is_v2_implementation = True
except json.JSONDecodeError:
pass
if not is_v2_implementation:
warnings.warn(
"You are likely using your own implementation for the summary / "
"transcribe service."
"We have released a new version and API contract for that service, "
"we recommend checking it out"
# pylint: disable=line-too-long
"https://github.com/suitenumerique/meet/blob/19c2a378e7e66652afbfa3e749badd697e3917ac/src/summary/summary/api/route/tasks_v2.py "
"and mimicking it's behavior. We will remove the legacy "
"handling in a future version.",
DeprecationWarning,
stacklevel=2,
)
except requests.RequestException as exc:
logger.exception(
"Summary service error for recording %s. URL: %s. Exception: %s",
@@ -0,0 +1,134 @@
"""
Test recordings API endpoints: external process hook.
"""
# pylint: disable=redefined-outer-name,unused-argument
import pytest
from ...factories import RecordingFactory
from ...models import RecordingStatusChoices
pytestmark = pytest.mark.django_db
@pytest.fixture
def external_process_settings(settings):
"""Configure authentication token for the external process webhook."""
settings.SUMMARY_SERVICE_WEBHOOK_API_TOKEN = "testWebhookToken"
return settings
def test_external_process_event_missing_authorization_header(
external_process_settings, client
):
"""Requests without authorization must be rejected."""
response = client.post(
"/api/v1.0/recordings/external-process-hook/",
{"job_id": "job-1", "type": "transcript", "status": "success"},
)
assert response.status_code == 403
def test_external_process_event_wrong_bearer_token(external_process_settings, client):
"""Requests with invalid bearer token must be rejected."""
response = client.post(
"/api/v1.0/recordings/external-process-hook/",
{"job_id": "job-1", "type": "transcript", "status": "success"},
HTTP_AUTHORIZATION="Bearer wrongToken",
)
assert response.status_code == 403
def test_external_process_event_missing_job_id(external_process_settings, client):
"""Payload without job_id must fail validation."""
response = client.post(
"/api/v1.0/recordings/external-process-hook/",
{"type": "transcript", "status": "success"},
HTTP_AUTHORIZATION="Bearer testWebhookToken",
)
assert response.status_code == 400
assert "No job_id provided" in str(response.json())
def test_external_process_event_success_updates_recording_status(
external_process_settings, client
):
"""A successful transcript process should update recording status."""
recording = RecordingFactory(
status=RecordingStatusChoices.SAVED,
external_process_id="job-123",
)
response = client.post(
"/api/v1.0/recordings/external-process-hook/",
{"job_id": "job-123", "type": "transcript", "status": "success"},
HTTP_AUTHORIZATION="Bearer testWebhookToken",
)
assert response.status_code == 200
assert response.json() == {"message": "Event processed."}
recording.refresh_from_db()
assert recording.status == RecordingStatusChoices.EXTERNAL_PROCESS_SUCCESSFUL
def test_external_process_event_failure_updates_recording_status(
external_process_settings, client
):
"""A failing transcript process should update recording status."""
recording = RecordingFactory(
status=RecordingStatusChoices.SAVED,
external_process_id="job-456",
)
response = client.post(
"/api/v1.0/recordings/external-process-hook/",
{"job_id": "job-456", "type": "transcript", "status": "failure"},
HTTP_AUTHORIZATION="Bearer testWebhookToken",
)
assert response.status_code == 200
assert response.json() == {"message": "Event processed."}
recording.refresh_from_db()
assert recording.status == RecordingStatusChoices.EXTERNAL_PROCESS_FAILED
def test_external_process_event_unknown_recording_is_ignored(
external_process_settings, client
):
"""Unknown job_id should not fail the webhook processing."""
response = client.post(
"/api/v1.0/recordings/external-process-hook/",
{"job_id": "missing-job", "type": "transcript", "status": "success"},
HTTP_AUTHORIZATION="Bearer testWebhookToken",
)
assert response.status_code == 200
assert response.json() == {"message": "Event processed."}
def test_external_process_event_non_transcript_event_does_not_change_status(
external_process_settings, client
):
"""Only transcript events should update recording status."""
recording = RecordingFactory(
status=RecordingStatusChoices.SAVED,
external_process_id="job-789",
)
response = client.post(
"/api/v1.0/recordings/external-process-hook/",
{"job_id": "job-789", "type": "thumbnail", "status": "success"},
HTTP_AUTHORIZATION="Bearer testWebhookToken",
)
assert response.status_code == 200
assert response.json() == {"message": "Event processed."}
recording.refresh_from_db()
assert recording.status == RecordingStatusChoices.SAVED
+35
View File
@@ -455,3 +455,38 @@ def generate_upload_policy(file):
)
return policy
def generate_download_s3_file_url(
key, *, expires_in: int, override_domain: bool = True
):
"""
Generate a S3 signed download url for a given key.
"""
# This settings should be used if the backend application and the frontend application
# can't connect to the object storage with the same domain. This is the case in the
# docker compose stack used in development. The frontend application will use localhost
# to connect to the object storage while the backend application will use the object storage
# service name declared in the docker compose stack.
# This is needed because the domain name is used to compute the signature. So it can't be
# changed dynamically by the frontend application.
if settings.AWS_S3_DOMAIN_REPLACE and override_domain:
s3_client = boto3.client(
"s3",
aws_access_key_id=settings.AWS_S3_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_S3_SECRET_ACCESS_KEY,
endpoint_url=settings.AWS_S3_DOMAIN_REPLACE,
config=botocore.client.Config(
region_name=settings.AWS_S3_REGION_NAME,
signature_version=settings.AWS_S3_SIGNATURE_VERSION,
),
)
else:
s3_client = default_storage.connection.meta.client
return s3_client.generate_presigned_url(
ClientMethod="get_object",
Params={"Bucket": default_storage.bucket_name, "Key": key},
ExpiresIn=expires_in,
)
+100 -82
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-21 14:09+0000\n"
"POT-Creation-Date: 2026-05-26 16:20+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"
@@ -88,17 +88,17 @@ msgstr "Scopes"
msgid "Creator is me"
msgstr "Ersteller bin ich"
#: core/api/serializers.py:88
#: core/api/serializers.py:89
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Sie müssen Administrator oder Eigentümer eines Raums sein, um Zugriffe "
"hinzuzufügen."
#: core/api/serializers.py:509
#: core/api/serializers.py:534
msgid "This file extension is not allowed."
msgstr "Diese Dateiendung ist nicht erlaubt."
#: core/api/viewsets.py:1090
#: core/api/viewsets.py:1228
msgid "You have reached the maximum number of files for this type."
msgstr "Sie haben die maximale Anzahl an Dateien dieses Typs erreicht."
@@ -146,51 +146,59 @@ msgstr "Stopp fehlgeschlagen"
msgid "Notification succeeded"
msgstr "Benachrichtigung erfolgreich"
#: core/models.py:89
#: core/models.py:65
msgid "External process successful"
msgstr ""
#: core/models.py:67
msgid "External process failed"
msgstr ""
#: core/models.py:96
msgid "SCREEN_RECORDING"
msgstr "BILDSCHIRMAUFZEICHNUNG"
#: core/models.py:90
#: core/models.py:97
msgid "TRANSCRIPT"
msgstr "TRANSKRIPT"
#: core/models.py:96
#: core/models.py:103
msgid "Public Access"
msgstr "Öffentlicher Zugriff"
#: core/models.py:97
#: core/models.py:104
msgid "Trusted Access"
msgstr "Vertrauenswürdiger Zugriff"
#: core/models.py:98
#: core/models.py:105
msgid "Restricted Access"
msgstr "Eingeschränkter Zugriff"
#: core/models.py:110
#: core/models.py:117
msgid "id"
msgstr "ID"
#: core/models.py:111
#: core/models.py:118
msgid "primary key for the record as UUID"
msgstr "Primärschlüssel des Eintrags als UUID"
#: core/models.py:117
#: core/models.py:124
msgid "created on"
msgstr "erstellt am"
#: core/models.py:118
#: core/models.py:125
msgid "date and time at which a record was created"
msgstr "Datum und Uhrzeit der Erstellung eines Eintrags"
#: core/models.py:123
#: core/models.py:130
msgid "updated on"
msgstr "aktualisiert am"
#: core/models.py:124
#: core/models.py:131
msgid "date and time at which a record was last updated"
msgstr "Datum und Uhrzeit der letzten Aktualisierung eines Eintrags"
#: core/models.py:144
#: core/models.py:151
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -198,11 +206,11 @@ msgstr ""
"Geben Sie einen gültigen Sub ein. Dieser Wert darf nur Buchstaben, Zahlen "
"und die Zeichen @/./+/-/_ enthalten."
#: core/models.py:150
#: core/models.py:157
msgid "sub"
msgstr "Sub"
#: core/models.py:152
#: core/models.py:159
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -210,55 +218,55 @@ msgstr ""
"Optional für ausstehende Benutzer; erforderlich nach Kontoaktivierung. "
"Maximal 255 Zeichen. Nur Buchstaben, Zahlen und @/./+/-/_ Zeichen erlaubt."
#: core/models.py:161
#: core/models.py:168
msgid "identity email address"
msgstr "Identitäts-E-Mail-Adresse"
#: core/models.py:166
#: core/models.py:173
msgid "admin email address"
msgstr "Administrator-E-Mail-Adresse"
#: core/models.py:168
#: core/models.py:175
msgid "full name"
msgstr "Vollständiger Name"
#: core/models.py:170
#: core/models.py:177
msgid "short name"
msgstr "Kurzname"
#: core/models.py:176
#: core/models.py:183
msgid "language"
msgstr "Sprache"
#: core/models.py:177
#: core/models.py:184
msgid "The language in which the user wants to see the interface."
msgstr "Die Sprache, in der der Benutzer die Oberfläche sehen möchte."
#: core/models.py:183
#: core/models.py:190
msgid "The timezone in which the user wants to see times."
msgstr "Die Zeitzone, in der der Benutzer die Zeiten sehen möchte."
#: core/models.py:186
#: core/models.py:193
msgid "device"
msgstr "Gerät"
#: core/models.py:188
#: core/models.py:195
msgid "Whether the user is a device or a real user."
msgstr "Ob es sich um ein Gerät oder einen echten Benutzer handelt."
#: core/models.py:191
#: core/models.py:198
msgid "staff status"
msgstr "Mitarbeiterstatus"
#: core/models.py:193
#: core/models.py:200
msgid "Whether the user can log into this admin site."
msgstr "Ob der Benutzer sich bei dieser Admin-Seite anmelden kann."
#: core/models.py:196
#: core/models.py:203
msgid "active"
msgstr "aktiv"
#: core/models.py:199
#: core/models.py:206
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -266,66 +274,66 @@ msgstr ""
"Ob dieser Benutzer als aktiv behandelt werden soll. Deaktivieren Sie dies "
"anstelle des Löschens des Kontos."
#: core/models.py:212
#: core/models.py:219
msgid "user"
msgstr "Benutzer"
#: core/models.py:213
#: core/models.py:220
msgid "users"
msgstr "Benutzer"
#: core/models.py:272
#: core/models.py:279
msgid "Resource"
msgstr "Ressource"
#: core/models.py:273
#: core/models.py:280
msgid "Resources"
msgstr "Ressourcen"
#: core/models.py:331
#: core/models.py:338
msgid "Resource access"
msgstr "Ressourcenzugriff"
#: core/models.py:332
#: core/models.py:339
msgid "Resource accesses"
msgstr "Ressourcenzugriffe"
#: core/models.py:338
#: core/models.py:345
msgid "Resource access with this User and Resource already exists."
msgstr ""
"Ein Ressourcenzugriff mit diesem Benutzer und dieser Ressource existiert "
"bereits."
#: core/models.py:394
#: core/models.py:402
msgid "Visio room configuration"
msgstr "Visio-Raumkonfiguration"
#: core/models.py:395
#: core/models.py:403
msgid "Values for Visio parameters to configure the room."
msgstr "Werte für Visio-Parameter zur Konfiguration des Raums."
#: core/models.py:402
#: core/models.py:410
msgid "Room PIN code"
msgstr "PIN-Code für den Raum"
#: core/models.py:403
#: core/models.py:411
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Eindeutiger n-stelliger Code, der diesen Raum im Telephonmodus identifiziert."
#: core/models.py:409 core/models.py:563
#: core/models.py:417 core/models.py:571
msgid "Room"
msgstr "Raum"
#: core/models.py:410
#: core/models.py:418
msgid "Rooms"
msgstr "Räume"
#: core/models.py:574
#: core/models.py:582
msgid "Worker ID"
msgstr "Worker-ID"
#: core/models.py:576
#: core/models.py:584
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -334,141 +342,149 @@ msgstr ""
"erhalten, auch wenn der Worker stoppt, was ein einfaches Nachverfolgen "
"ermöglicht."
#: core/models.py:584
#: core/models.py:592
msgid "Recording mode"
msgstr "Aufzeichnungsmodus"
#: core/models.py:585
#: core/models.py:593
msgid "Defines the mode of recording being called."
msgstr "Definiert den aufgerufenen Aufzeichnungsmodus."
#: core/models.py:590 core/models.py:591
#: core/models.py:598 core/models.py:599
msgid "Recording options"
msgstr "Aufnahmeoptionen"
#: core/models.py:597
#: core/models.py:606
msgid "External Process ID"
msgstr ""
#: core/models.py:607
msgid "ID of the external process associated with the recording."
msgstr ""
#: core/models.py:613
msgid "Recording"
msgstr "Aufzeichnung"
#: core/models.py:598
#: core/models.py:614
msgid "Recordings"
msgstr "Aufzeichnungen"
#: core/models.py:706
#: core/models.py:724
msgid "Recording/user relation"
msgstr "Beziehung Aufzeichnung/Benutzer"
#: core/models.py:707
#: core/models.py:725
msgid "Recording/user relations"
msgstr "Beziehungen Aufzeichnung/Benutzer"
#: core/models.py:713
#: core/models.py:731
msgid "This user is already in this recording."
msgstr "Dieser Benutzer ist bereits Teil dieser Aufzeichnung."
#: core/models.py:719
#: core/models.py:737
msgid "This team is already in this recording."
msgstr "Dieses Team ist bereits Teil dieser Aufzeichnung."
#: core/models.py:725
#: core/models.py:743
msgid "Either user or team must be set, not both."
msgstr "Entweder Benutzer oder Team muss festgelegt werden, nicht beides."
#: core/models.py:742
#: core/models.py:760
msgid "Create rooms"
msgstr "Räume erstellen"
#: core/models.py:743
#: core/models.py:761
msgid "List rooms"
msgstr "Räume auflisten"
#: core/models.py:744
#: core/models.py:762
msgid "Retrieve room details"
msgstr "Raumdetails abrufen"
#: core/models.py:745
#: core/models.py:763
msgid "Update rooms"
msgstr "Räume aktualisieren"
#: core/models.py:746
#: core/models.py:764
msgid "Delete rooms"
msgstr "Räume löschen"
#: core/models.py:759
#: core/models.py:777
msgid "Application name"
msgstr "Anwendungsname"
#: core/models.py:760
#: core/models.py:778
msgid "Descriptive name for this application."
msgstr "Beschreibender Name für diese Anwendung."
#: core/models.py:770
#: core/models.py:788
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr ""
"Beim Speichern gehasht. Jetzt kopieren, wenn dies ein neues Geheimnis ist."
#: core/models.py:781
#: core/models.py:799
msgid "Application"
msgstr "Anwendung"
#: core/models.py:782
#: core/models.py:800
msgid "Applications"
msgstr "Anwendungen"
#: core/models.py:805
#: core/models.py:823
msgid "Enter a valid domain"
msgstr "Geben Sie eine gültige Domain ein"
#: core/models.py:808
#: core/models.py:826
msgid "Domain"
msgstr "Domain"
#: core/models.py:809
#: core/models.py:827
msgid "Email domain this application can act on behalf of."
msgstr "E-Mail-Domain, im Namen der diese Anwendung handeln kann."
#: core/models.py:821
#: core/models.py:839
msgid "Application domain"
msgstr "Anwendungsdomain"
#: core/models.py:822
#: core/models.py:840
msgid "Application domains"
msgstr "Anwendungsdomains"
#: core/models.py:840
#: core/models.py:858
msgid "Pending"
msgstr "Ausstehend"
#: core/models.py:848
#: core/models.py:866
msgid "Ready"
msgstr "Bereit"
#: core/models.py:854
#: core/models.py:872
msgid "Background image"
msgstr "Hintergrundbild"
#: core/models.py:866
#: core/models.py:884
msgid "title"
msgstr "Titel"
#: core/models.py:890
#: core/models.py:908
msgid "Malware detection info when the analysis status is unsafe."
msgstr ""
"Informationen zur Malware-Erkennung, wenn der Analyse-Status unsicher ist."
#: core/models.py:895
#: core/models.py:913
msgid "File"
msgstr "Datei"
#: core/models.py:896
#: core/models.py:914
msgid "Files"
msgstr "Dateien"
#: core/models.py:1000
#: core/models.py:1018
msgid "This file is already hard deleted."
msgstr "Diese Datei wurde bereits endgültig gelöscht."
#: core/models.py:1010
#: core/models.py:1028
#, fuzzy
#| msgid "To hard delete a file, it must first be soft deleted."
msgid "To hard delete a file, it must first be soft deleted."
@@ -476,7 +492,7 @@ msgstr ""
"Um eine Datei endgültig zu löschen, muss sie zuvor weich gelöscht worden "
"sein."
#: core/recording/event/notification.py:116
#: core/recording/event/notification.py:124
msgid "Your recording is ready"
msgstr "Ihre Aufzeichnung ist bereit"
@@ -576,7 +592,9 @@ msgstr "So speichern Sie diese Aufzeichnung dauerhaft:"
#: core/templates/mail/html/screen_recording.html:208
#: core/templates/mail/text/screen_recording.txt:13
msgid "Click the \"<a href=\"%(link)s\">Open</a>\" link below "
#, fuzzy
#| msgid "Click the \"<a href=\"%(link)s\">Open</a>\" link below "
msgid "Click the \"Open\" button below "
msgstr "Klicken Sie auf den Link „<a href=\"%(link)s\">Öffnen</a>\" unten "
#: core/templates/mail/html/screen_recording.html:209
+100 -82
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-21 14:09+0000\n"
"POT-Creation-Date: 2026-05-26 16:20+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"
@@ -88,15 +88,15 @@ msgstr "Scopes"
msgid "Creator is me"
msgstr "Creator is me"
#: core/api/serializers.py:88
#: core/api/serializers.py:89
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr "You must be administrator or owner of a room to add accesses to it."
#: core/api/serializers.py:509
#: core/api/serializers.py:534
msgid "This file extension is not allowed."
msgstr "This file extension is not allowed."
#: core/api/viewsets.py:1090
#: core/api/viewsets.py:1228
msgid "You have reached the maximum number of files for this type."
msgstr "You have reached the maximum number of files for this type."
@@ -144,51 +144,59 @@ msgstr "Failed to Stop"
msgid "Notification succeeded"
msgstr "Notification succeeded"
#: core/models.py:89
#: core/models.py:65
msgid "External process successful"
msgstr ""
#: core/models.py:67
msgid "External process failed"
msgstr ""
#: core/models.py:96
msgid "SCREEN_RECORDING"
msgstr "SCREEN_RECORDING"
#: core/models.py:90
#: core/models.py:97
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
#: core/models.py:96
#: core/models.py:103
msgid "Public Access"
msgstr "Public Access"
#: core/models.py:97
#: core/models.py:104
msgid "Trusted Access"
msgstr "Trusted Access"
#: core/models.py:98
#: core/models.py:105
msgid "Restricted Access"
msgstr "Restricted Access"
#: core/models.py:110
#: core/models.py:117
msgid "id"
msgstr "id"
#: core/models.py:111
#: core/models.py:118
msgid "primary key for the record as UUID"
msgstr "primary key for the record as UUID"
#: core/models.py:117
#: core/models.py:124
msgid "created on"
msgstr "created on"
#: core/models.py:118
#: core/models.py:125
msgid "date and time at which a record was created"
msgstr "date and time at which a record was created"
#: core/models.py:123
#: core/models.py:130
msgid "updated on"
msgstr "updated on"
#: core/models.py:124
#: core/models.py:131
msgid "date and time at which a record was last updated"
msgstr "date and time at which a record was last updated"
#: core/models.py:144
#: core/models.py:151
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -196,11 +204,11 @@ msgstr ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
#: core/models.py:150
#: core/models.py:157
msgid "sub"
msgstr "sub"
#: core/models.py:152
#: core/models.py:159
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -208,55 +216,55 @@ msgstr ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
#: core/models.py:161
#: core/models.py:168
msgid "identity email address"
msgstr "identity email address"
#: core/models.py:166
#: core/models.py:173
msgid "admin email address"
msgstr "admin email address"
#: core/models.py:168
#: core/models.py:175
msgid "full name"
msgstr "full name"
#: core/models.py:170
#: core/models.py:177
msgid "short name"
msgstr "short name"
#: core/models.py:176
#: core/models.py:183
msgid "language"
msgstr "language"
#: core/models.py:177
#: core/models.py:184
msgid "The language in which the user wants to see the interface."
msgstr "The language in which the user wants to see the interface."
#: core/models.py:183
#: core/models.py:190
msgid "The timezone in which the user wants to see times."
msgstr "The timezone in which the user wants to see times."
#: core/models.py:186
#: core/models.py:193
msgid "device"
msgstr "device"
#: core/models.py:188
#: core/models.py:195
msgid "Whether the user is a device or a real user."
msgstr "Whether the user is a device or a real user."
#: core/models.py:191
#: core/models.py:198
msgid "staff status"
msgstr "staff status"
#: core/models.py:193
#: core/models.py:200
msgid "Whether the user can log into this admin site."
msgstr "Whether the user can log into this admin site."
#: core/models.py:196
#: core/models.py:203
msgid "active"
msgstr "active"
#: core/models.py:199
#: core/models.py:206
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -264,63 +272,63 @@ msgstr ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
#: core/models.py:212
#: core/models.py:219
msgid "user"
msgstr "user"
#: core/models.py:213
#: core/models.py:220
msgid "users"
msgstr "users"
#: core/models.py:272
#: core/models.py:279
msgid "Resource"
msgstr "Resource"
#: core/models.py:273
#: core/models.py:280
msgid "Resources"
msgstr "Resources"
#: core/models.py:331
#: core/models.py:338
msgid "Resource access"
msgstr "Resource access"
#: core/models.py:332
#: core/models.py:339
msgid "Resource accesses"
msgstr "Resource accesses"
#: core/models.py:338
#: core/models.py:345
msgid "Resource access with this User and Resource already exists."
msgstr "Resource access with this User and Resource already exists."
#: core/models.py:394
#: core/models.py:402
msgid "Visio room configuration"
msgstr "Visio room configuration"
#: core/models.py:395
#: core/models.py:403
msgid "Values for Visio parameters to configure the room."
msgstr "Values for Visio parameters to configure the room."
#: core/models.py:402
#: core/models.py:410
msgid "Room PIN code"
msgstr "Room PIN code"
#: core/models.py:403
#: core/models.py:411
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr "Unique n-digit code that identifies this room in telephony mode."
#: core/models.py:409 core/models.py:563
#: core/models.py:417 core/models.py:571
msgid "Room"
msgstr "Room"
#: core/models.py:410
#: core/models.py:418
msgid "Rooms"
msgstr "Rooms"
#: core/models.py:574
#: core/models.py:582
msgid "Worker ID"
msgstr "Worker ID"
#: core/models.py:576
#: core/models.py:584
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -328,151 +336,159 @@ msgstr ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
#: core/models.py:584
#: core/models.py:592
msgid "Recording mode"
msgstr "Recording mode"
#: core/models.py:585
#: core/models.py:593
msgid "Defines the mode of recording being called."
msgstr "Defines the mode of recording being called."
#: core/models.py:590 core/models.py:591
#: core/models.py:598 core/models.py:599
msgid "Recording options"
msgstr "Recording options"
#: core/models.py:597
#: core/models.py:606
msgid "External Process ID"
msgstr ""
#: core/models.py:607
msgid "ID of the external process associated with the recording."
msgstr ""
#: core/models.py:613
msgid "Recording"
msgstr "Recording"
#: core/models.py:598
#: core/models.py:614
msgid "Recordings"
msgstr "Recordings"
#: core/models.py:706
#: core/models.py:724
msgid "Recording/user relation"
msgstr "Recording/user relation"
#: core/models.py:707
#: core/models.py:725
msgid "Recording/user relations"
msgstr "Recording/user relations"
#: core/models.py:713
#: core/models.py:731
msgid "This user is already in this recording."
msgstr "This user is already in this recording."
#: core/models.py:719
#: core/models.py:737
msgid "This team is already in this recording."
msgstr "This team is already in this recording."
#: core/models.py:725
#: core/models.py:743
msgid "Either user or team must be set, not both."
msgstr "Either user or team must be set, not both."
#: core/models.py:742
#: core/models.py:760
#, fuzzy
#| msgid "created on"
msgid "Create rooms"
msgstr "Create rooms"
#: core/models.py:743
#: core/models.py:761
msgid "List rooms"
msgstr "List rooms"
#: core/models.py:744
#: core/models.py:762
msgid "Retrieve room details"
msgstr "Retrieve room details"
#: core/models.py:745
#: core/models.py:763
#, fuzzy
#| msgid "updated on"
msgid "Update rooms"
msgstr "Update rooms"
#: core/models.py:746
#: core/models.py:764
msgid "Delete rooms"
msgstr "Delete rooms"
#: core/models.py:759
#: core/models.py:777
msgid "Application name"
msgstr "Application name"
#: core/models.py:760
#: core/models.py:778
msgid "Descriptive name for this application."
msgstr "Descriptive name for this application."
#: core/models.py:770
#: core/models.py:788
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr "Hashed on Save. Copy it now if this is a new secret."
#: core/models.py:781
#: core/models.py:799
msgid "Application"
msgstr "Application"
#: core/models.py:782
#: core/models.py:800
msgid "Applications"
msgstr "Applications"
#: core/models.py:805
#: core/models.py:823
msgid "Enter a valid domain"
msgstr "Enter a valid domain"
#: core/models.py:808
#: core/models.py:826
msgid "Domain"
msgstr "Domain"
#: core/models.py:809
#: core/models.py:827
msgid "Email domain this application can act on behalf of."
msgstr "Email domain this application can act on behalf of."
#: core/models.py:821
#: core/models.py:839
msgid "Application domain"
msgstr "Application domain"
#: core/models.py:822
#: core/models.py:840
msgid "Application domains"
msgstr "Application domains"
#: core/models.py:840
#: core/models.py:858
#, fuzzy
#| msgid "Recording"
msgid "Pending"
msgstr "Pending"
#: core/models.py:848
#: core/models.py:866
msgid "Ready"
msgstr "Ready"
#: core/models.py:854
#: core/models.py:872
msgid "Background image"
msgstr "Background image"
#: core/models.py:866
#: core/models.py:884
msgid "title"
msgstr "title"
#: core/models.py:890
#: core/models.py:908
msgid "Malware detection info when the analysis status is unsafe."
msgstr "Malware detection info when the analysis status is unsafe."
#: core/models.py:895
#: core/models.py:913
msgid "File"
msgstr "File"
#: core/models.py:896
#: core/models.py:914
msgid "Files"
msgstr "Files"
#: core/models.py:1000
#: core/models.py:1018
#, fuzzy
#| msgid "This user is already in this recording."
msgid "This file is already hard deleted."
msgstr "This file is already hard deleted."
#: core/models.py:1010
#: core/models.py:1028
msgid "To hard delete a file, it must first be soft deleted."
msgstr "To hard delete a file, it must first be soft deleted."
#: core/recording/event/notification.py:116
#: core/recording/event/notification.py:124
msgid "Your recording is ready"
msgstr "Your recording is ready"
@@ -572,7 +588,9 @@ msgstr "To keep this recording permanently:"
#: core/templates/mail/html/screen_recording.html:208
#: core/templates/mail/text/screen_recording.txt:13
msgid "Click the \"<a href=\"%(link)s\">Open</a>\" link below "
#, fuzzy
#| msgid "Click the \"<a href=\"%(link)s\">Open</a>\" link below "
msgid "Click the \"Open\" button below "
msgstr "Click the \"<a href=\"%(link)s\">Open</a>\" link below "
#: core/templates/mail/html/screen_recording.html:209
+100 -82
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-21 14:09+0000\n"
"POT-Creation-Date: 2026-05-26 16:20+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"
@@ -89,17 +89,17 @@ msgstr "Scopes"
msgid "Creator is me"
msgstr "Je suis le créateur"
#: core/api/serializers.py:88
#: core/api/serializers.py:89
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Vous devez être administrateur ou propriétaire d'une salle pour y ajouter "
"des accès."
#: core/api/serializers.py:509
#: core/api/serializers.py:534
msgid "This file extension is not allowed."
msgstr "Cette extension n'est pas autorisée"
#: core/api/viewsets.py:1090
#: core/api/viewsets.py:1228
msgid "You have reached the maximum number of files for this type."
msgstr "Vous avez atteint le nombre maximum de fichiers de ce type"
@@ -147,53 +147,61 @@ msgstr "Échec à l'arrêt"
msgid "Notification succeeded"
msgstr "Notification réussie"
#: core/models.py:89
#: core/models.py:65
msgid "External process successful"
msgstr ""
#: core/models.py:67
msgid "External process failed"
msgstr ""
#: core/models.py:96
msgid "SCREEN_RECORDING"
msgstr "ENREGISTREMENT_ÉCRAN"
#: core/models.py:90
#: core/models.py:97
msgid "TRANSCRIPT"
msgstr "TRANSCRIPTION"
#: core/models.py:96
#: core/models.py:103
msgid "Public Access"
msgstr "Accès public"
#: core/models.py:97
#: core/models.py:104
msgid "Trusted Access"
msgstr "Accès de confiance"
#: core/models.py:98
#: core/models.py:105
msgid "Restricted Access"
msgstr "Accès restreint"
#: core/models.py:110
#: core/models.py:117
msgid "id"
msgstr "id"
#: core/models.py:111
#: core/models.py:118
msgid "primary key for the record as UUID"
msgstr "clé primaire pour l'enregistrement sous forme d'UUID"
#: core/models.py:117
#: core/models.py:124
msgid "created on"
msgstr "créé le"
#: core/models.py:118
#: core/models.py:125
msgid "date and time at which a record was created"
msgstr "date et heure auxquelles un enregistrement a été créé"
#: core/models.py:123
#: core/models.py:130
msgid "updated on"
msgstr "mis à jour le"
#: core/models.py:124
#: core/models.py:131
msgid "date and time at which a record was last updated"
msgstr ""
"date et heure auxquelles un enregistrement a été mis à jour pour la dernière "
"fois"
#: core/models.py:144
#: core/models.py:151
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -201,11 +209,11 @@ msgstr ""
"Entrez un sub valide. Cette valeur ne peut contenir que des lettres, des "
"chiffres et les caractères @/./+/-/_."
#: core/models.py:150
#: core/models.py:157
msgid "sub"
msgstr "sub"
#: core/models.py:152
#: core/models.py:159
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -213,55 +221,55 @@ msgstr ""
"Optionnel pour les utilisateurs en attente ; requis lors de l'activation du "
"compte. 255 caractères maximum. Lettres, chiffres et @/./+/-/_ uniquement."
#: core/models.py:161
#: core/models.py:168
msgid "identity email address"
msgstr "adresse e-mail d'identité"
#: core/models.py:166
#: core/models.py:173
msgid "admin email address"
msgstr "adresse e-mail d'administrateur"
#: core/models.py:168
#: core/models.py:175
msgid "full name"
msgstr "nom complet"
#: core/models.py:170
#: core/models.py:177
msgid "short name"
msgstr "nom court"
#: core/models.py:176
#: core/models.py:183
msgid "language"
msgstr "langue"
#: core/models.py:177
#: core/models.py:184
msgid "The language in which the user wants to see the interface."
msgstr "La langue dans laquelle l'utilisateur souhaite voir l'interface."
#: core/models.py:183
#: core/models.py:190
msgid "The timezone in which the user wants to see times."
msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures."
#: core/models.py:186
#: core/models.py:193
msgid "device"
msgstr "appareil"
#: core/models.py:188
#: core/models.py:195
msgid "Whether the user is a device or a real user."
msgstr "Si l'utilisateur est un appareil ou un utilisateur réel."
#: core/models.py:191
#: core/models.py:198
msgid "staff status"
msgstr "statut du personnel"
#: core/models.py:193
#: core/models.py:200
msgid "Whether the user can log into this admin site."
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
#: core/models.py:196
#: core/models.py:203
msgid "active"
msgstr "actif"
#: core/models.py:199
#: core/models.py:206
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -269,65 +277,65 @@ msgstr ""
"Si cet utilisateur doit être traité comme actif. Désélectionnez cette option "
"au lieu de supprimer des comptes."
#: core/models.py:212
#: core/models.py:219
msgid "user"
msgstr "utilisateur"
#: core/models.py:213
#: core/models.py:220
msgid "users"
msgstr "utilisateurs"
#: core/models.py:272
#: core/models.py:279
msgid "Resource"
msgstr "Ressource"
#: core/models.py:273
#: core/models.py:280
msgid "Resources"
msgstr "Ressources"
#: core/models.py:331
#: core/models.py:338
msgid "Resource access"
msgstr "Accès aux ressources"
#: core/models.py:332
#: core/models.py:339
msgid "Resource accesses"
msgstr "Accès aux ressources"
#: core/models.py:338
#: core/models.py:345
msgid "Resource access with this User and Resource already exists."
msgstr ""
"L'accès à la ressource avec cet utilisateur et cette ressource existe déjà."
#: core/models.py:394
#: core/models.py:402
msgid "Visio room configuration"
msgstr "Configuration de la salle de visioconférence"
#: core/models.py:395
#: core/models.py:403
msgid "Values for Visio parameters to configure the room."
msgstr "Valeurs des paramètres de visioconférence pour configurer la salle."
#: core/models.py:402
#: core/models.py:410
msgid "Room PIN code"
msgstr "Code PIN de la salle"
#: core/models.py:403
#: core/models.py:411
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Code unique à n chiffres qui identifie cette salle en mode téléphonique."
#: core/models.py:409 core/models.py:563
#: core/models.py:417 core/models.py:571
msgid "Room"
msgstr "Salle"
#: core/models.py:410
#: core/models.py:418
msgid "Rooms"
msgstr "Salles"
#: core/models.py:574
#: core/models.py:582
msgid "Worker ID"
msgstr "ID du Worker"
#: core/models.py:576
#: core/models.py:584
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -335,150 +343,158 @@ msgstr ""
"Entrez un identifiant pour l'enregistrement du Worker. Cet identifiant est "
"conservé même lorsque le Worker s'arrête, permettant un suivi facile."
#: core/models.py:584
#: core/models.py:592
msgid "Recording mode"
msgstr "Mode d'enregistrement"
#: core/models.py:585
#: core/models.py:593
msgid "Defines the mode of recording being called."
msgstr "Définit le mode d'enregistrement appelé."
#: core/models.py:590 core/models.py:591
#: core/models.py:598 core/models.py:599
msgid "Recording options"
msgstr "Options d'enregistrement"
#: core/models.py:597
#: core/models.py:606
msgid "External Process ID"
msgstr ""
#: core/models.py:607
msgid "ID of the external process associated with the recording."
msgstr ""
#: core/models.py:613
msgid "Recording"
msgstr "Enregistrement"
#: core/models.py:598
#: core/models.py:614
msgid "Recordings"
msgstr "Enregistrements"
#: core/models.py:706
#: core/models.py:724
msgid "Recording/user relation"
msgstr "Relation enregistrement/utilisateur"
#: core/models.py:707
#: core/models.py:725
msgid "Recording/user relations"
msgstr "Relations enregistrement/utilisateur"
#: core/models.py:713
#: core/models.py:731
msgid "This user is already in this recording."
msgstr "Cet utilisateur est déjà dans cet enregistrement."
#: core/models.py:719
#: core/models.py:737
msgid "This team is already in this recording."
msgstr "Cette équipe est déjà dans cet enregistrement."
#: core/models.py:725
#: core/models.py:743
msgid "Either user or team must be set, not both."
msgstr "Soit l'utilisateur, soit l'équipe doit être défini, pas les deux."
#: core/models.py:742
#: core/models.py:760
msgid "Create rooms"
msgstr "Créer des salles"
#: core/models.py:743
#: core/models.py:761
msgid "List rooms"
msgstr "Lister les salles"
#: core/models.py:744
#: core/models.py:762
msgid "Retrieve room details"
msgstr "Afficher les détails dune salle"
#: core/models.py:745
#: core/models.py:763
msgid "Update rooms"
msgstr "Mettre à jour les salles"
#: core/models.py:746
#: core/models.py:764
msgid "Delete rooms"
msgstr "Supprimer les salles"
#: core/models.py:759
#: core/models.py:777
msgid "Application name"
msgstr "Nom de lapplication"
#: core/models.py:760
#: core/models.py:778
msgid "Descriptive name for this application."
msgstr "Nom descriptif de cette application."
#: core/models.py:770
#: core/models.py:788
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr ""
"Haché lors de lenregistrement. Copiez-le maintenant sil sagit dun "
"nouveau secret."
#: core/models.py:781
#: core/models.py:799
msgid "Application"
msgstr "Application"
#: core/models.py:782
#: core/models.py:800
msgid "Applications"
msgstr "Applications"
#: core/models.py:805
#: core/models.py:823
msgid "Enter a valid domain"
msgstr "Saisissez un domaine valide"
#: core/models.py:808
#: core/models.py:826
msgid "Domain"
msgstr "Domaine"
#: core/models.py:809
#: core/models.py:827
msgid "Email domain this application can act on behalf of."
msgstr "Domaine de messagerie au nom duquel cette application peut agir."
#: core/models.py:821
#: core/models.py:839
msgid "Application domain"
msgstr "Domaine dapplication"
#: core/models.py:822
#: core/models.py:840
msgid "Application domains"
msgstr "Domaines dapplication"
#: core/models.py:840
#: core/models.py:858
msgid "Pending"
msgstr "En attente"
#: core/models.py:848
#: core/models.py:866
msgid "Ready"
msgstr "Prêt"
#: core/models.py:854
#: core/models.py:872
msgid "Background image"
msgstr "Image de fond"
#: core/models.py:866
#: core/models.py:884
msgid "title"
msgstr "Titre"
#: core/models.py:890
#: core/models.py:908
msgid "Malware detection info when the analysis status is unsafe."
msgstr ""
"Information concernant la détection de Malware cand le statut n'est pas sain"
#: core/models.py:895
#: core/models.py:913
msgid "File"
msgstr "Fichier"
#: core/models.py:896
#: core/models.py:914
msgid "Files"
msgstr "Fichiers"
#: core/models.py:1000
#: core/models.py:1018
#, fuzzy
#| msgid "This user is already in this recording."
msgid "This file is already hard deleted."
msgstr "Ce fichier a été supprimé."
#: core/models.py:1010
#: core/models.py:1028
msgid "To hard delete a file, it must first be soft deleted."
msgstr ""
"Pour supprimer définitivement un fichier il doit d'abord avoir été marqué "
"comme supprimé (soft delete)"
#: core/recording/event/notification.py:116
#: core/recording/event/notification.py:124
msgid "Your recording is ready"
msgstr "Votre enregistrement est prêt"
@@ -578,7 +594,9 @@ msgstr "Pour conserver cet enregistrement de façon permanente :"
#: core/templates/mail/html/screen_recording.html:208
#: core/templates/mail/text/screen_recording.txt:13
msgid "Click the \"<a href=\"%(link)s\">Open</a>\" link below "
#, fuzzy
#| msgid "Click the \"<a href=\"%(link)s\">Open</a>\" link below "
msgid "Click the \"Open\" button below "
msgstr "Cliquez sur le lien \"<a href=\"%(link)s\">Ouvrir</a>\" ci-dessous "
#: core/templates/mail/html/screen_recording.html:209
+100 -82
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-21 14:09+0000\n"
"POT-Creation-Date: 2026-05-26 16:20+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"
@@ -88,16 +88,16 @@ msgstr "Scopes"
msgid "Creator is me"
msgstr "Maker ben ik"
#: core/api/serializers.py:88
#: core/api/serializers.py:89
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Je moet beheerder of eigenaar van een ruimte zijn om toegang toe te voegen."
#: core/api/serializers.py:509
#: core/api/serializers.py:534
msgid "This file extension is not allowed."
msgstr "Deze bestandsextensie is niet toegestaan."
#: core/api/viewsets.py:1090
#: core/api/viewsets.py:1228
msgid "You have reached the maximum number of files for this type."
msgstr "Het maximale aantal bestanden voor dit type is bereikt."
@@ -145,51 +145,59 @@ msgstr "Stoppen mislukt"
msgid "Notification succeeded"
msgstr "Notificatie geslaagd"
#: core/models.py:89
#: core/models.py:65
msgid "External process successful"
msgstr ""
#: core/models.py:67
msgid "External process failed"
msgstr ""
#: core/models.py:96
msgid "SCREEN_RECORDING"
msgstr "SCHERM_OPNAME"
#: core/models.py:90
#: core/models.py:97
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
#: core/models.py:96
#: core/models.py:103
msgid "Public Access"
msgstr "Openbare toegang"
#: core/models.py:97
#: core/models.py:104
msgid "Trusted Access"
msgstr "Vertrouwde toegang"
#: core/models.py:98
#: core/models.py:105
msgid "Restricted Access"
msgstr "Beperkte toegang"
#: core/models.py:110
#: core/models.py:117
msgid "id"
msgstr "id"
#: core/models.py:111
#: core/models.py:118
msgid "primary key for the record as UUID"
msgstr "primaire sleutel voor het record als UUID"
#: core/models.py:117
#: core/models.py:124
msgid "created on"
msgstr "aangemaakt op"
#: core/models.py:118
#: core/models.py:125
msgid "date and time at which a record was created"
msgstr "datum en tijd waarop een record werd aangemaakt"
#: core/models.py:123
#: core/models.py:130
msgid "updated on"
msgstr "bijgewerkt op"
#: core/models.py:124
#: core/models.py:131
msgid "date and time at which a record was last updated"
msgstr "datum en tijd waarop een record voor het laatst werd bijgewerkt"
#: core/models.py:144
#: core/models.py:151
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -197,11 +205,11 @@ msgstr ""
"Voer een geldige sub in. Deze waarde mag alleen letters, cijfers en @/./+/-/"
"_ tekens bevatten."
#: core/models.py:150
#: core/models.py:157
msgid "sub"
msgstr "sub"
#: core/models.py:152
#: core/models.py:159
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -209,55 +217,55 @@ msgstr ""
"Optioneel voor gebruikers in afwachting; vereist bij accountactivering. "
"Maximum 255 tekens. Alleen letters, cijfers en @/./+/-/_ toegestaan."
#: core/models.py:161
#: core/models.py:168
msgid "identity email address"
msgstr "identiteit e-mailadres"
#: core/models.py:166
#: core/models.py:173
msgid "admin email address"
msgstr "beheerder e-mailadres"
#: core/models.py:168
#: core/models.py:175
msgid "full name"
msgstr "volledige naam"
#: core/models.py:170
#: core/models.py:177
msgid "short name"
msgstr "korte naam"
#: core/models.py:176
#: core/models.py:183
msgid "language"
msgstr "taal"
#: core/models.py:177
#: core/models.py:184
msgid "The language in which the user wants to see the interface."
msgstr "De taal waarin de gebruiker de interface wil zien."
#: core/models.py:183
#: core/models.py:190
msgid "The timezone in which the user wants to see times."
msgstr "De tijdzone waarin de gebruiker tijden wil zien."
#: core/models.py:186
#: core/models.py:193
msgid "device"
msgstr "apparaat"
#: core/models.py:188
#: core/models.py:195
msgid "Whether the user is a device or a real user."
msgstr "Of de gebruiker een apparaat is of een echte gebruiker."
#: core/models.py:191
#: core/models.py:198
msgid "staff status"
msgstr "personeelsstatus"
#: core/models.py:193
#: core/models.py:200
msgid "Whether the user can log into this admin site."
msgstr "Of de gebruiker kan inloggen op deze beheersite."
#: core/models.py:196
#: core/models.py:203
msgid "active"
msgstr "actief"
#: core/models.py:199
#: core/models.py:206
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -265,64 +273,64 @@ msgstr ""
"Of deze gebruiker als actief moet worden behandeld. Deselecteer dit in "
"plaats van accounts te verwijderen."
#: core/models.py:212
#: core/models.py:219
msgid "user"
msgstr "gebruiker"
#: core/models.py:213
#: core/models.py:220
msgid "users"
msgstr "gebruikers"
#: core/models.py:272
#: core/models.py:279
msgid "Resource"
msgstr "Bron"
#: core/models.py:273
#: core/models.py:280
msgid "Resources"
msgstr "Bronnen"
#: core/models.py:331
#: core/models.py:338
msgid "Resource access"
msgstr "Brontoegang"
#: core/models.py:332
#: core/models.py:339
msgid "Resource accesses"
msgstr "Brontoegangsrechten"
#: core/models.py:338
#: core/models.py:345
msgid "Resource access with this User and Resource already exists."
msgstr "Brontoegang met deze gebruiker en bron bestaat al."
#: core/models.py:394
#: core/models.py:402
msgid "Visio room configuration"
msgstr "Visio-ruimteconfiguratie"
#: core/models.py:395
#: core/models.py:403
msgid "Values for Visio parameters to configure the room."
msgstr "Waarden voor Visio-parameters om de ruimte te configureren."
#: core/models.py:402
#: core/models.py:410
msgid "Room PIN code"
msgstr "Pincode van de kamer"
#: core/models.py:403
#: core/models.py:411
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Unieke n-cijferige code die deze kamer identificeert in telefonie-modus."
#: core/models.py:409 core/models.py:563
#: core/models.py:417 core/models.py:571
msgid "Room"
msgstr "Ruimte"
#: core/models.py:410
#: core/models.py:418
msgid "Rooms"
msgstr "Ruimtes"
#: core/models.py:574
#: core/models.py:582
msgid "Worker ID"
msgstr "Worker ID"
#: core/models.py:576
#: core/models.py:584
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -330,140 +338,148 @@ msgstr ""
"Voer een identificatie in voor de worker-opname. Deze ID blijft behouden, "
"zelfs wanneer de worker stopt, waardoor eenvoudige tracking mogelijk is."
#: core/models.py:584
#: core/models.py:592
msgid "Recording mode"
msgstr "Opnamemodus"
#: core/models.py:585
#: core/models.py:593
msgid "Defines the mode of recording being called."
msgstr "Definieert de modus van opname die wordt aangeroepen."
#: core/models.py:590 core/models.py:591
#: core/models.py:598 core/models.py:599
msgid "Recording options"
msgstr "Opnameopties"
#: core/models.py:597
#: core/models.py:606
msgid "External Process ID"
msgstr ""
#: core/models.py:607
msgid "ID of the external process associated with the recording."
msgstr ""
#: core/models.py:613
msgid "Recording"
msgstr "Opname"
#: core/models.py:598
#: core/models.py:614
msgid "Recordings"
msgstr "Opnames"
#: core/models.py:706
#: core/models.py:724
msgid "Recording/user relation"
msgstr "Opname/gebruiker-relatie"
#: core/models.py:707
#: core/models.py:725
msgid "Recording/user relations"
msgstr "Opname/gebruiker-relaties"
#: core/models.py:713
#: core/models.py:731
msgid "This user is already in this recording."
msgstr "Deze gebruiker is al in deze opname."
#: core/models.py:719
#: core/models.py:737
msgid "This team is already in this recording."
msgstr "Dit team is al in deze opname."
#: core/models.py:725
#: core/models.py:743
msgid "Either user or team must be set, not both."
msgstr "Ofwel gebruiker of team moet worden ingesteld, niet beide."
#: core/models.py:742
#: core/models.py:760
msgid "Create rooms"
msgstr "Ruimtes aanmaken"
#: core/models.py:743
#: core/models.py:761
msgid "List rooms"
msgstr "Ruimtes weergeven"
#: core/models.py:744
#: core/models.py:762
msgid "Retrieve room details"
msgstr "Details van een ruimte ophalen"
#: core/models.py:745
#: core/models.py:763
msgid "Update rooms"
msgstr "Ruimtes bijwerken"
#: core/models.py:746
#: core/models.py:764
msgid "Delete rooms"
msgstr "Ruimtes verwijderen"
#: core/models.py:759
#: core/models.py:777
msgid "Application name"
msgstr "Naam van de applicatie"
#: core/models.py:760
#: core/models.py:778
msgid "Descriptive name for this application."
msgstr "Beschrijvende naam voor deze applicatie."
#: core/models.py:770
#: core/models.py:788
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr ""
"Wordt gehasht bij het opslaan. Kopieer het nu als dit een nieuw geheim is."
#: core/models.py:781
#: core/models.py:799
msgid "Application"
msgstr "Applicatie"
#: core/models.py:782
#: core/models.py:800
msgid "Applications"
msgstr "Applicaties"
#: core/models.py:805
#: core/models.py:823
msgid "Enter a valid domain"
msgstr "Voer een geldig domein in"
#: core/models.py:808
#: core/models.py:826
msgid "Domain"
msgstr "Domein"
#: core/models.py:809
#: core/models.py:827
msgid "Email domain this application can act on behalf of."
msgstr "E-maildomein namens welke deze applicatie kan handelen."
#: core/models.py:821
#: core/models.py:839
msgid "Application domain"
msgstr "Applicatiedomein"
#: core/models.py:822
#: core/models.py:840
msgid "Application domains"
msgstr "Applicatiedomeinen"
#: core/models.py:840
#: core/models.py:858
msgid "Pending"
msgstr "In afwachting"
#: core/models.py:848
#: core/models.py:866
msgid "Ready"
msgstr "Klaar"
#: core/models.py:854
#: core/models.py:872
msgid "Background image"
msgstr "Achtergrondafbeelding"
#: core/models.py:866
#: core/models.py:884
msgid "title"
msgstr "Titel"
#: core/models.py:890
#: core/models.py:908
msgid "Malware detection info when the analysis status is unsafe."
msgstr "Informatie over malwaredetectie wanneer de analysestatus onveilig is."
#: core/models.py:895
#: core/models.py:913
msgid "File"
msgstr "Bestand"
#: core/models.py:896
#: core/models.py:914
msgid "Files"
msgstr "Bestanden"
#: core/models.py:1000
#: core/models.py:1018
msgid "This file is already hard deleted."
msgstr "Dit bestand is al definitief verwijderd."
#: core/models.py:1010
#: core/models.py:1028
#, fuzzy
#| msgid "To hard delete a file, it must first be soft deleted."
msgid "To hard delete a file, it must first be soft deleted."
@@ -471,7 +487,7 @@ msgstr ""
"Om een bestand definitief te verwijderen, moet het eerst zacht verwijderd "
"zijn."
#: core/recording/event/notification.py:116
#: core/recording/event/notification.py:124
msgid "Your recording is ready"
msgstr "Je opname is klaar"
@@ -571,7 +587,9 @@ msgstr "Om deze opname permanent te bewaren:"
#: core/templates/mail/html/screen_recording.html:208
#: core/templates/mail/text/screen_recording.txt:13
msgid "Click the \"<a href=\"%(link)s\">Open</a>\" link below "
#, fuzzy
#| msgid "Click the \"<a href=\"%(link)s\">Open</a>\" link below "
msgid "Click the \"Open\" button below "
msgstr "Klik op de \"<a href=\"%(link)s\">Openen</a>\"-link hieronder "
#: core/templates/mail/html/screen_recording.html:209
+3
View File
@@ -744,6 +744,9 @@ class Base(Configuration):
SUMMARY_SERVICE_API_TOKEN = SecretFileValue(
None, environ_name="SUMMARY_SERVICE_API_TOKEN", environ_prefix=None
)
SUMMARY_SERVICE_WEBHOOK_API_TOKEN = SecretFileValue(
None, environ_name="SUMMARY_SERVICE_WEBHOOK_API_TOKEN", environ_prefix=None
)
SCREEN_RECORDING_BASE_URL = values.Value(
None, environ_name="SCREEN_RECORDING_BASE_URL", environ_prefix=None
)
@@ -105,8 +105,9 @@ backend:
RECORDING_ENABLE: True
RECORDING_STORAGE_EVENT_ENABLE: True
RECORDING_STORAGE_EVENT_TOKEN: password
SUMMARY_SERVICE_ENDPOINT: http://meet-summary:80/api/v1/tasks/
SUMMARY_SERVICE_ENDPOINT: http://meet-summary:80/api/v2/async-jobs/transcribe/
SUMMARY_SERVICE_API_TOKEN: password
SUMMARY_SERVICE_WEBHOOK_API_TOKEN: webhook-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
@@ -6,7 +6,26 @@ _summaryEnvVars: &summaryEnvVars
AWS_S3_ACCESS_KEY_ID: meet
AWS_S3_SECRET_ACCESS_KEY: password
AWS_S3_SECURE_ACCESS: False
AUTHORIZED_TENANTS: '[{"id": "dictaphone", "api_key": "dictaphone_token", "webhook_url": "http://dictaphone-backend.dictaphone.svc.cluster.local/api/v1.0/ai-jobs/webhook/", "webhook_api_key": "token_summary"}]'
# Note: setting allowed_push_to_docs false because it won't work in dev mode
AUTHORIZED_TENANTS: >
[
{
"id": "dictaphone",
"api_key": "dictaphone_token",
"webhook_url": "http://dictaphone-backend.dictaphone.svc.cluster.local/api/v1.0/ai-jobs/webhook/",
"webhook_api_key": "token_summary",
"allowed_push_to_docs": false
},
{
"id": "visio",
"api_key": "password",
"webhook_url": "https://meet.127.0.0.1.nip.io/api/v1.0/recordings/external-process-hook/",
"webhook_api_key": "webhook-password",
"allowed_push_to_docs": false
}
]
SSL_CERT_FILE: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
IS_DOCS_INTEGRATION_ENABLED: false
WHISPERX_API_KEY:
secretKeyRef:
name: secret-dev
@@ -109,8 +128,9 @@ backend:
RECORDING_ENABLE: True
RECORDING_STORAGE_EVENT_ENABLE: True
RECORDING_STORAGE_EVENT_TOKEN: password
SUMMARY_SERVICE_ENDPOINT: http://meet-summary:80/api/v1/tasks/
SUMMARY_SERVICE_ENDPOINT: http://meet-summary:80/api/v2/async-jobs/transcribe/
SUMMARY_SERVICE_API_TOKEN: password
SUMMARY_SERVICE_WEBHOOK_API_TOKEN: webhook-password
RECORDING_DOWNLOAD_BASE_URL: https://meet.127.0.0.1.nip.io/recording
ROOM_TELEPHONY_ENABLED: True
ROOM_TELEPHONY_DEFAULT_COUNTRY: 'FR'
@@ -258,6 +278,23 @@ celerySummaryBackend:
- "-Q"
- "call-webhook-queue-v2"
# Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false
extraVolumeMounts:
- name: certs
mountPath: /usr/local/lib/python3.13/site-packages/certifi/cacert.pem
subPath: cacert.pem
# Extra volumes to manage our local custom CA and avoid to set ssl_verify: false
extraVolumes:
- name: certs
configMap:
name: certifi
items:
- key: cacert.pem
path: cacert.pem
agentMetadata:
replicas: 1
envVars:
-6
View File
@@ -155,12 +155,6 @@ class FileService:
raise ValueError("Invalid cloud_storage_url")
extension = Path(urlparse(cloud_storage_url).path).suffix.lower()
if extension not in self._allowed_extensions:
logger.warning(
"Invalid file extension '%s' from cloud_storage_url", extension
)
raise ValueError(f"Invalid file extension '{extension}'")
try:
with requests.get(
cloud_storage_url,
-1
View File
@@ -11,7 +11,6 @@ from summary.main import app
def get_settings_override():
"""Return settings for tests."""
return Settings(
v1_tenant_id="test-tenant",
authorized_tenants=(
AuthorizedTenant(
webhook_url="https://example.com/webhook",