mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-27 04:09:26 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d7f2957f9 | |||
| d83d3ccc18 | |||
| 52a9a583e8 |
@@ -8,17 +8,6 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- 🔒️(backend) add validation of Room.configuration
|
||||
- ✨(helm) add support multiple transcribe worker / endpoint #1247
|
||||
|
||||
### Fixed
|
||||
|
||||
- ♻(frontend) standardize role terminology across localizations
|
||||
- 🐛(backend) make start-recording atomic and fault-tolerant
|
||||
- 🔒️(frontend) room ids are generated with non-cryptographic rand
|
||||
|
||||
## [1.15.0] - 2026-04-30
|
||||
|
||||
### Added
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ k8s_resource('meet-backend', resource_deps=['postgresql', 'minio', 'redis', 'liv
|
||||
k8s_resource('meet-celery-backend', resource_deps=['redis'])
|
||||
k8s_resource('meet-celery-summarize', resource_deps=['redis'])
|
||||
k8s_resource('meet-celery-summary-backend', resource_deps=['redis'])
|
||||
k8s_resource('meet-celery-transcribe-default', 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'])
|
||||
|
||||
@@ -101,12 +101,6 @@ update_npm_version "mail"
|
||||
# Update backend pyproject.toml
|
||||
update_python_version "backend"
|
||||
|
||||
# Run uv lock in backend
|
||||
print_info "Running uv lock in backend..."
|
||||
cd "src/backend"
|
||||
uv lock
|
||||
cd -
|
||||
|
||||
# Update summary pyproject.toml
|
||||
update_python_version "summary"
|
||||
|
||||
@@ -155,7 +149,6 @@ echo " - src/frontend/package.json"
|
||||
echo " - src/sdk/package.json"
|
||||
echo " - src/mail/package.json"
|
||||
echo " - src/backend/pyproject.toml"
|
||||
echo " - src/backend/uv.lock"
|
||||
echo " - src/summary/pyproject.toml"
|
||||
echo " - src/agents/pyproject.toml"
|
||||
echo " - CHANGELOG.md"
|
||||
|
||||
@@ -44,15 +44,14 @@ server {
|
||||
add_header Pragma "no-cache" always;
|
||||
add_header Expires 0 always;
|
||||
|
||||
set $ms_domains "https://*.live.com https://*.office.com https://*.microsoft.com https://*.office365.com https://*.sharepoint.com https://*.cloud.microsoft";
|
||||
set $ministries_domains "https://courrieljf.ccomptes.fr";
|
||||
set $ms_domains "https://*.live.com https://*.office.com https://*.microsoft.com https://*.office365.com https://*.sharepoint.com";
|
||||
|
||||
set $nonce $request_id;
|
||||
|
||||
set $csp "upgrade-insecure-requests; ";
|
||||
set $csp "${csp}frame-ancestors ${ms_domains} ${ministries_domains}; ";
|
||||
set $csp "${csp}frame-ancestors ${ms_domains}; ";
|
||||
set $csp "${csp}script-src 'nonce-${nonce}' 'strict-dynamic'; ";
|
||||
set $csp "${csp}connect-src 'self' ${ms_domains} ${ministries_domains}; ";
|
||||
set $csp "${csp}connect-src 'self' ${ms_domains}; ";
|
||||
set $csp "${csp}frame-src 'none'; ";
|
||||
set $csp "${csp}object-src 'none'; ";
|
||||
set $csp "${csp}base-uri 'none'; ";
|
||||
|
||||
Generated
+27
-23
@@ -36,7 +36,7 @@
|
||||
"source-map-loader": "^5.0.0",
|
||||
"webpack": "^5.95.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "5.2.1"
|
||||
"webpack-dev-server": "5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@apidevtools/json-schema-ref-parser": {
|
||||
@@ -8996,6 +8996,23 @@
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-entities": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
|
||||
"integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/mdevils"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://patreon.com/mdevils"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
|
||||
@@ -15654,16 +15671,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.1.tgz",
|
||||
"integrity": "sha512-ml/0HIj9NLpVKOMq+SuBPLHcmbG+TGIjXRHsYfZwocUBIqEvws8NnS/V9AFQ5FKP+tgn5adwVwRrTEpGL33QFQ==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.1.0.tgz",
|
||||
"integrity": "sha512-aQpaN81X6tXie1FoOB7xlMfCsN19pSvRAeYUHOdFWOlhpQ/LlbfTqYwwmEDFV0h8GGuqmCmKmT+pxcUV/Nt2gQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/bonjour": "^3.5.13",
|
||||
"@types/connect-history-api-fallback": "^1.5.4",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-serve-static-core": "^4.17.21",
|
||||
"@types/serve-index": "^1.9.4",
|
||||
"@types/serve-static": "^1.15.5",
|
||||
"@types/sockjs": "^0.3.36",
|
||||
@@ -15674,9 +15690,10 @@
|
||||
"colorette": "^2.0.10",
|
||||
"compression": "^1.7.4",
|
||||
"connect-history-api-fallback": "^2.0.0",
|
||||
"express": "^4.21.2",
|
||||
"express": "^4.19.2",
|
||||
"graceful-fs": "^4.2.6",
|
||||
"http-proxy-middleware": "^2.0.7",
|
||||
"html-entities": "^2.4.0",
|
||||
"http-proxy-middleware": "^2.0.3",
|
||||
"ipaddr.js": "^2.1.0",
|
||||
"launch-editor": "^2.6.1",
|
||||
"open": "^10.0.3",
|
||||
@@ -15711,19 +15728,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/@types/express-serve-static-core": {
|
||||
"version": "4.19.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz",
|
||||
"integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/qs": "*",
|
||||
"@types/range-parser": "*",
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/define-lazy-prop": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
|
||||
@@ -15767,9 +15771,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/ws": {
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
|
||||
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"source-map-loader": "^5.0.0",
|
||||
"webpack": "^5.95.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "5.2.1"
|
||||
"webpack-dev-server": "5.1.0"
|
||||
},
|
||||
"prettier": "office-addin-prettier-config",
|
||||
"browserslist": [
|
||||
|
||||
Generated
+4169
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "thunderbird",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "web-ext run",
|
||||
"lint": "web-ext lint --source-dir=./src"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"web-ext": "^10.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { buildMeetingMessage } from "./lib/meeting-message.js";
|
||||
|
||||
console.log("[meeting-link] background loaded at", new Date().toISOString());
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log("[meeting-link] browser.calendar exists?", !!browser.calendar);
|
||||
if (browser.calendar) {
|
||||
console.log("[meeting-link] calendar namespace keys:",
|
||||
Object.keys(browser.calendar));
|
||||
}
|
||||
|
||||
// Try the most basic read call — list configured calendars.
|
||||
// The exact method name varies between experiment versions; we'll
|
||||
// try the common one first.
|
||||
if (browser.calendar.calendars?.query) {
|
||||
const calendars = await browser.calendar.calendars.query({});
|
||||
console.log("[meeting-link] calendars found:", calendars.length, calendars);
|
||||
} else {
|
||||
console.warn("[meeting-link] calendars.query not present — namespace shape:",
|
||||
browser.calendar);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error("[meeting-link] calendar probe failed:", err);
|
||||
}
|
||||
})();
|
||||
|
||||
// Hardcoded for Spike 1. Spike 2 replaces this with a fetch() to your API.
|
||||
const STUB_MEETING_DATA = {
|
||||
url: "https://meet.example.com/m/abc-123-xyz",
|
||||
telephony: {
|
||||
phone_number: "+33123456789",
|
||||
pin_code: "1234567890",
|
||||
},
|
||||
};
|
||||
|
||||
browser.runtime.onMessage.addListener(async (msg) => {
|
||||
if (msg?.type === "GET_MEETING_MESSAGE") {
|
||||
try {
|
||||
const built = buildMeetingMessage(STUB_MEETING_DATA);
|
||||
return { ok: true, ...built };
|
||||
} catch (err) {
|
||||
console.error("[meeting-link] build failed", err);
|
||||
return { ok: false, error: String(err.message || err) };
|
||||
}
|
||||
}
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Meeting Link (dev)",
|
||||
"version": "0.1.0",
|
||||
"description": "Spike 0 — verifying the dev loop",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "meeting-link-dev@yourcompany.example",
|
||||
"strict_min_version": "128.0"
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"scripts": ["background.js"],
|
||||
"type": "module"
|
||||
},
|
||||
"compose_action": {
|
||||
"default_title": "Insert meeting link",
|
||||
"default_popup": "popup/popup.html",
|
||||
"default_icon": "icons/icon-32.png"
|
||||
},
|
||||
"permissions": ["compose"],
|
||||
"experiment_apis": {
|
||||
"calendar_provider": {
|
||||
"schema": "experiments/calendar/schema/calendar-provider.json",
|
||||
"parent": {
|
||||
"scopes": ["addon_parent"],
|
||||
"paths": [["calendar", "provider"]],
|
||||
"script": "experiments/calendar/parent/ext-calendar-provider.js",
|
||||
"events": ["startup"]
|
||||
}
|
||||
},
|
||||
"calendar_calendars": {
|
||||
"schema": "experiments/calendar/schema/calendar-calendars.json",
|
||||
"parent": {
|
||||
"scopes": ["addon_parent"],
|
||||
"paths": [["calendar", "calendars"]],
|
||||
"script": "experiments/calendar/parent/ext-calendar-calendars.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Meeting link</title>
|
||||
<style>
|
||||
body { font: 13px system-ui; padding: 12px; min-width: 240px; margin: 0; }
|
||||
button { font: inherit; padding: 6px 12px; cursor: pointer; }
|
||||
#status { margin-top: 8px; color: #555; min-height: 1.2em; white-space: pre-wrap; }
|
||||
#status.error { color: #b00020; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button id="insert">Insert meeting link</button>
|
||||
<div id="status"></div>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,65 @@
|
||||
const insertBtn = document.getElementById("insert");
|
||||
const statusEl = document.getElementById("status");
|
||||
|
||||
function setStatus(text, isError = false) {
|
||||
statusEl.textContent = text;
|
||||
statusEl.classList.toggle("error", isError);
|
||||
}
|
||||
|
||||
insertBtn.addEventListener("click", async () => {
|
||||
insertBtn.disabled = true;
|
||||
setStatus("Generating link…");
|
||||
|
||||
try {
|
||||
// 1. Find the compose tab this popup belongs to.
|
||||
// A compose_action popup is anchored to a compose window, so the
|
||||
// "active tab in the current window" is the compose tab itself.
|
||||
const [composeTab] = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
if (!composeTab) throw new Error("No compose tab found");
|
||||
|
||||
// 2. Read the current compose state — we need to know if we're
|
||||
// in HTML mode or plain-text mode, and we need the existing body
|
||||
// so we can append rather than overwrite.
|
||||
const details = await browser.compose.getComposeDetails(composeTab.id);
|
||||
|
||||
// 3. Ask the background to produce the meeting message.
|
||||
const reply = await browser.runtime.sendMessage({
|
||||
type: "GET_MEETING_MESSAGE",
|
||||
});
|
||||
if (!reply?.ok) throw new Error(reply?.error || "Background error");
|
||||
|
||||
// 4. Append to the existing body in the right format.
|
||||
if (details.isPlainText) {
|
||||
const newBody = (details.plainTextBody || "") + "\n\n" + reply.text;
|
||||
await browser.compose.setComposeDetails(composeTab.id, {
|
||||
plainTextBody: newBody,
|
||||
});
|
||||
} else {
|
||||
const newBody = appendHtmlBeforeBodyEnd(details.body || "", reply.html);
|
||||
await browser.compose.setComposeDetails(composeTab.id, {
|
||||
body: newBody,
|
||||
});
|
||||
}
|
||||
|
||||
setStatus("Inserted ✓");
|
||||
setTimeout(() => window.close(), 600);
|
||||
} catch (err) {
|
||||
console.error("[meeting-link] popup insert failed", err);
|
||||
setStatus("Failed: " + (err.message || err), true);
|
||||
insertBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Append HTML right before </body>, or fall back to concatenation if no
|
||||
* </body> tag is present (Thunderbird's compose body is usually a full
|
||||
* HTML document, but be defensive).
|
||||
*/
|
||||
function appendHtmlBeforeBodyEnd(currentHtml, fragment) {
|
||||
const idx = currentHtml.toLowerCase().lastIndexOf("</body>");
|
||||
if (idx === -1) return currentHtml + fragment;
|
||||
return currentHtml.slice(0, idx) + fragment + currentHtml.slice(idx);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// web-ext-config.cjs
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
|
||||
function thunderbirdBinary() {
|
||||
if (process.env.WEB_EXT_FIREFOX) return process.env.WEB_EXT_FIREFOX;
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
return "/Applications/Thunderbird Beta.app/Contents/MacOS/thunderbird";
|
||||
case "linux":
|
||||
return "/usr/bin/thunderbird"; // adjust to your install
|
||||
case "win32":
|
||||
return "C:\\Program Files\\Mozilla Thunderbird\\thunderbird.exe";
|
||||
default:
|
||||
throw new Error("Unsupported platform: " + process.platform);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sourceDir: "./src",
|
||||
artifactsDir: "./web-ext-artifacts",
|
||||
run: {
|
||||
firefox: thunderbirdBinary(),
|
||||
firefoxProfile: path.join(os.homedir(), ".thunderbird-dev-profile"),
|
||||
profileCreateIfMissing: true,
|
||||
keepProfileChanges: true,
|
||||
browserConsole: true,
|
||||
},
|
||||
ignoreFiles: ["package-lock.json", "web-ext-config.cjs", "*.md"],
|
||||
};
|
||||
@@ -14,7 +14,6 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django_pydantic_field.rest_framework import SchemaField
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import ValidationError as PydanticValidationError
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from timezone_field.rest_framework import TimeZoneSerializerField
|
||||
@@ -132,16 +131,6 @@ class RoomSerializer(serializers.ModelSerializer):
|
||||
fields = ["id", "name", "slug", "configuration", "access_level", "pin_code"]
|
||||
read_only_fields = ["id", "slug", "pin_code"]
|
||||
|
||||
def validate_configuration(self, value):
|
||||
"""Validate room configuration against the RoomConfiguration schema."""
|
||||
if value is None or value == {}:
|
||||
return value
|
||||
try:
|
||||
RoomConfiguration.model_validate(value)
|
||||
except PydanticValidationError as e:
|
||||
raise serializers.ValidationError(e.errors()) from e
|
||||
return value
|
||||
|
||||
def to_representation(self, instance):
|
||||
"""
|
||||
Add users only for administrator users.
|
||||
@@ -317,22 +306,6 @@ class MuteParticipantSerializer(BaseParticipantsManagementSerializer):
|
||||
)
|
||||
|
||||
|
||||
RoomConfigurationTrackSource = Literal[
|
||||
"camera", "microphone", "screen_share", "screen_share_audio"
|
||||
]
|
||||
|
||||
|
||||
class RoomConfiguration(BaseModel):
|
||||
"""Validate room configuration structure.
|
||||
|
||||
Unknown fields are rejected.
|
||||
"""
|
||||
|
||||
can_publish_sources: list[RoomConfigurationTrackSource] | None = None
|
||||
|
||||
model_config = {"extra": "forbid"}
|
||||
|
||||
|
||||
TrackSource = Literal["SCREEN_SHARE", "SCREEN_SHARE_AUDIO", "CAMERA", "MICROPHONE"]
|
||||
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@ from logging import getLogger
|
||||
from urllib.parse import unquote, urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.core.files.storage import default_storage
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import Q
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
@@ -322,27 +320,16 @@ class RoomViewSet(
|
||||
options = serializer.validated_data.get("options")
|
||||
room = self.get_object()
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
recording = models.Recording.objects.create(
|
||||
room=room,
|
||||
mode=mode,
|
||||
options=options.model_dump(exclude_none=True) if options else {},
|
||||
)
|
||||
models.RecordingAccess.objects.create(
|
||||
user=self.request.user,
|
||||
role=models.RoleChoices.OWNER,
|
||||
recording=recording,
|
||||
)
|
||||
# May raise exception if an active or initiated recording already exist for the room
|
||||
recording = models.Recording.objects.create(
|
||||
room=room,
|
||||
mode=mode,
|
||||
options=options.model_dump(exclude_none=True) if options else {},
|
||||
)
|
||||
|
||||
except (DjangoValidationError, IntegrityError):
|
||||
# DjangoValidationError covers the Python-level check (full_clean);
|
||||
# IntegrityError covers the race where two concurrent requests both
|
||||
# pass that check and the DB-level UNIQUE constraint catches the loser.
|
||||
return drf_response.Response(
|
||||
{"error": f"A recording is already in progress for room {room.slug}"},
|
||||
status=drf_status.HTTP_409_CONFLICT,
|
||||
)
|
||||
models.RecordingAccess.objects.create(
|
||||
user=self.request.user, role=models.RoleChoices.OWNER, recording=recording
|
||||
)
|
||||
|
||||
worker_service = get_worker_service(mode=recording.mode)
|
||||
worker_manager = WorkerServiceMediator(worker_service=worker_service)
|
||||
@@ -350,12 +337,9 @@ class RoomViewSet(
|
||||
try:
|
||||
worker_manager.start(recording)
|
||||
except RecordingStartError:
|
||||
models.Recording.objects.filter(pk=recording.pk).update(
|
||||
status=models.RecordingStatusChoices.FAILED_TO_START
|
||||
)
|
||||
return drf_response.Response(
|
||||
{"error": f"Recording failed to start for room {room.slug}"},
|
||||
status=drf_status.HTTP_502_BAD_GATEWAY,
|
||||
status=drf_status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
if settings.METADATA_COLLECTOR_ENABLED and (
|
||||
|
||||
@@ -123,7 +123,7 @@ class LobbyService:
|
||||
|
||||
def request_entry(
|
||||
self,
|
||||
room: models.Room,
|
||||
room,
|
||||
request,
|
||||
username: str,
|
||||
) -> Tuple[LobbyParticipant, Optional[Dict]]:
|
||||
|
||||
@@ -117,7 +117,7 @@ def test_api_files_create_file_authenticated_success():
|
||||
policy_parsed = urlparse(policy)
|
||||
|
||||
assert policy_parsed.scheme == "http"
|
||||
assert policy_parsed.netloc in ["minio:9000", "localhost:9000"]
|
||||
assert policy_parsed.netloc == "localhost:9000"
|
||||
assert policy_parsed.path == f"/meet-media-storage/files/{file.id!s}.png"
|
||||
|
||||
query_params = parse_qs(policy_parsed.query)
|
||||
|
||||
@@ -232,7 +232,7 @@ def test_api_rooms_retrieve_authenticated_public(mock_token):
|
||||
"""
|
||||
room = RoomFactory(
|
||||
access_level=RoomAccessLevel.PUBLIC,
|
||||
configuration={"can_publish_sources": ["camera"]},
|
||||
configuration={"can_publish_sources": ["mock-source"]},
|
||||
)
|
||||
|
||||
user = UserFactory()
|
||||
@@ -264,7 +264,7 @@ def test_api_rooms_retrieve_authenticated_public(mock_token):
|
||||
user=user,
|
||||
username=None,
|
||||
color=None,
|
||||
sources=["camera"],
|
||||
sources=["mock-source"],
|
||||
is_admin_or_owner=False,
|
||||
participant_id=None,
|
||||
)
|
||||
@@ -363,7 +363,7 @@ def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries, setti
|
||||
other_user = UserFactory()
|
||||
|
||||
room = RoomFactory(
|
||||
configuration={"can_publish_sources": ["camera"]},
|
||||
configuration={"can_publish_sources": ["mock-source"]},
|
||||
)
|
||||
UserResourceAccessFactory(resource=room, user=user, role="member")
|
||||
UserResourceAccessFactory(resource=room, user=other_user, role="member")
|
||||
@@ -401,7 +401,7 @@ def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries, setti
|
||||
user=user,
|
||||
username=None,
|
||||
color=None,
|
||||
sources=["camera"],
|
||||
sources=["mock-source"],
|
||||
is_admin_or_owner=False,
|
||||
participant_id=None,
|
||||
)
|
||||
|
||||
@@ -140,18 +140,16 @@ def test_start_recording_worker_error(
|
||||
|
||||
mock_worker_service_factory.assert_called_once_with(mode="screen_recording")
|
||||
|
||||
assert response.status_code == 502
|
||||
assert response.status_code == 500
|
||||
assert response.json() == {
|
||||
"error": f"Recording failed to start for room {room.slug}"
|
||||
}
|
||||
|
||||
# Recording object should be created even if worker fails, and moved out
|
||||
# of the unique-constraint window so the room is not locked.
|
||||
# Recording object should be created even if worker fails
|
||||
assert Recording.objects.count() == 1
|
||||
recording = Recording.objects.first()
|
||||
assert recording.room == room
|
||||
assert recording.mode == "screen_recording"
|
||||
assert recording.status == "failed_to_start"
|
||||
|
||||
# Verify recording access details
|
||||
assert recording.accesses.count() == 1
|
||||
@@ -160,72 +158,6 @@ def test_start_recording_worker_error(
|
||||
assert access.role == "owner"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"status",
|
||||
["active", "initiated"],
|
||||
)
|
||||
def test_start_recording_conflict_when_already_in_progress(
|
||||
status, mock_worker_service_factory, mock_worker_manager, settings
|
||||
):
|
||||
"""Should return 409 when a second start is attempted while a recording is already active."""
|
||||
settings.RECORDING_ENABLE = True
|
||||
|
||||
room = RoomFactory()
|
||||
user = UserFactory()
|
||||
room.accesses.create(user=user, role="owner")
|
||||
|
||||
# Pre-existing active recording for the same room.
|
||||
Recording.objects.create(room=room, mode="screen_recording", status="active")
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/start-recording/",
|
||||
{"mode": "screen_recording"},
|
||||
)
|
||||
|
||||
assert response.status_code == 409
|
||||
assert response.json() == {
|
||||
"error": f"A recording is already in progress for room {room.slug}"
|
||||
}
|
||||
# No new recording row, no access row leaked from the rolled-back transaction.
|
||||
assert Recording.objects.count() == 1
|
||||
assert Recording.objects.first().accesses.count() == 0
|
||||
mock_worker_manager.start.assert_not_called()
|
||||
|
||||
|
||||
def test_start_recording_after_worker_failure_unblocks_room(
|
||||
mock_worker_service_factory, mock_worker_manager, settings
|
||||
):
|
||||
"""Should allow a new recording when the previous recording failed."""
|
||||
settings.RECORDING_ENABLE = True
|
||||
|
||||
room = RoomFactory()
|
||||
user = UserFactory()
|
||||
room.accesses.create(user=user, role="owner")
|
||||
|
||||
mock_worker_manager.start = mock.Mock(
|
||||
side_effect=[RecordingStartError("boom"), None]
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
first = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/start-recording/",
|
||||
{"mode": "screen_recording"},
|
||||
)
|
||||
assert first.status_code == 502
|
||||
|
||||
second = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/start-recording/",
|
||||
{"mode": "screen_recording"},
|
||||
)
|
||||
assert second.status_code == 201
|
||||
assert Recording.objects.count() == 2
|
||||
|
||||
|
||||
def test_start_recording_success(
|
||||
mock_worker_service_factory, mock_worker_manager, settings
|
||||
):
|
||||
|
||||
@@ -67,7 +67,7 @@ def test_api_rooms_update_members():
|
||||
"name": "New name",
|
||||
"slug": "should-be-ignored",
|
||||
"access_level": RoomAccessLevel.RESTRICTED,
|
||||
"configuration": {"can_publish_sources": ["camera", "microphone"]},
|
||||
"configuration": {"the_key": "the_value"},
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
@@ -95,7 +95,7 @@ def test_api_rooms_update_administrators():
|
||||
"name": "New name",
|
||||
"slug": "should-be-ignored",
|
||||
"access_level": RoomAccessLevel.PUBLIC,
|
||||
"configuration": {"can_publish_sources": ["camera", "microphone"]},
|
||||
"configuration": {"the_key": "the_value"},
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
@@ -104,98 +104,7 @@ def test_api_rooms_update_administrators():
|
||||
assert room.name == "New name"
|
||||
assert room.slug == "new-name"
|
||||
assert room.access_level == RoomAccessLevel.PUBLIC
|
||||
assert room.configuration == {"can_publish_sources": ["camera", "microphone"]}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"configuration",
|
||||
[
|
||||
{},
|
||||
{"can_publish_sources": ["camera", "microphone"]},
|
||||
{
|
||||
"can_publish_sources": [
|
||||
"camera",
|
||||
"microphone",
|
||||
"screen_share",
|
||||
"screen_share_audio",
|
||||
]
|
||||
},
|
||||
{"can_publish_sources": []},
|
||||
{"can_publish_sources": None},
|
||||
],
|
||||
)
|
||||
def test_api_rooms_update_configuration_valid(configuration):
|
||||
"""Administrators should be allowed to set valid configurations."""
|
||||
user = UserFactory()
|
||||
room = RoomFactory(users=[(user, "owner")])
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/rooms/{room.id!s}/",
|
||||
{"configuration": configuration},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
room.refresh_from_db()
|
||||
assert room.configuration == configuration
|
||||
|
||||
|
||||
def test_api_rooms_update_configuration_extra_keys_rejected():
|
||||
"""Extra keys in configuration should be rejected."""
|
||||
user = UserFactory()
|
||||
room = RoomFactory(users=[(user, "owner")])
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/rooms/{room.id!s}/",
|
||||
{
|
||||
"configuration": {
|
||||
"can_publish_sources": ["camera"],
|
||||
"arbitrary_key": "value",
|
||||
}
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 400
|
||||
room.refresh_from_db()
|
||||
assert room.configuration == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("invalid_source", ["invalid_source", "CAMERA"])
|
||||
def test_api_rooms_update_configuration_invalid_source_value(invalid_source):
|
||||
"""Invalid source values should be rejected."""
|
||||
user = UserFactory()
|
||||
room = RoomFactory(users=[(user, "owner")])
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/rooms/{room.id!s}/",
|
||||
{"configuration": {"can_publish_sources": [invalid_source]}},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 400
|
||||
room.refresh_from_db()
|
||||
assert room.configuration == {}
|
||||
|
||||
|
||||
def test_api_rooms_update_configuration_wrong_type():
|
||||
"""Configuration values with wrong types should be rejected."""
|
||||
user = UserFactory()
|
||||
room = RoomFactory(users=[(user, "owner")])
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/rooms/{room.id!s}/",
|
||||
{"configuration": {"can_publish_sources": "camera"}},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 400
|
||||
room.refresh_from_db()
|
||||
assert room.configuration == {}
|
||||
assert room.configuration == {"the_key": "the_value"}
|
||||
|
||||
|
||||
def test_api_rooms_update_administrators_of_another():
|
||||
|
||||
Generated
+4
-120
@@ -54,7 +54,7 @@
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-react-hooks": "5.2.0",
|
||||
"eslint-plugin-react-refresh": "0.4.20",
|
||||
"postcss": "8.5.10",
|
||||
"postcss": "8.5.6",
|
||||
"prettier": "3.8.1",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "7.3.2",
|
||||
@@ -1759,35 +1759,6 @@
|
||||
"ts-pattern": "5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pandacss/core/node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@pandacss/dev": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@pandacss/dev/-/dev-1.8.2.tgz",
|
||||
@@ -1845,35 +1816,6 @@
|
||||
"ts-pattern": "5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pandacss/generator/node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@pandacss/is-valid-prop": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@pandacss/is-valid-prop/-/is-valid-prop-1.8.2.tgz",
|
||||
@@ -1975,35 +1917,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@pandacss/node/node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@pandacss/node/node_modules/prettier": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
|
||||
@@ -2063,35 +1976,6 @@
|
||||
"postcss": "8.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@pandacss/postcss/node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@pandacss/preset-base": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@pandacss/preset-base/-/preset-base-1.8.2.tgz",
|
||||
@@ -9548,9 +9432,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
|
||||
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-react-hooks": "5.2.0",
|
||||
"eslint-plugin-react-refresh": "0.4.20",
|
||||
"postcss": "8.5.10",
|
||||
"postcss": "8.5.6",
|
||||
"prettier": "3.8.1",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "7.3.2",
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
// Google Meet uses only letters in a room identifier
|
||||
const ROOM_ID_ALLOWED_CHARACTERS = 'abcdefghijklmnopqrstuvwxyz'
|
||||
|
||||
const getRandomChar = () => {
|
||||
const maxValue =
|
||||
Math.floor(0x100000000 / ROOM_ID_ALLOWED_CHARACTERS.length) *
|
||||
ROOM_ID_ALLOWED_CHARACTERS.length
|
||||
const randomValue = new Uint32Array(1)
|
||||
|
||||
do {
|
||||
crypto.getRandomValues(randomValue)
|
||||
} while (randomValue[0] >= maxValue)
|
||||
|
||||
return ROOM_ID_ALLOWED_CHARACTERS[
|
||||
randomValue[0] % ROOM_ID_ALLOWED_CHARACTERS.length
|
||||
const getRandomChar = () =>
|
||||
ROOM_ID_ALLOWED_CHARACTERS[
|
||||
Math.floor(Math.random() * ROOM_ID_ALLOWED_CHARACTERS.length)
|
||||
]
|
||||
}
|
||||
|
||||
const generateSegment = (length: number): string =>
|
||||
Array.from(Array(length), getRandomChar).join('')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"error": {
|
||||
"title": "Aufzeichnung nicht verfügbar",
|
||||
"body": "Die Aufzeichnung ist nicht verfügbar oder wurde möglicherweise gelöscht. Nur der Host der Besprechung hat Zugriff darauf. Wende dich bei Bedarf gerne an die Person."
|
||||
"body": "Die Aufzeichnung ist nicht verfügbar oder wurde möglicherweise gelöscht. Nur die*der Organisator:in der Besprechung hat Zugriff darauf. Wende dich bei Bedarf gerne an die Person."
|
||||
},
|
||||
"expired": {
|
||||
"title": "Aufzeichnung abgelaufen",
|
||||
@@ -22,7 +22,7 @@
|
||||
"button": "Herunterladen",
|
||||
"warning": {
|
||||
"title": "Linkfreigabe deaktiviert",
|
||||
"body": "Die Freigabe der Aufzeichnung per Link ist noch nicht verfügbar. Nur Hosts können sie herunterladen."
|
||||
"body": "Die Freigabe der Aufzeichnung per Link ist noch nicht verfügbar. Nur Organisator:innen können sie herunterladen."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"heading": {
|
||||
"normal": "Du hast das Meeting verlassen",
|
||||
"duplicateIdentity": "Du bist dem Meeting von einem anderen Gerät aus beigetreten",
|
||||
"participantRemoved": "Du wurdest vom Host aus dem Meeting entfernt"
|
||||
"participantRemoved": "Du wurdest von der*dem Organisator:in aus dem Meeting entfernt"
|
||||
},
|
||||
"home": "Zur Startseite zurückkehren",
|
||||
"back": "Dem Meeting erneut beitreten"
|
||||
@@ -383,7 +383,7 @@
|
||||
"body": "Transkribiere bis zu {{max_duration}} Meetingdauer.",
|
||||
"bodyWithoutMaxDuration": "Transkribieren dein Meeting ohne Zeitbegrenzung.",
|
||||
"details": {
|
||||
"receiver": "Das Transkript wird an den Host und die Co-Hosts gesendet.",
|
||||
"receiver": "Das Transkript wird an die*den Organisator:in und die Mitorganisierenden gesendet.",
|
||||
"destination": "Ein neues Dokument wird erstellt auf",
|
||||
"destinationUnknown": "Ein neues Dokument wird erstellt",
|
||||
"language": "Meeting-Sprache:",
|
||||
@@ -438,7 +438,7 @@
|
||||
"linkMore": "Dokumentation öffnen",
|
||||
"linkAriaLabel": "Dokumentation zur Aufzeichnung öffnen – öffnet in neuem Tab",
|
||||
"details": {
|
||||
"receiver": "Die Aufzeichnung wird an den Host und die Co-Hosts gesendet.",
|
||||
"receiver": "Die Aufzeichnung wird an die*den Organisator:in und die Mitorganisierenden gesendet.",
|
||||
"destination": "Diese Aufzeichnung wird vorübergehend auf unseren Servern gespeichert",
|
||||
"loading": "Aufzeichnung wird gestartet",
|
||||
"linkMore": "Mehr erfahren",
|
||||
@@ -494,7 +494,7 @@
|
||||
"button": "OK"
|
||||
},
|
||||
"admin": {
|
||||
"description": "Diese Host-Einstellungen ermöglichen dir die Kontrolle über dein Meeting. Nur Hosts haben Zugriff auf diese Optionen.",
|
||||
"description": "Diese Einstellungen für Organisierende ermöglichen dir die Kontrolle über dein Meeting. Nur Organisierende haben Zugriff auf diese Optionen.",
|
||||
"access": {
|
||||
"title": "Raumzugang",
|
||||
"description": "Diese Einstellungen gelten auch für zukünftige Meetings in diesem Raum.",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"error": {
|
||||
"title": "Recording unavailable",
|
||||
"body": "The recording is unavailable or may have been deleted. Only the meeting host can access it. Feel free to contact them if needed."
|
||||
"body": "The recording is unavailable or may have been deleted. Only the meeting organizer can access it. Feel free to contact them if needed."
|
||||
},
|
||||
"expired": {
|
||||
"title": "Recording expired",
|
||||
@@ -22,7 +22,7 @@
|
||||
"button": "Download",
|
||||
"warning": {
|
||||
"title": "Link sharing disabled",
|
||||
"body": "Sharing the recording via link is not yet available. Only hosts can download it."
|
||||
"body": "Sharing the recording via link is not yet available. Only organizers can download it."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"heading": {
|
||||
"normal": "You have left the meeting",
|
||||
"duplicateIdentity": "You have joined the meeting from another device",
|
||||
"participantRemoved": "You have been removed from the meeting by a host"
|
||||
"participantRemoved": "You have been removed from the meeting by an administrator"
|
||||
},
|
||||
"home": "Return to home",
|
||||
"back": "Rejoin the meeting"
|
||||
@@ -328,7 +328,7 @@
|
||||
"chat": "Messages in the chat",
|
||||
"transcript": "Transcribe",
|
||||
"screenRecording": "Record",
|
||||
"admin": "Host settings",
|
||||
"admin": "Admin settings",
|
||||
"tools": "More tools",
|
||||
"info": "Meeting information"
|
||||
},
|
||||
@@ -338,7 +338,7 @@
|
||||
"chat": "messages",
|
||||
"transcript": "transcribe",
|
||||
"screenRecording": "record",
|
||||
"admin": "host settings",
|
||||
"admin": "admin settings",
|
||||
"tools": "more tools",
|
||||
"info": "meeting information"
|
||||
},
|
||||
@@ -493,7 +493,7 @@
|
||||
"button": "OK"
|
||||
},
|
||||
"admin": {
|
||||
"description": "These host settings allow you to maintain control of your meeting. Only hosts can access these controls.",
|
||||
"description": "These organizer settings allow you to maintain control of your meeting. Only organizers can access these controls.",
|
||||
"access": {
|
||||
"title": "Room access",
|
||||
"description": "These settings will also apply to future occurrences of this meeting.",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"error": {
|
||||
"title": "Opname niet beschikbaar",
|
||||
"body": "De opname is niet beschikbaar of is mogelijk verwijderd. Alleen de host van de vergadering heeft toegang. Neem gerust contact met hem of haar op als dat nodig is."
|
||||
"body": "De opname is niet beschikbaar of is mogelijk verwijderd. Alleen de organisator van de vergadering heeft toegang. Neem gerust contact met hem of haar op als dat nodig is."
|
||||
},
|
||||
"expired": {
|
||||
"title": "Opname verlopen",
|
||||
@@ -22,7 +22,7 @@
|
||||
"button": "Downloaden",
|
||||
"warning": {
|
||||
"title": "Delen via link uitgeschakeld",
|
||||
"body": "Het delen van de opname via een link is nog niet beschikbaar. Alleen hosts kunnen deze downloaden."
|
||||
"body": "Het delen van de opname via een link is nog niet beschikbaar. Alleen organisatoren kunnen deze downloaden."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@
|
||||
"linkMore": "Documentatie openen",
|
||||
"linkAriaLabel": "Documentatie over opname openen - opent in nieuw venster",
|
||||
"details": {
|
||||
"receiver": "De opname wordt verzonden naar de host en co-hosts.",
|
||||
"receiver": "De opname wordt verzonden naar de organisator en co-organisatoren.",
|
||||
"destination": "Deze opname wordt tijdelijk op onze servers bewaard",
|
||||
"loading": "Opname wordt gestart",
|
||||
"linkMore": "Meer informatie",
|
||||
@@ -493,7 +493,7 @@
|
||||
"button": "Opnieuw proberen"
|
||||
},
|
||||
"admin": {
|
||||
"description": "Deze hostinstellingen geven u controle over uw vergadering. Alleen hosts hebben toegang tot deze bedieningselementen.",
|
||||
"description": "Deze organisatorinstellingen geven u controle over uw vergadering. Alleen organisatoren hebben toegang tot deze bedieningselementen.",
|
||||
"access": {
|
||||
"title": "Toegang tot vergadering",
|
||||
"description": "Deze instellingen zijn ook van toepassing op toekomstige sessies van deze vergadering.",
|
||||
|
||||
@@ -1,62 +1,52 @@
|
||||
{{- $sharedEnvVars := default (dict) .Values.celeryTranscribe.envVars -}}
|
||||
{{- $envVars := include "meet.common.env" (list . .Values.celeryTranscribe) -}}
|
||||
{{- $fullName := include "meet.celeryTranscribe.fullname" . -}}
|
||||
{{- $component := "celery-transcribe" -}}
|
||||
|
||||
{{- range $idx, $instance := .Values.celeryTranscribe.instances }}
|
||||
{{- $instanceValues := merge $instance $.Values.celeryTranscribe }}
|
||||
{{- $fullName := printf "%s-%s" (include "meet.celeryTranscribe.fullname" $) $instance.name }}
|
||||
{{- $extraInstanceEnvVars := default (dict) $instance.extraEnvVars -}}
|
||||
{{- $mergedInstanceEnvVars := merge $extraInstanceEnvVars $sharedEnvVars -}}
|
||||
{{- $fakeInstanceObjectForEnvHelper := dict "envVars" $mergedInstanceEnvVars -}}
|
||||
{{- $envVars := include "meet.common.env" (list . $fakeInstanceObjectForEnvHelper) -}}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
annotations:
|
||||
{{- with $instanceValues.dpAnnotations }}
|
||||
{{- with .Values.celeryTranscribe.dpAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
namespace: {{ $.Release.Namespace | quote }}
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "meet.common.labels" (list $ $component) | nindent 4 }}
|
||||
instance: {{ $instance.name }}
|
||||
{{- include "meet.common.labels" (list . $component) | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ $instanceValues.replicas }}
|
||||
replicas: {{ .Values.celeryTranscribe.replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "meet.common.selectorLabels" (list $ $component) | nindent 6 }}
|
||||
instance: {{ $instance.name }}
|
||||
{{- include "meet.common.selectorLabels" (list . $component) | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
{{- with $instanceValues.podAnnotations }}
|
||||
{{- with .Values.celeryTranscribe.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "meet.common.selectorLabels" (list $ $component) | nindent 8 }}
|
||||
instance: {{ $instance.name }}
|
||||
{{- include "meet.common.selectorLabels" (list . $component) | nindent 8 }}
|
||||
spec:
|
||||
{{- if $.Values.image.credentials }}
|
||||
imagePullSecrets:
|
||||
- name: {{ include "meet.secret.dockerconfigjson.name" (dict "fullname" (include "meet.fullname" $) "imageCredentials" $.Values.image.credentials) }}
|
||||
- name: {{ include "meet.secret.dockerconfigjson.name" (dict "fullname" (include "meet.fullname" .) "imageCredentials" $.Values.image.credentials) }}
|
||||
{{- end }}
|
||||
shareProcessNamespace: {{ $instanceValues.shareProcessNamespace }}
|
||||
{{- with $instanceValues.podSecurityContext }}
|
||||
shareProcessNamespace: {{ .Values.celeryTranscribe.shareProcessNamespace }}
|
||||
{{- with .Values.celeryTranscribe.podSecurityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
{{- with $instanceValues.sidecars }}
|
||||
{{- with .Values.celeryTranscribe.sidecars }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
- name: {{ $.Chart.Name }}
|
||||
image: "{{ ($instanceValues.image | default dict).repository | default $.Values.image.repository }}:{{ ($instanceValues.image | default dict).tag | default $.Values.image.tag }}"
|
||||
imagePullPolicy: {{ ($instanceValues.image | default dict).pullPolicy | default $.Values.image.pullPolicy }}
|
||||
{{- with $instanceValues.command }}
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ (.Values.celeryTranscribe.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.celeryTranscribe.image | default dict).tag | default .Values.image.tag }}"
|
||||
imagePullPolicy: {{ (.Values.celeryTranscribe.image | default dict).pullPolicy | default .Values.image.pullPolicy }}
|
||||
{{- with .Values.celeryTranscribe.command }}
|
||||
command:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with $instanceValues.args }}
|
||||
{{- with .Values.celeryTranscribe.args }}
|
||||
args:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
@@ -64,65 +54,65 @@ spec:
|
||||
{{- if $envVars }}
|
||||
{{- $envVars | indent 12 }}
|
||||
{{- end }}
|
||||
{{- with $instanceValues.securityContext }}
|
||||
{{- with .Values.celeryTranscribe.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ $instanceValues.service.targetPort }}
|
||||
containerPort: {{ .Values.celeryTranscribe.service.targetPort }}
|
||||
protocol: TCP
|
||||
{{- if $instanceValues.probes.liveness }}
|
||||
{{- if .Values.celeryTranscribe.probes.liveness }}
|
||||
livenessProbe:
|
||||
{{- include "meet.probes.abstract" (merge $instanceValues.probes.liveness (dict "targetPort" $instanceValues.service.targetPort)) | nindent 12 }}
|
||||
{{- include "meet.probes.abstract" (merge .Values.celeryTranscribe.probes.liveness (dict "targetPort" .Values.celeryTranscribe.service.targetPort )) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if $instanceValues.probes.readiness }}
|
||||
{{- if .Values.celeryTranscribe.probes.readiness }}
|
||||
readinessProbe:
|
||||
{{- include "meet.probes.abstract" (merge $instanceValues.probes.readiness (dict "targetPort" $instanceValues.service.targetPort)) | nindent 12 }}
|
||||
{{- include "meet.probes.abstract" (merge .Values.celeryTranscribe.probes.readiness (dict "targetPort" .Values.celeryTranscribe.service.targetPort )) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if $instanceValues.probes.startup }}
|
||||
{{- if .Values.celeryTranscribe.probes.startup }}
|
||||
startupProbe:
|
||||
{{- include "meet.probes.abstract" (merge $instanceValues.probes.startup (dict "targetPort" $instanceValues.service.targetPort)) | nindent 12 }}
|
||||
{{- include "meet.probes.abstract" (merge .Values.celeryTranscribe.probes.startup (dict "targetPort" .Values.celeryTranscribe.service.targetPort )) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with $instanceValues.resources }}
|
||||
{{- with .Values.celeryTranscribe.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
{{- range $index, $value := $.Values.mountFiles }}
|
||||
{{- range $index, $value := .Values.mountFiles }}
|
||||
- name: "files-{{ $index }}"
|
||||
mountPath: {{ $value.path }}
|
||||
subPath: content
|
||||
{{- end }}
|
||||
{{- range $name, $volume := $instanceValues.persistence }}
|
||||
{{- range $name, $volume := .Values.celeryTranscribe.persistence }}
|
||||
- name: "{{ $name }}"
|
||||
mountPath: "{{ $volume.mountPath }}"
|
||||
{{- end }}
|
||||
{{- range $instanceValues.extraVolumeMounts }}
|
||||
{{- range .Values.celeryTranscribe.extraVolumeMounts }}
|
||||
- name: {{ .name }}
|
||||
mountPath: {{ .mountPath }}
|
||||
subPath: {{ .subPath | default "" }}
|
||||
readOnly: {{ .readOnly }}
|
||||
{{- end }}
|
||||
{{- with $instanceValues.nodeSelector }}
|
||||
{{- with .Values.celeryTranscribe.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with $instanceValues.affinity }}
|
||||
{{- with .Values.celeryTranscribe.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with $instanceValues.tolerations }}
|
||||
{{- with .Values.celeryTranscribe.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
{{- range $index, $value := $.Values.mountFiles }}
|
||||
{{- range $index, $value := .Values.mountFiles }}
|
||||
- name: "files-{{ $index }}"
|
||||
configMap:
|
||||
name: "{{ include "meet.fullname" $ }}-files-{{ $index }}"
|
||||
{{- end }}
|
||||
{{- range $name, $volume := $instanceValues.persistence }}
|
||||
{{- range $name, $volume := .Values.celeryTranscribe.persistence }}
|
||||
- name: "{{ $name }}"
|
||||
{{- if eq $volume.type "emptyDir" }}
|
||||
emptyDir: {}
|
||||
@@ -131,7 +121,7 @@ spec:
|
||||
claimName: "{{ $fullName }}-{{ $name }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range $instanceValues.extraVolumes }}
|
||||
{{- range .Values.celeryTranscribe.extraVolumes }}
|
||||
- name: {{ .name }}
|
||||
{{- if .existingClaim }}
|
||||
persistentVolumeClaim:
|
||||
@@ -153,18 +143,15 @@ spec:
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
---
|
||||
{{ if $instanceValues.pdb.enabled }}
|
||||
{{ if .Values.celeryTranscribe.pdb.enabled }}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
namespace: {{ $.Release.Namespace | quote }}
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
spec:
|
||||
maxUnavailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "meet.common.selectorLabels" (list $ $component) | nindent 6 }}
|
||||
instance: {{ $instance.name }}
|
||||
---
|
||||
{{- include "meet.common.selectorLabels" (list . $component) | nindent 6 }}
|
||||
{{ end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -848,21 +848,6 @@ celeryTranscribe:
|
||||
pdb:
|
||||
enabled: false
|
||||
|
||||
## @param celeryTranscribe.instances List of celeryTranscribe instances to deploy. Each entry creates a dedicated Deployment. Useful when wanting to use multiple instances of WhisperX (configure each endpoint in the extraEnv value specific to that instance).
|
||||
## @extra celeryTranscribe.instances[].name Unique name suffix for the instance (used in the Deployment name and pod labels)
|
||||
## @extra celeryTranscribe.instances[].replicas Override the number of replicas for this specific instance
|
||||
## @extra celeryTranscribe.instances[].extraEnvVars Additional environment variables for this specific instance (same structure as envVars)
|
||||
## @extra celeryTranscribe.instances[].command Override the container command for this specific instance
|
||||
## @extra celeryTranscribe.instances[].args Override the container args for this specific instance
|
||||
## @extra celeryTranscribe.instances[].resources Override resource requirements for this specific instance
|
||||
## @extra celeryTranscribe.instances[].nodeSelector Override node selector for this specific instance
|
||||
## @extra celeryTranscribe.instances[].affinity Override affinity for this specific instance
|
||||
## @extra celeryTranscribe.instances[].tolerations Override tolerations for this specific instance
|
||||
## @extra celeryTranscribe.instances[].pdb.enabled Enable pdb for this specific instance
|
||||
instances:
|
||||
- name: default
|
||||
extraEnvVars: {}
|
||||
|
||||
## @section celerySummarize
|
||||
|
||||
celerySummarize:
|
||||
|
||||
@@ -20,7 +20,7 @@ dependencies = [
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"ruff==0.15.6",
|
||||
"pytest==9.0.3",
|
||||
"pytest==9.0.2",
|
||||
"responses>=0.25.8",
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user