mirror of
https://github.com/lucaspalomodevelop/indico-plugins.git
synced 2026-03-19 18:24:39 +00:00
Merge branch 'version_2' of ../../indico_zoom_plugin into vc-vidyo-room-name-sanitization
This commit is contained in:
commit
f820fc75ba
37
vc_zoom/.flake8
Normal file
37
vc_zoom/.flake8
Normal file
@ -0,0 +1,37 @@
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
|
||||
# colored output
|
||||
format = ${cyan}%(path)s${reset}:${yellow_bold}%(row)d${reset}:${green_bold}%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s
|
||||
|
||||
# decent quote styles
|
||||
inline-quotes = single
|
||||
multiline-quotes = single
|
||||
docstring-quotes = double
|
||||
avoid-escape = true
|
||||
|
||||
exclude =
|
||||
build
|
||||
dist
|
||||
docs
|
||||
ext_modules
|
||||
htmlcov
|
||||
indico.egg-info
|
||||
node_modules
|
||||
.*/
|
||||
# TODO: remove the next two entries and use extend-exclude once flake8 3.8.0 is out
|
||||
.git
|
||||
__pycache__
|
||||
|
||||
ignore =
|
||||
# allow omitting whitespace around arithmetic operators
|
||||
E226
|
||||
# don't require specific wrapping before/after binary operators
|
||||
W503
|
||||
W504
|
||||
# allow assigning lambdas (it's useful for single-line functions defined inside other functions)
|
||||
E731
|
||||
# while single quotes are nicer, we have double quotes in way too many places
|
||||
Q000
|
||||
# for non-docstring multiline strings we don't really enforce a quote style
|
||||
Q001
|
||||
85
vc_zoom/.gitignore
vendored
Executable file
85
vc_zoom/.gitignore
vendored
Executable file
@ -0,0 +1,85 @@
|
||||
.vscode/
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
venv/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/build/
|
||||
docs/source/generated/
|
||||
|
||||
# pytest
|
||||
.pytest_cache/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Editor files
|
||||
#mac
|
||||
.DS_Store
|
||||
*~
|
||||
|
||||
#vim
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
#pycharm
|
||||
.idea/*
|
||||
|
||||
|
||||
#Ipython Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
|
||||
webpack-build-config.json
|
||||
url_map.json
|
||||
6
vc_zoom/MANIFEST.in
Normal file
6
vc_zoom/MANIFEST.in
Normal file
@ -0,0 +1,6 @@
|
||||
graft indico_vc_zoom/static
|
||||
graft indico_vc_zoom/migrations
|
||||
graft indico_vc_zoom/templates
|
||||
graft indico_vc_zoom/translations
|
||||
|
||||
global-exclude *.pyc __pycache__ .keep
|
||||
7
vc_zoom/README.md
Normal file
7
vc_zoom/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
Indico Plugin for Zoom based on Vidyo plugin.
|
||||
|
||||
To obtain Api key and api secret, please visit [https://marketplace.zoom.us/docs/guides/auth/jwt](https://marketplace.zoom.us/docs/guides/auth/jwt)
|
||||
|
||||
Not ready for production.
|
||||
|
||||
Developed by Giovanni Mariano @ ENEA Frascati
|
||||
1
vc_zoom/conftest.py
Normal file
1
vc_zoom/conftest.py
Normal file
@ -0,0 +1 @@
|
||||
pytest_plugins = 'indico'
|
||||
6
vc_zoom/indico_vc_zoom/__init__.py
Normal file
6
vc_zoom/indico_vc_zoom/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from indico.util.i18n import make_bound_gettext
|
||||
|
||||
|
||||
_ = make_bound_gettext('vc_zoom')
|
||||
4
vc_zoom/indico_vc_zoom/api/__init__.py
Normal file
4
vc_zoom/indico_vc_zoom/api/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .client import ZoomIndicoClient, ZoomClient
|
||||
|
||||
|
||||
__all__ = ['ZoomIndicoClient', 'ZoomClient']
|
||||
236
vc_zoom/indico_vc_zoom/api/client.py
Normal file
236
vc_zoom/indico_vc_zoom/api/client.py
Normal file
@ -0,0 +1,236 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import time
|
||||
|
||||
import jwt
|
||||
from requests import Session
|
||||
from requests.exceptions import HTTPError
|
||||
from pytz import utc
|
||||
|
||||
|
||||
def format_iso_dt(d):
|
||||
"""Convertdatetime objects to a UTC-based string.
|
||||
|
||||
:param d: The :class:`datetime.datetime` to convert to a string
|
||||
:returns: The string representation of the date
|
||||
"""
|
||||
return d.astimezone(utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
|
||||
def _handle_response(resp, expected_code=200, expects_json=True):
|
||||
resp.raise_for_status()
|
||||
if resp.status_code != expected_code:
|
||||
raise HTTPError("Unexpected status code {}".format(resp.status_code), response=resp)
|
||||
if expects_json:
|
||||
return resp.json()
|
||||
else:
|
||||
return resp
|
||||
|
||||
|
||||
class APIException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseComponent(object):
|
||||
def __init__(self, base_uri, config, timeout):
|
||||
self.base_uri = base_uri
|
||||
self.config = config
|
||||
self.timeout = timeout
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
header = {"alg": "HS256", "typ": "JWT"}
|
||||
payload = {"iss": self.config['api_key'], "exp": int(time.time() + 3600)}
|
||||
token = jwt.encode(payload, self.config['api_secret'], algorithm="HS256", headers=header)
|
||||
return token.decode("utf-8")
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
session = Session()
|
||||
session.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer {}'.format(self.token)
|
||||
}
|
||||
return session
|
||||
|
||||
|
||||
class MeetingComponent(BaseComponent):
|
||||
def list(self, user_id, **kwargs):
|
||||
return self.get(
|
||||
"{}/users/{}/meetings".format(self.base_uri, user_id), params=kwargs
|
||||
)
|
||||
|
||||
def create(self, user_id, **kwargs):
|
||||
if kwargs.get("start_time"):
|
||||
kwargs["start_time"] = format_iso_dt(kwargs["start_time"])
|
||||
return self.session.post(
|
||||
"{}/users/{}/meetings".format(self.base_uri, user_id),
|
||||
json=kwargs
|
||||
)
|
||||
|
||||
def get(self, meeting_id, **kwargs):
|
||||
return self.session.get("{}/meetings/{}".format(self.base_uri, meeting_id), json=kwargs)
|
||||
|
||||
def update(self, meeting_id, **kwargs):
|
||||
if kwargs.get("start_time"):
|
||||
kwargs["start_time"] = format_iso_dt(kwargs["start_time"])
|
||||
return self.session.patch(
|
||||
"{}/meetings/{}".format(self.base_uri, meeting_id), json=kwargs
|
||||
)
|
||||
|
||||
def delete(self, meeting_id, **kwargs):
|
||||
return self.session.delete(
|
||||
"{}/meetings/{}".format(self.base_uri, meeting_id), json=kwargs
|
||||
)
|
||||
|
||||
|
||||
class WebinarComponent(BaseComponent):
|
||||
def list(self, user_id, **kwargs):
|
||||
return self.get(
|
||||
"{}/users/{}/webinars".format(self.base_uri, user_id), params=kwargs
|
||||
)
|
||||
|
||||
def create(self, user_id, **kwargs):
|
||||
if kwargs.get("start_time"):
|
||||
kwargs["start_time"] = format_iso_dt(kwargs["start_time"])
|
||||
return self.session.post(
|
||||
"{}/users/{}/webinars".format(self.base_uri, user_id),
|
||||
json=kwargs
|
||||
)
|
||||
|
||||
def get(self, meeting_id, **kwargs):
|
||||
return self.session.get("{}/webinars/{}".format(self.base_uri, meeting_id), json=kwargs)
|
||||
|
||||
def update(self, meeting_id, **kwargs):
|
||||
if kwargs.get("start_time"):
|
||||
kwargs["start_time"] = format_iso_dt(kwargs["start_time"])
|
||||
return self.session.patch(
|
||||
"{}/webinars/{}".format(self.base_uri, meeting_id), json=kwargs
|
||||
)
|
||||
|
||||
def delete(self, meeting_id, **kwargs):
|
||||
return self.session.delete(
|
||||
"{}/webinars/{}".format(self.base_uri, meeting_id), json=kwargs
|
||||
)
|
||||
|
||||
|
||||
class UserComponent(BaseComponent):
|
||||
def me(self):
|
||||
return self.get('me')
|
||||
|
||||
def list(self, **kwargs):
|
||||
return self.session.get("{}/users".format(self.base_uri), params=kwargs)
|
||||
|
||||
def create(self, **kwargs):
|
||||
return self.session.post("{}/users".format(self.base_uri), params=kwargs)
|
||||
|
||||
def update(self, user_id, **kwargs):
|
||||
return self.session.patch("{}/users/{}".format(self.base_uri, user_id), params=kwargs)
|
||||
|
||||
def delete(self, user_id, **kwargs):
|
||||
return self.session.delete("{}/users/{}".format(self.base_uri, user_id), params=kwargs)
|
||||
|
||||
def add_assistant(self, user_id, **kwargs):
|
||||
return self.session.post("{}/users/{}/assistants".format(self.base_uri, user_id), json=kwargs)
|
||||
|
||||
def get_assistants(self, user_id, **kwargs):
|
||||
return self.session.get("{}/users/{}/assistants".format(self.base_uri, user_id), params=kwargs)
|
||||
|
||||
def get(self, user_id, **kwargs):
|
||||
return self.session.get("{}/users/{}".format(self.base_uri, user_id), params=kwargs)
|
||||
|
||||
|
||||
class ZoomClient(object):
|
||||
"""Zoom REST API Python Client."""
|
||||
|
||||
_components = {
|
||||
'user': UserComponent,
|
||||
'meeting': MeetingComponent,
|
||||
'webinar': WebinarComponent
|
||||
}
|
||||
|
||||
def __init__(self, api_key, api_secret, timeout=15):
|
||||
"""Create a new Zoom client.
|
||||
|
||||
:param api_key: the Zoom JWT API key
|
||||
:param api_secret: the Zoom JWT API Secret
|
||||
:param timeout: the time out to use for API requests
|
||||
"""
|
||||
BASE_URI = "https://api.zoom.us/v2"
|
||||
|
||||
# Setup the config details
|
||||
config = {
|
||||
"api_key": api_key,
|
||||
"api_secret": api_secret
|
||||
}
|
||||
|
||||
# Instantiate the components
|
||||
|
||||
self.components = {
|
||||
key: component(base_uri=BASE_URI, config=config, timeout=timeout)
|
||||
for key, component in self._components.viewitems()
|
||||
}
|
||||
|
||||
@property
|
||||
def meeting(self):
|
||||
"""Get the meeting component."""
|
||||
return self.components.get("meeting")
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
"""Get the user component."""
|
||||
return self.components.get("user")
|
||||
|
||||
@property
|
||||
def webinar(self):
|
||||
"""Get the user component."""
|
||||
return self.components.get("webinar")
|
||||
|
||||
|
||||
class ZoomIndicoClient(object):
|
||||
def __init__(self):
|
||||
self._refresh_client()
|
||||
|
||||
def _refresh_client(self):
|
||||
from indico_vc_zoom.plugin import ZoomPlugin
|
||||
settings = ZoomPlugin.settings
|
||||
self.client = ZoomClient(
|
||||
settings.get('api_key'),
|
||||
settings.get('api_secret')
|
||||
)
|
||||
|
||||
def create_meeting(self, user_id, **kwargs):
|
||||
return _handle_response(self.client.meeting.create(user_id, **kwargs), 201)
|
||||
|
||||
def get_meeting(self, meeting_id):
|
||||
return _handle_response(self.client.meeting.get(meeting_id))
|
||||
|
||||
def update_meeting(self, meeting_id, data):
|
||||
return _handle_response(self.client.meeting.update(meeting_id, **data), 204, expects_json=False)
|
||||
|
||||
def delete_meeting(self, meeting_id):
|
||||
return _handle_response(self.client.meeting.delete(meeting_id), 204, expects_json=False)
|
||||
|
||||
def create_webinar(self, user_id, **kwargs):
|
||||
return _handle_response(self.client.webinar.create(user_id, **kwargs), 201)
|
||||
|
||||
def get_webinar(self, webinar_id):
|
||||
return _handle_response(self.client.webinar.get(webinar_id))
|
||||
|
||||
def update_webinar(self, webinar_id, data):
|
||||
return _handle_response(self.client.webinar.update(webinar_id, **data), 204, expects_json=False)
|
||||
|
||||
def delete_webinar(self, webinar_id):
|
||||
return _handle_response(self.client.webinar.delete(webinar_id), 204, expects_json=False)
|
||||
|
||||
def check_user_meeting_time(self, user_id, start_dt, end_dt):
|
||||
pass
|
||||
|
||||
def get_user(self, user_id):
|
||||
return _handle_response(self.client.user.get(user_id))
|
||||
|
||||
def get_assistants_for_user(self, user_id):
|
||||
return _handle_response(self.client.user.get_assistants(user_id))
|
||||
|
||||
def add_assistant_to_user(self, user_id, assistant_email):
|
||||
return _handle_response(self.client.user.add_assistant(user_id, assistants=[{'email': assistant_email}]), 201)
|
||||
14
vc_zoom/indico_vc_zoom/blueprint.py
Normal file
14
vc_zoom/indico_vc_zoom/blueprint.py
Normal file
@ -0,0 +1,14 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from indico.core.plugins import IndicoPluginBlueprint
|
||||
|
||||
from indico_vc_zoom.controllers import RHRoomHost
|
||||
|
||||
|
||||
blueprint = IndicoPluginBlueprint('vc_zoom', 'indico_vc_zoom')
|
||||
|
||||
# Room management
|
||||
# using any(zoom) instead of defaults since the event vc room locator
|
||||
# includes the service and normalization skips values provided in 'defaults'
|
||||
blueprint.add_url_rule('/event/<confId>/manage/videoconference/zoom/<int:event_vc_room_id>/room-host',
|
||||
'set_room_host', RHRoomHost, methods=('POST',))
|
||||
34
vc_zoom/indico_vc_zoom/cli.py
Normal file
34
vc_zoom/indico_vc_zoom/cli.py
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import click
|
||||
from terminaltables import AsciiTable
|
||||
|
||||
from indico.cli.core import cli_group
|
||||
from indico.modules.vc.models.vc_rooms import VCRoom, VCRoomStatus
|
||||
|
||||
|
||||
@cli_group(name='zoom')
|
||||
def cli():
|
||||
"""Manage the Zoom plugin."""
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--status', type=click.Choice(['deleted', 'created']))
|
||||
def rooms(status=None):
|
||||
"""Lists all Zoom rooms"""
|
||||
|
||||
room_query = VCRoom.find(type='zoom')
|
||||
table_data = [['ID', 'Name', 'Status', 'Zoom ID']]
|
||||
|
||||
if status:
|
||||
room_query = room_query.filter(VCRoom.status == VCRoomStatus.get(status))
|
||||
|
||||
for room in room_query:
|
||||
table_data.append([unicode(room.id), room.name, room.status.name, unicode(room.data['zoom_id'])])
|
||||
|
||||
table = AsciiTable(table_data)
|
||||
for col in (0, 3, 4):
|
||||
table.justify_columns[col] = 'right'
|
||||
print table.table
|
||||
34
vc_zoom/indico_vc_zoom/client/index.js
Normal file
34
vc_zoom/indico_vc_zoom/client/index.js
Normal file
@ -0,0 +1,34 @@
|
||||
// This file is part of the Indico plugins.
|
||||
// Copyright (C) 2002 - 2019 CERN
|
||||
//
|
||||
// The Indico plugins are free software; you can redistribute
|
||||
// them and/or modify them under the terms of the MIT License;
|
||||
// see the LICENSE file for more details.
|
||||
|
||||
$(function() {
|
||||
$('.vc-toolbar').dropdown({
|
||||
positioning: {
|
||||
level1: {my: 'right top', at: 'right bottom', offset: '0px 0px'},
|
||||
},
|
||||
});
|
||||
|
||||
$('.vc-toolbar .action-make-owner').click(function() {
|
||||
const $this = $(this);
|
||||
|
||||
$.ajax({
|
||||
url: $this.data('href'),
|
||||
method: 'POST',
|
||||
complete: IndicoUI.Dialogs.Util.progress(),
|
||||
})
|
||||
.done(function(result) {
|
||||
if (handleAjaxError(result)) {
|
||||
return;
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
})
|
||||
.fail(function(error) {
|
||||
handleAjaxError(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
26
vc_zoom/indico_vc_zoom/controllers.py
Normal file
26
vc_zoom/indico_vc_zoom/controllers.py
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from flask import flash, jsonify, session
|
||||
|
||||
from indico.core.db import db
|
||||
from indico.modules.vc.controllers import RHVCSystemEventBase
|
||||
from indico.modules.vc.exceptions import VCRoomError
|
||||
from indico.util.i18n import _
|
||||
|
||||
|
||||
class RHRoomHost(RHVCSystemEventBase):
|
||||
def _process(self):
|
||||
result = {}
|
||||
self.vc_room.data['host'] = session.user.identifier
|
||||
try:
|
||||
self.plugin.update_room(self.vc_room, self.event)
|
||||
except VCRoomError as err:
|
||||
result['error'] = {'message': err.message}
|
||||
result['success'] = False
|
||||
db.session.rollback()
|
||||
else:
|
||||
flash(_("You are now the host of room '{room.name}'".format(room=self.vc_room)), 'success')
|
||||
result['success'] = True
|
||||
return jsonify(result)
|
||||
93
vc_zoom/indico_vc_zoom/forms.py
Normal file
93
vc_zoom/indico_vc_zoom/forms.py
Normal file
@ -0,0 +1,93 @@
|
||||
from __future__ import unicode_literals
|
||||
from flask import session
|
||||
|
||||
from wtforms.fields.core import BooleanField
|
||||
from wtforms.fields.simple import TextAreaField
|
||||
from wtforms.validators import DataRequired, ValidationError
|
||||
|
||||
from indico.modules.vc.forms import VCRoomAttachFormBase, VCRoomFormBase
|
||||
from indico.util.user import principal_from_identifier
|
||||
from indico.web.forms.base import generated_data
|
||||
from indico.web.forms.fields import IndicoRadioField, PrincipalField
|
||||
from indico.web.forms.validators import HiddenUnless
|
||||
from indico.web.forms.widgets import SwitchWidget
|
||||
|
||||
from indico_vc_zoom import _
|
||||
|
||||
|
||||
class VCRoomAttachForm(VCRoomAttachFormBase):
|
||||
password_visibility = IndicoRadioField(_("Password visibility"),
|
||||
description=_("Who should be able to know this meeting's password"),
|
||||
orientation='horizontal',
|
||||
choices=[
|
||||
('everyone', _('Everyone')),
|
||||
('logged_in', _('Logged-in users')),
|
||||
('no_one', _("No one"))])
|
||||
|
||||
|
||||
class VCRoomForm(VCRoomFormBase):
|
||||
"""Contains all information concerning a Zoom booking."""
|
||||
|
||||
advanced_fields = {'mute_audio', 'mute_host_video', 'mute_participant_video'} | VCRoomFormBase.advanced_fields
|
||||
|
||||
skip_fields = advanced_fields | VCRoomFormBase.conditional_fields
|
||||
|
||||
meeting_type = IndicoRadioField(_("Meeting Type"),
|
||||
description=_("The type of Zoom meeting ot be created"),
|
||||
orientation='horizontal',
|
||||
choices=[
|
||||
('regular', _('Regular Meeting')),
|
||||
('webinar', _('Webinar'))])
|
||||
|
||||
host_choice = IndicoRadioField(_("Meeting Host"), [DataRequired()],
|
||||
choices=[('myself', _("Myself")), ('someone_else', _("Someone else"))])
|
||||
|
||||
host_user = PrincipalField(_("User"),
|
||||
[HiddenUnless('host_choice', 'someone_else'), DataRequired()])
|
||||
|
||||
password_visibility = IndicoRadioField(_("Password visibility"),
|
||||
description=_("Who should be able to know this meeting's password"),
|
||||
orientation='horizontal',
|
||||
choices=[
|
||||
('everyone', _('Everyone')),
|
||||
('logged_in', _('Logged-in users')),
|
||||
('no_one', _("No one"))])
|
||||
|
||||
mute_audio = BooleanField(_('Mute audio'),
|
||||
[HiddenUnless('meeting_type', 'regular')],
|
||||
widget=SwitchWidget(),
|
||||
description=_('Participants will join the VC room muted by default '))
|
||||
|
||||
mute_host_video = BooleanField(_('Mute video (host)'),
|
||||
widget=SwitchWidget(),
|
||||
description=_('The host will join the VC room with video disabled'))
|
||||
|
||||
mute_participant_video = BooleanField(_('Mute video (participants)'),
|
||||
[HiddenUnless('meeting_type', 'regular')],
|
||||
widget=SwitchWidget(),
|
||||
description=_('Participants will join the VC room with video disabled'))
|
||||
|
||||
waiting_room = BooleanField(_('Waiting room'),
|
||||
[HiddenUnless('meeting_type', 'regular')],
|
||||
widget=SwitchWidget(),
|
||||
description=_('Participants may be kept in a waiting room by the host'))
|
||||
|
||||
description = TextAreaField(_('Description'), description=_('Optional description for this room'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
defaults = kwargs['obj']
|
||||
if defaults.host_user is None and defaults.host is not None:
|
||||
host = principal_from_identifier(defaults.host)
|
||||
defaults.host_choice = 'myself' if host == session.user else 'someone_else'
|
||||
defaults.host_user = None if host == session.user else host
|
||||
super(VCRoomForm, self).__init__(*args, **kwargs)
|
||||
|
||||
@generated_data
|
||||
def host(self):
|
||||
if self.host_choice is None:
|
||||
return None
|
||||
return session.user.identifier if self.host_choice.data == 'myself' else self.host_user.data.identifier
|
||||
|
||||
def validate_host_user(self, field):
|
||||
if not field.data:
|
||||
raise ValidationError(_("Unable to find this user in Indico."))
|
||||
51
vc_zoom/indico_vc_zoom/http_api.py
Normal file
51
vc_zoom/indico_vc_zoom/http_api.py
Normal file
@ -0,0 +1,51 @@
|
||||
|
||||
|
||||
from flask import request
|
||||
|
||||
from indico.modules.vc.models.vc_rooms import VCRoom, VCRoomStatus
|
||||
from indico.web.http_api.hooks.base import HTTPAPIHook
|
||||
|
||||
|
||||
class DeleteVCRoomAPI(HTTPAPIHook):
|
||||
PREFIX = 'api'
|
||||
TYPES = ('deletevcroom',)
|
||||
RE = r'zoom'
|
||||
GUEST_ALLOWED = False
|
||||
VALID_FORMATS = ('json',)
|
||||
COMMIT = True
|
||||
HTTP_POST = True
|
||||
|
||||
def _has_access(self, user):
|
||||
from indico_vc_zoom.plugin import ZoomPlugin
|
||||
return user in ZoomPlugin.settings.acls.get('managers')
|
||||
|
||||
def _getParams(self):
|
||||
super(DeleteVCRoomAPI, self)._getParams()
|
||||
self._room_ids = map(int, request.form.getlist('rid'))
|
||||
|
||||
def api_deletevcroom(self, user):
|
||||
from indico_vc_zoom.plugin import ZoomPlugin
|
||||
from indico_vc_zoom.api import APIException
|
||||
|
||||
success = []
|
||||
failed = []
|
||||
not_in_db = []
|
||||
|
||||
for rid in self._room_ids:
|
||||
room = VCRoom.query.filter(VCRoom.type == 'zoom',
|
||||
VCRoom.status == VCRoomStatus.created,
|
||||
VCRoom.data.contains({'zoom_id': str(rid)})).first()
|
||||
if not room:
|
||||
not_in_db.append(rid)
|
||||
continue
|
||||
try:
|
||||
room.plugin.delete_meeting(room, None)
|
||||
except APIException:
|
||||
failed.append(rid)
|
||||
ZoomPlugin.logger.exception('Could not delete VC room %s', room)
|
||||
else:
|
||||
room.status = VCRoomStatus.deleted
|
||||
success.append(rid)
|
||||
ZoomPlugin.logger.info('%s deleted', room)
|
||||
|
||||
return {'success': success, 'failed': failed, 'missing': not_in_db}
|
||||
37
vc_zoom/indico_vc_zoom/notifications.py
Normal file
37
vc_zoom/indico_vc_zoom/notifications.py
Normal file
@ -0,0 +1,37 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from indico.web.flask.templating import get_template_module
|
||||
from indico.core.notifications import make_email, send_email
|
||||
from indico.util.user import principal_from_identifier
|
||||
|
||||
|
||||
def notify_host_start_url(vc_room):
|
||||
from indico_vc_zoom.plugin import ZoomPlugin
|
||||
|
||||
user = principal_from_identifier(vc_room.data['host'])
|
||||
to_list = {user.email}
|
||||
|
||||
template_module = get_template_module(
|
||||
'vc_zoom:emails/notify_start_url.html',
|
||||
plugin=ZoomPlugin.instance,
|
||||
vc_room=vc_room,
|
||||
user=user
|
||||
)
|
||||
|
||||
email = make_email(to_list, template=template_module, html=True)
|
||||
send_email(email, None, 'Zoom')
|
||||
|
||||
|
||||
def notify_new_host(actor, vc_room):
|
||||
from indico_vc_zoom.plugin import ZoomPlugin
|
||||
|
||||
template_module = get_template_module(
|
||||
'vc_zoom:emails/notify_new_host.html',
|
||||
plugin=ZoomPlugin.instance,
|
||||
vc_room=vc_room,
|
||||
actor=actor
|
||||
)
|
||||
|
||||
new_host = principal_from_identifier(vc_room.data['host'])
|
||||
email = make_email({new_host.email}, cc_list={actor.email}, template=template_module, html=True)
|
||||
send_email(email, None, 'Zoom')
|
||||
478
vc_zoom/indico_vc_zoom/plugin.py
Normal file
478
vc_zoom/indico_vc_zoom/plugin.py
Normal file
@ -0,0 +1,478 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import random
|
||||
import string
|
||||
|
||||
from flask import flash, session
|
||||
from requests.exceptions import HTTPError
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
from werkzeug.exceptions import Forbidden, NotFound
|
||||
from wtforms.fields.core import BooleanField
|
||||
from wtforms.fields import IntegerField, TextAreaField
|
||||
from wtforms.fields.html5 import EmailField, URLField
|
||||
from wtforms.fields.simple import StringField
|
||||
from wtforms.validators import DataRequired, NumberRange
|
||||
|
||||
from indico.core import signals
|
||||
from indico.core.config import config
|
||||
from indico.core.plugins import IndicoPlugin, render_plugin_template, url_for_plugin
|
||||
from indico.modules.events.views import WPSimpleEventDisplay
|
||||
from indico.modules.vc import VCPluginMixin, VCPluginSettingsFormBase
|
||||
from indico.modules.vc.exceptions import VCRoomError, VCRoomNotFoundError
|
||||
from indico.modules.vc.models.vc_rooms import VCRoom
|
||||
from indico.modules.vc.views import WPVCEventPage, WPVCManageEvent
|
||||
from indico.util.date_time import now_utc
|
||||
from indico.util.user import principal_from_identifier
|
||||
from indico.web.forms.fields.simple import IndicoPasswordField
|
||||
from indico.web.forms.widgets import CKEditorWidget, SwitchWidget
|
||||
from indico.web.http_api.hooks.base import HTTPAPIHook
|
||||
|
||||
from indico_vc_zoom import _
|
||||
from indico_vc_zoom.api import ZoomIndicoClient
|
||||
from indico_vc_zoom.blueprint import blueprint
|
||||
from indico_vc_zoom.cli import cli
|
||||
from indico_vc_zoom.forms import VCRoomAttachForm, VCRoomForm
|
||||
from indico_vc_zoom.http_api import DeleteVCRoomAPI
|
||||
from indico_vc_zoom.notifications import notify_new_host, notify_host_start_url
|
||||
from indico_vc_zoom.util import find_enterprise_email
|
||||
|
||||
|
||||
def _gen_random_password():
|
||||
return ''.join(random.sample(string.ascii_lowercase + string.ascii_uppercase + string.digits, 10))
|
||||
|
||||
|
||||
def _fetch_zoom_meeting(vc_room, client=None, is_webinar=False):
|
||||
try:
|
||||
client = client or ZoomIndicoClient()
|
||||
if is_webinar:
|
||||
return client.get_webinar(vc_room.data['zoom_id'])
|
||||
return client.get_meeting(vc_room.data['zoom_id'])
|
||||
except HTTPError as e:
|
||||
if e.response.status_code in {400, 404}:
|
||||
# Indico will automatically mark this room as deleted
|
||||
raise VCRoomNotFoundError(_("This room has been deleted from Zoom"))
|
||||
else:
|
||||
ZoomPlugin.logger.exception('Error getting Zoom Room: %s', e.response.content)
|
||||
raise VCRoomError(_("Problem fetching room from Zoom. Please contact support if the error persists."))
|
||||
|
||||
|
||||
def _update_zoom_meeting(zoom_id, changes, is_webinar=False):
|
||||
client = ZoomIndicoClient()
|
||||
try:
|
||||
if is_webinar:
|
||||
client.update_webinar(zoom_id, changes)
|
||||
else:
|
||||
client.update_meeting(zoom_id, changes)
|
||||
except HTTPError as e:
|
||||
ZoomPlugin.logger.exception("Error updating meeting '%s': %s", zoom_id, e.response.content)
|
||||
raise VCRoomError(_("Can't update meeting. Please contact support if the error persists."))
|
||||
|
||||
|
||||
def _get_schedule_args(obj):
|
||||
duration = obj.end_dt - obj.start_dt
|
||||
|
||||
if obj.start_dt < now_utc():
|
||||
return {}
|
||||
|
||||
return {
|
||||
'start_time': obj.start_dt,
|
||||
'duration': duration.total_seconds() / 60,
|
||||
}
|
||||
|
||||
|
||||
class PluginSettingsForm(VCPluginSettingsFormBase):
|
||||
support_email = EmailField(_('Zoom email support'))
|
||||
|
||||
api_key = StringField(_('API Key'), [DataRequired()])
|
||||
|
||||
api_secret = IndicoPasswordField(_('API Secret'), [DataRequired()], toggle=True)
|
||||
|
||||
email_domains = StringField(_('E-mail domains'), [DataRequired()],
|
||||
description=_("Comma-separated list of e-mail domains which can use the Zoom API."))
|
||||
|
||||
assistant_id = StringField(_('Assistant Zoom ID'), [DataRequired()],
|
||||
description=_('Account to be used as owner of all rooms. It will get "assistant" '
|
||||
'privileges on all accounts for which it books rooms'))
|
||||
|
||||
mute_audio = BooleanField(_('Mute audio'),
|
||||
widget=SwitchWidget(),
|
||||
description=_('Participants will join the VC room muted by default '))
|
||||
|
||||
mute_host_video = BooleanField(_('Mute video (host)'),
|
||||
widget=SwitchWidget(),
|
||||
description=_('The host will join the VC room with video disabled'))
|
||||
|
||||
mute_participant_video = BooleanField(_('Mute video (participants)'),
|
||||
widget=SwitchWidget(),
|
||||
description=_('Participants will join the VC room with video disabled'))
|
||||
|
||||
join_before_host = BooleanField(_('Join Before Host'),
|
||||
widget=SwitchWidget(),
|
||||
description=_('Allow participants to join the meeting before the host starts the '
|
||||
'meeting. Only used for scheduled or recurring meetings.'))
|
||||
|
||||
waiting_room = BooleanField(_('Waiting room'),
|
||||
widget=SwitchWidget(),
|
||||
description=_('Participants may be kept in a waiting room by the host'))
|
||||
|
||||
num_days_old = IntegerField(_('VC room age threshold'), [NumberRange(min=1), DataRequired()],
|
||||
description=_('Number of days after an Indico event when a videoconference room is '
|
||||
'considered old'))
|
||||
max_rooms_warning = IntegerField(_('Max. num. VC rooms before warning'), [NumberRange(min=1), DataRequired()],
|
||||
description=_('Maximum number of rooms until a warning is sent to the managers'))
|
||||
zoom_phone_link = URLField(_('ZoomVoice phone number'),
|
||||
description=_('Link to the list of ZoomVoice phone numbers'))
|
||||
|
||||
creation_email_footer = TextAreaField(_('Creation email footer'), widget=CKEditorWidget(),
|
||||
description=_('Footer to append to emails sent upon creation of a VC room'))
|
||||
|
||||
send_host_url = BooleanField(_('Send host URL'),
|
||||
widget=SwitchWidget(),
|
||||
description=_('Whether to send an e-mail with the Host URL to the meeting host upon '
|
||||
'creation of a meeting'))
|
||||
|
||||
|
||||
class ZoomPlugin(VCPluginMixin, IndicoPlugin):
|
||||
"""Zoom
|
||||
|
||||
Zoom Plugin for Indico."""
|
||||
|
||||
configurable = True
|
||||
settings_form = PluginSettingsForm
|
||||
vc_room_form = VCRoomForm
|
||||
vc_room_attach_form = VCRoomAttachForm
|
||||
friendly_name = 'Zoom'
|
||||
|
||||
def init(self):
|
||||
super(ZoomPlugin, self).init()
|
||||
self.connect(signals.plugin.cli, self._extend_indico_cli)
|
||||
self.connect(signals.event.times_changed, self._times_changed)
|
||||
self.template_hook('event-vc-room-list-item-labels', self._render_vc_room_labels)
|
||||
self.inject_bundle('main.js', WPSimpleEventDisplay)
|
||||
self.inject_bundle('main.js', WPVCEventPage)
|
||||
self.inject_bundle('main.js', WPVCManageEvent)
|
||||
HTTPAPIHook.register(DeleteVCRoomAPI)
|
||||
|
||||
@property
|
||||
def default_settings(self):
|
||||
return dict(VCPluginMixin.default_settings, **{
|
||||
'support_email': config.SUPPORT_EMAIL,
|
||||
'assistant_id': config.SUPPORT_EMAIL,
|
||||
'api_key': '',
|
||||
'api_secret': '',
|
||||
'email_domains': '',
|
||||
'mute_host_video': True,
|
||||
'mute_audio': True,
|
||||
'mute_participant_video': True,
|
||||
'join_before_host': True,
|
||||
'waiting_room': False,
|
||||
'num_days_old': 5,
|
||||
'max_rooms_warning': 5000,
|
||||
'zoom_phone_link': None,
|
||||
'creation_email_footer': None,
|
||||
'send_host_url': False
|
||||
})
|
||||
|
||||
@property
|
||||
def logo_url(self):
|
||||
return url_for_plugin(self.name + '.static', filename='images/zoom_logo.png')
|
||||
|
||||
@property
|
||||
def icon_url(self):
|
||||
return url_for_plugin(self.name + '.static', filename='images/zoom_logo.png')
|
||||
|
||||
def create_form(self, event, existing_vc_room=None, existing_event_vc_room=None):
|
||||
"""Override the default room form creation mechanism."""
|
||||
form = super(ZoomPlugin, self).create_form(
|
||||
event,
|
||||
existing_vc_room=existing_vc_room,
|
||||
existing_event_vc_room=existing_event_vc_room
|
||||
)
|
||||
|
||||
if existing_vc_room:
|
||||
# if we're editing a VC room, we will not allow the meeting type to be changed
|
||||
form.meeting_type.render_kw = {'disabled': True}
|
||||
|
||||
if form.data['meeting_type'] == 'webinar':
|
||||
# webinar hosts cannot be changed through the API
|
||||
form.host_choice.render_kw = {'disabled': True}
|
||||
form.host_user.render_kw = {'disabled': True}
|
||||
|
||||
return form
|
||||
|
||||
def _extend_indico_cli(self, sender, **kwargs):
|
||||
return cli
|
||||
|
||||
def update_data_association(self, event, vc_room, room_assoc, data):
|
||||
# XXX: This feels slightly hacky. Maybe we should change the API on the core?
|
||||
association_is_new = room_assoc.vc_room is None
|
||||
old_link = room_assoc.link_object
|
||||
super(ZoomPlugin, self).update_data_association(event, vc_room, room_assoc, data)
|
||||
|
||||
if vc_room.data:
|
||||
# this is not a new room
|
||||
if association_is_new:
|
||||
# this means we are updating an existing meeting with a new vc_room-event association
|
||||
_update_zoom_meeting(vc_room.data['zoom_id'], {
|
||||
'start_time': None,
|
||||
'duration': None,
|
||||
'type': 3
|
||||
})
|
||||
elif room_assoc.link_object != old_link:
|
||||
# the booking should now be linked to something else
|
||||
new_schedule_args = _get_schedule_args(room_assoc.link_object)
|
||||
meeting = _fetch_zoom_meeting(vc_room)
|
||||
current_schedule_args = {k: meeting[k] for k in {'start_time', 'duration'}}
|
||||
|
||||
# check whether the start time / duration of the scheduled meeting differs
|
||||
if new_schedule_args != current_schedule_args:
|
||||
_update_zoom_meeting(vc_room.data['zoom_id'], new_schedule_args)
|
||||
|
||||
room_assoc.data['password_visibility'] = data.pop('password_visibility')
|
||||
flag_modified(room_assoc, 'data')
|
||||
|
||||
def update_data_vc_room(self, vc_room, data, is_new=False):
|
||||
super(ZoomPlugin, self).update_data_vc_room(vc_room, data)
|
||||
fields = {'description'}
|
||||
if data['meeting_type'] == 'webinar':
|
||||
fields |= {'mute_host_video'}
|
||||
if is_new:
|
||||
fields |= {'host', 'meeting_type'}
|
||||
else:
|
||||
fields |= {
|
||||
'meeting_type', 'host', 'mute_audio', 'mute_participant_video', 'mute_host_video', 'join_before_host',
|
||||
'waiting_room'
|
||||
}
|
||||
|
||||
for key in fields:
|
||||
if key in data:
|
||||
vc_room.data[key] = data.pop(key)
|
||||
|
||||
flag_modified(vc_room, 'data')
|
||||
|
||||
def _check_indico_is_assistant(self, user_id):
|
||||
client = ZoomIndicoClient()
|
||||
assistant_id = self.settings.get('assistant_id')
|
||||
|
||||
if user_id != assistant_id:
|
||||
try:
|
||||
assistants = {assist['email'] for assist in client.get_assistants_for_user(user_id)['assistants']}
|
||||
except HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
raise NotFound(_("No Zoom account found for this user"))
|
||||
self.logger.exception('Error getting assistants for account %s: %s', user_id, e.response.content)
|
||||
raise VCRoomError(_("Problem getting information about Zoom account"))
|
||||
if assistant_id not in assistants:
|
||||
client.add_assistant_to_user(user_id, assistant_id)
|
||||
|
||||
def create_room(self, vc_room, event):
|
||||
"""Create a new Zoom room for an event, given a VC room.
|
||||
|
||||
In order to create the Zoom room, the function will try to get
|
||||
a valid e-mail address for the user in question, which can be
|
||||
use with the Zoom API.
|
||||
|
||||
:param vc_room: the VC room from which to create the Zoom room
|
||||
:param event: the event to the Zoom room will be attached
|
||||
"""
|
||||
client = ZoomIndicoClient()
|
||||
host = principal_from_identifier(vc_room.data['host'])
|
||||
host_email = find_enterprise_email(host)
|
||||
|
||||
# get the object that this booking is linked to
|
||||
vc_room_assoc = vc_room.events[0]
|
||||
link_obj = vc_room_assoc.link_object
|
||||
is_webinar = vc_room.data['meeting_type'] == 'webinar'
|
||||
scheduling_args = _get_schedule_args(link_obj) if link_obj.start_dt else {}
|
||||
|
||||
self._check_indico_is_assistant(host_email)
|
||||
|
||||
try:
|
||||
settings = {
|
||||
'host_video': vc_room.data['mute_host_video'],
|
||||
}
|
||||
|
||||
kwargs = {}
|
||||
if is_webinar:
|
||||
kwargs = {
|
||||
'type': 5 if scheduling_args else 6,
|
||||
'host_email': host_email
|
||||
}
|
||||
else:
|
||||
kwargs = {
|
||||
'type': 2 if scheduling_args else 3,
|
||||
'schedule_for': host_email
|
||||
}
|
||||
settings.update({
|
||||
'mute_upon_entry': vc_room.data['mute_audio'],
|
||||
'participant_video': not vc_room.data['mute_participant_video'],
|
||||
'waiting_room': vc_room.data['waiting_room'],
|
||||
'join_before_host': self.settings.get('join_before_host'),
|
||||
})
|
||||
|
||||
kwargs.update({
|
||||
'topic': vc_room.name,
|
||||
'password': _gen_random_password(),
|
||||
'timezone': event.timezone,
|
||||
'settings': settings
|
||||
})
|
||||
kwargs.update(scheduling_args)
|
||||
if is_webinar:
|
||||
meeting_obj = client.create_webinar(self.settings.get('assistant_id'), **kwargs)
|
||||
else:
|
||||
meeting_obj = client.create_meeting(self.settings.get('assistant_id'), **kwargs)
|
||||
except HTTPError as e:
|
||||
self.logger.exception('Error creating Zoom Room: %s', e.response.content)
|
||||
raise VCRoomError(_("Could not create the room in Zoom. Please contact support if the error persists"))
|
||||
|
||||
vc_room.data.update({
|
||||
'zoom_id': unicode(meeting_obj['id']),
|
||||
'url': meeting_obj['join_url'],
|
||||
'public_url': meeting_obj['join_url'].split('?')[0],
|
||||
'start_url': meeting_obj['start_url'],
|
||||
'password': meeting_obj['password'],
|
||||
'host': host.identifier
|
||||
})
|
||||
|
||||
flag_modified(vc_room, 'data')
|
||||
|
||||
# e-mail Host URL to meeting host
|
||||
if self.settings.get('send_host_url'):
|
||||
notify_host_start_url(vc_room)
|
||||
|
||||
def update_room(self, vc_room, event):
|
||||
client = ZoomIndicoClient()
|
||||
is_webinar = vc_room.data['meeting_type'] == 'webinar'
|
||||
zoom_meeting = _fetch_zoom_meeting(vc_room, client=client, is_webinar=is_webinar)
|
||||
changes = {}
|
||||
|
||||
host = principal_from_identifier(vc_room.data['host'])
|
||||
host_id = zoom_meeting['host_id']
|
||||
|
||||
try:
|
||||
host_data = client.get_user(host_id)
|
||||
except HTTPError as e:
|
||||
self.logger.exception("Error retrieving user '%s': %s", host_id, e.response.content)
|
||||
raise VCRoomError(_("Can't get information about user. Please contact support if the error persists."))
|
||||
|
||||
# host changed
|
||||
if host_data['email'] not in host.all_emails:
|
||||
email = find_enterprise_email(host)
|
||||
|
||||
if not email:
|
||||
raise Forbidden(_("This user doesn't seem to have an associated Zoom account"))
|
||||
|
||||
changes['host_email' if is_webinar else 'schedule_for'] = email
|
||||
self._check_indico_is_assistant(email)
|
||||
notify_new_host(session.user, vc_room)
|
||||
|
||||
if vc_room.name != zoom_meeting['topic']:
|
||||
changes['topic'] = vc_room.name
|
||||
|
||||
zoom_meeting_settings = zoom_meeting['settings']
|
||||
if vc_room.data['mute_host_video'] == zoom_meeting_settings['host_video']:
|
||||
changes.setdefault('settings', {})['host_video'] = not vc_room.data['mute_host_video']
|
||||
|
||||
if not is_webinar:
|
||||
if vc_room.data['mute_audio'] != zoom_meeting_settings['mute_upon_entry']:
|
||||
changes.setdefault('settings', {})['mute_upon_entry'] = vc_room.data['mute_audio']
|
||||
if vc_room.data['mute_participant_video'] == zoom_meeting_settings['participant_video']:
|
||||
changes.setdefault('settings', {})['participant_video'] = not vc_room.data['mute_participant_video']
|
||||
if vc_room.data['waiting_room'] != zoom_meeting_settings['waiting_room']:
|
||||
changes.setdefault('settings', {})['waiting_room'] = vc_room.data['waiting_room']
|
||||
|
||||
if changes:
|
||||
_update_zoom_meeting(vc_room.data['zoom_id'], changes, is_webinar=is_webinar)
|
||||
|
||||
def refresh_room(self, vc_room, event):
|
||||
is_webinar = vc_room.data['meeting_type'] == 'webinar'
|
||||
zoom_meeting = _fetch_zoom_meeting(vc_room, is_webinar=is_webinar)
|
||||
vc_room.name = zoom_meeting['topic']
|
||||
vc_room.data.update({
|
||||
'url': zoom_meeting['join_url'],
|
||||
'public_url': zoom_meeting['join_url'].split('?')[0],
|
||||
'zoom_id': zoom_meeting['id']
|
||||
})
|
||||
flag_modified(vc_room, 'data')
|
||||
|
||||
def delete_room(self, vc_room, event):
|
||||
client = ZoomIndicoClient()
|
||||
zoom_id = vc_room.data['zoom_id']
|
||||
is_webinar = vc_room.data['meeting_type'] == 'webinar'
|
||||
try:
|
||||
if is_webinar:
|
||||
client.delete_webinar(zoom_id)
|
||||
else:
|
||||
client.delete_meeting(zoom_id)
|
||||
except HTTPError as e:
|
||||
# if there's a 404, there is no problem, since the room is supposed to be gone anyway
|
||||
if not e.response.status_code == 404:
|
||||
self.logger.exception('Error getting Zoom Room: %s', e.response.content)
|
||||
raise VCRoomError(_("Problem fetching room from Zoom. Please contact support if the error persists."))
|
||||
|
||||
def get_blueprints(self):
|
||||
return blueprint
|
||||
|
||||
def get_vc_room_form_defaults(self, event):
|
||||
defaults = super(ZoomPlugin, self).get_vc_room_form_defaults(event)
|
||||
defaults.update({
|
||||
'meeting_type': 'regular',
|
||||
'mute_audio': self.settings.get('mute_audio'),
|
||||
'mute_host_video': self.settings.get('mute_host_video'),
|
||||
'mute_participant_video': self.settings.get('mute_participant_video'),
|
||||
'waiting_room': self.settings.get('waiting_room'),
|
||||
'host_choice': 'myself',
|
||||
'host_user': None,
|
||||
'password_visibility': 'logged_in'
|
||||
})
|
||||
return defaults
|
||||
|
||||
def get_vc_room_attach_form_defaults(self, event):
|
||||
defaults = super(ZoomPlugin, self).get_vc_room_attach_form_defaults(event)
|
||||
defaults['password_visibility'] = 'logged_in'
|
||||
return defaults
|
||||
|
||||
def can_manage_vc_room(self, user, room):
|
||||
return (
|
||||
user == principal_from_identifier(room.data['host']) or
|
||||
super(ZoomPlugin, self).can_manage_vc_room(user, room)
|
||||
)
|
||||
|
||||
def _merge_users(self, target, source, **kwargs):
|
||||
super(ZoomPlugin, self)._merge_users(target, source, **kwargs)
|
||||
for room in VCRoom.query.filter(
|
||||
VCRoom.type == self.service_name, VCRoom.data.contains({'host': source.identifier})
|
||||
):
|
||||
room.data['host'] = target.id
|
||||
flag_modified(room, 'data')
|
||||
|
||||
def get_notification_cc_list(self, action, vc_room, event):
|
||||
return {principal_from_identifier(vc_room.data['host']).email}
|
||||
|
||||
def _render_vc_room_labels(self, event, vc_room, **kwargs):
|
||||
if vc_room.plugin != self:
|
||||
return
|
||||
return render_plugin_template('room_labels.html', vc_room=vc_room)
|
||||
|
||||
def _times_changed(self, sender, obj, **kwargs):
|
||||
from indico.modules.events.models.events import Event
|
||||
from indico.modules.events.contributions.models.contributions import Contribution
|
||||
from indico.modules.events.sessions.models.blocks import SessionBlock
|
||||
|
||||
if not hasattr(obj, 'vc_room_associations'):
|
||||
return
|
||||
|
||||
if any(assoc.vc_room.type == 'zoom' and len(assoc.vc_room.events) == 1 for assoc in obj.vc_room_associations):
|
||||
if sender == Event:
|
||||
message = _("There are one or more scheduled Zoom meetings associated with this event which were not "
|
||||
"automatically updated.")
|
||||
elif sender == Contribution:
|
||||
message = _("There are one or more scheduled Zoom meetings associated with contribution '{}' which "
|
||||
" were not automatically updated.").format(obj.title)
|
||||
elif sender == SessionBlock:
|
||||
message = _("There are one or more scheduled Zoom meetings associated with this session block which "
|
||||
"were not automatically updated.")
|
||||
else:
|
||||
return
|
||||
|
||||
flash(message, 'warning')
|
||||
BIN
vc_zoom/indico_vc_zoom/static/images/zoom_logo.png
Normal file
BIN
vc_zoom/indico_vc_zoom/static/images/zoom_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
21
vc_zoom/indico_vc_zoom/templates/buttons.html
Normal file
21
vc_zoom/indico_vc_zoom/templates/buttons.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% macro render_join_button(vc_room, event_vc_room, extra_classes="", is_manager=false) %}
|
||||
{% if event_vc_room.data.password_visibility == 'everyone' or is_manager or
|
||||
(session.user and event_vc_room.data.password_visibility == 'logged_in') %}
|
||||
<a class="i-button icon-play highlight {{ extra_classes }}"
|
||||
href="{{ vc_room.data.url }}" target="_blank">
|
||||
{% trans %}Join{% endtrans %}
|
||||
</a>
|
||||
{% elif event_vc_room.data.password_visibility == 'no_one' %}
|
||||
<a class="i-button icon-lock-center warning {{ extra_classes }}"
|
||||
title="{% trans %}You will need a password to join this Zoom meeting{% endtrans %}"
|
||||
href="{{ vc_room.data.public_url }}">
|
||||
{% trans %}Join{% endtrans %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a class="i-button icon-lock-center {{ extra_classes }}"
|
||||
title="{% trans %}This Zoom Meeting can only be seen by logged in users{% endtrans %}"
|
||||
href="{{ url_for_login(request.relative_url) }}">
|
||||
{% trans %}Please log in{% endtrans %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
20
vc_zoom/indico_vc_zoom/templates/emails/created.html
Normal file
20
vc_zoom/indico_vc_zoom/templates/emails/created.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% extends 'vc/emails/created.html' %}
|
||||
|
||||
{% block plugin_specific_info %}
|
||||
<li>
|
||||
<strong>Host</strong>:
|
||||
<span>{{ (vc_room.data.host|decodeprincipal).full_name }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Zoom URL</strong>:
|
||||
<a href="{{ vc_room.data.url }}">{{ vc_room.data.url }}</a>
|
||||
</li>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_footer %}
|
||||
{% if plugin.settings.get('creation_email_footer') %}
|
||||
<hr>
|
||||
{{ plugin.settings.get('creation_email_footer') | sanitize_html }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
1
vc_zoom/indico_vc_zoom/templates/emails/deleted.html
Normal file
1
vc_zoom/indico_vc_zoom/templates/emails/deleted.html
Normal file
@ -0,0 +1 @@
|
||||
{% extends 'vc/emails/deleted.html' %}
|
||||
25
vc_zoom/indico_vc_zoom/templates/emails/notify_new_host.html
Normal file
25
vc_zoom/indico_vc_zoom/templates/emails/notify_new_host.html
Normal file
@ -0,0 +1,25 @@
|
||||
{% extends 'emails/base.html' %}
|
||||
|
||||
{% block subject -%}
|
||||
[{{ plugin.friendly_name }}] You are now hosting '{{ vc_room.name }}'
|
||||
{%- endblock %}
|
||||
|
||||
{% block header -%}
|
||||
<p>
|
||||
<strong>{{ actor.full_name }}</strong> has just made you the host of Zoom Meeting '{{ vc_room.name }}'.
|
||||
</p>
|
||||
{% if plugin.settings.get('send_host_url') %}
|
||||
<p>
|
||||
<strong>ATTENTION:</strong>
|
||||
You should not share this URL with anyone, since it will allow them to become meeting hosts!
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Host URL</strong>:
|
||||
<a href="{{ vc_room.data.start_url }}">{{ vc_room.data.start_url }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% block custom_footer %}{% endblock %}
|
||||
{%- endblock %}
|
||||
@ -0,0 +1,20 @@
|
||||
{% extends 'emails/base.html' %}
|
||||
|
||||
{% block subject -%}
|
||||
[{{ plugin.friendly_name }}] Host URL - {{ vc_room.name }}
|
||||
{%- endblock %}
|
||||
|
||||
{% block header -%}
|
||||
<p>
|
||||
<strong>ATTENTION:</strong>
|
||||
You should not share this URL with anyone, since it will allow them to become meeting hosts!
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Host URL</strong>:
|
||||
<a href="{{ vc_room.data.start_url }}">{{ vc_room.data.start_url }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% block custom_footer %}{% endblock %}
|
||||
{%- endblock %}
|
||||
16
vc_zoom/indico_vc_zoom/templates/emails/remote_deleted.html
Normal file
16
vc_zoom/indico_vc_zoom/templates/emails/remote_deleted.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends 'emails/base.html' %}
|
||||
|
||||
{% block subject -%}
|
||||
[{{ plugin.friendly_name }}] Room deleted from server: {{ vc_room.name }}
|
||||
{%- endblock %}
|
||||
|
||||
{% block header -%}
|
||||
<p>
|
||||
The Zoom room "{{ vc_room.name }}" has been deleted from the Zoom server since it has not been used by any recent event.
|
||||
</p>
|
||||
<p>
|
||||
You won't be able to attach it to any future events. If you need to do so, please create a new room.
|
||||
</p>
|
||||
{% block custom_footer %}{% endblock %}
|
||||
|
||||
{%- endblock %}
|
||||
6
vc_zoom/indico_vc_zoom/templates/event_buttons.html
Normal file
6
vc_zoom/indico_vc_zoom/templates/event_buttons.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% extends 'vc/event_buttons.html' %}
|
||||
{% from 'vc_zoom:buttons.html' import render_join_button %}
|
||||
|
||||
{% block buttons %}
|
||||
{{ render_join_button(vc_room, event_vc_room, "i-button-small event-service-right-button join-button") }}
|
||||
{% endblock %}
|
||||
32
vc_zoom/indico_vc_zoom/templates/info_box.html
Normal file
32
vc_zoom/indico_vc_zoom/templates/info_box.html
Normal file
@ -0,0 +1,32 @@
|
||||
{% from '_clipboard_input.html' import clipboard_input %}
|
||||
{% set host = vc_room.data.host %}
|
||||
{% set phone_link = settings.get('zoom_phone_link') %}
|
||||
<dl>
|
||||
<dt>{% trans %}Zoom Meeting ID{% endtrans %}</dt>
|
||||
<dd>{{ vc_room.data.zoom_id }}</dd>
|
||||
{% if host %}
|
||||
<dt>{% trans %}Host{% endtrans %}</dt>
|
||||
<dd>{{ (host|decodeprincipal).full_name }}</dd>
|
||||
{% endif %}
|
||||
{% if event_vc_room.data.password_visibility == 'everyone' or is_manager or
|
||||
(session.user and event_vc_room.data.password_visibility == 'logged_in') %}
|
||||
<dt>{% trans %}Password{% endtrans %}</dt>
|
||||
<dd>{{ vc_room.data.password }}</dd>
|
||||
{% endif %}
|
||||
{% if event_vc_room.data.show_autojoin %}
|
||||
<dt class="large-row">{% trans %}Zoom URL{% endtrans %}</dt>
|
||||
<dd class="large-row">
|
||||
{{ clipboard_input(vc_room.data.url, name="vc-room-url-%s"|format(event_vc_room.id)) }}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if event_vc_room.data.show_phone_numbers and phone_link %}
|
||||
<dt>
|
||||
{% trans %}Useful links{% endtrans %}
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="{{ phone_link }}" target="_blank">
|
||||
{% trans %}Phone numbers{% endtrans %}
|
||||
</a>
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
37
vc_zoom/indico_vc_zoom/templates/manage_event_info_box.html
Normal file
37
vc_zoom/indico_vc_zoom/templates/manage_event_info_box.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% from '_password.html' import password %}
|
||||
{% from '_clipboard_input.html' import clipboard_input %}
|
||||
{% set host = vc_room.data.host %}
|
||||
{% set phone_link = settings.get('zoom_phone_link') %}
|
||||
<dl class="details-container">
|
||||
<dt>{% trans %}Zoom Meeting ID{% endtrans %}</dt>
|
||||
<dd>{{ vc_room.data.zoom_id }}</dd>
|
||||
<dt>{% trans %}Host{% endtrans %}</dt>
|
||||
<dd>
|
||||
{{ (host|decodeprincipal).full_name }}
|
||||
</dd>
|
||||
<dt>{% trans %}Linked to{% endtrans %}</dt>
|
||||
<dd>
|
||||
{% set obj = event_vc_room.link_object %}
|
||||
{% if obj is none %}
|
||||
<em>(missing {{ event_vc_room.link_type.name }})</em>
|
||||
{% elif event_vc_room.link_type.name == 'event' %}
|
||||
{% trans %}the whole event{% endtrans %}
|
||||
{% elif event_vc_room.link_type.name == 'contribution' %}
|
||||
{% trans %}Contribution{% endtrans %}: {{ obj.title }}
|
||||
{% elif event_vc_room.link_type.name == 'block' %}
|
||||
{% trans %}Session{% endtrans %}: {{ obj.full_title }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt>{% trans %}Password{% endtrans %}</dt>
|
||||
<dd>{{ vc_room.data.password }}</dd>
|
||||
<dt>{% trans %}Zoom URL{% endtrans %}</dt>
|
||||
<dd>
|
||||
{{ clipboard_input(vc_room.data.url, name="vc-room-url") }}
|
||||
</dd>
|
||||
<dt>{% trans %}Created on{% endtrans %}</dt>
|
||||
<dd>{{ vc_room.created_dt | format_datetime(timezone=event.tzinfo) }}</dd>
|
||||
{% if vc_room.modified_dt %}
|
||||
<dt>{% trans %}Modified on{% endtrans %}</dt>
|
||||
<dd>{{ vc_room.modified_dt | format_datetime(timezone=event.tzinfo) }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
6
vc_zoom/indico_vc_zoom/templates/management_buttons.html
Normal file
6
vc_zoom/indico_vc_zoom/templates/management_buttons.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% extends 'vc/management_buttons.html' %}
|
||||
{% from 'vc_zoom:buttons.html' import render_join_button %}
|
||||
|
||||
{% block buttons %}
|
||||
{{ render_join_button(vc_room, event_vc_room, extra_classes="icon-play", is_manager=true) }}
|
||||
{% endblock %}
|
||||
6
vc_zoom/indico_vc_zoom/templates/room_labels.html
Normal file
6
vc_zoom/indico_vc_zoom/templates/room_labels.html
Normal file
@ -0,0 +1,6 @@
|
||||
{% if vc_room.data.meeting_type == 'webinar' %}
|
||||
<div class="i-label color-teal text-color outline"
|
||||
title="{% trans %}This is a Zoom webinar{% endtrans %}">
|
||||
{% trans %}Webinar{% endtrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -0,0 +1,7 @@
|
||||
{% extends 'vc/vc_room_timetable_buttons.html' %}
|
||||
{% from 'vc_zoom:buttons.html' import render_join_button %}
|
||||
{% set vc_room = event_vc_room.vc_room %}
|
||||
|
||||
{% block buttons %}
|
||||
{{ render_join_button(vc_room, event_vc_room, "i-button-small event-service-right-button join-button") }}
|
||||
{% endblock %}
|
||||
@ -0,0 +1,23 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2015 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
#
|
||||
# Translators:
|
||||
# Thomas Baron <thomas.baron@cern.ch>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Indico\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2015-03-11 16:21+0100\n"
|
||||
"PO-Revision-Date: 2015-03-12 12:52+0000\n"
|
||||
"Last-Translator: Thomas Baron <thomas.baron@cern.ch>\n"
|
||||
"Language-Team: French (France) (http://www.transifex.com/projects/p/indico/language/fr_FR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 1.3\n"
|
||||
"Language: fr_FR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
msgid "Indico"
|
||||
msgstr "Indico"
|
||||
@ -0,0 +1,289 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2017 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
#
|
||||
# Translators:
|
||||
# Thomas Baron <thomas.baron@cern.ch>, 2015,2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Indico\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2017-10-18 11:55+0200\n"
|
||||
"PO-Revision-Date: 2017-10-30 11:04+0000\n"
|
||||
"Last-Translator: Thomas Baron <thomas.baron@cern.ch>\n"
|
||||
"Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.5.1\n"
|
||||
"Language: fr_FR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: indico_vc_zoom/controllers.py:38
|
||||
msgid "You are now the owner of the room '{room.name}'"
|
||||
msgstr "Vous êtes maintenant responsable de la salle '{room.name}'"
|
||||
|
||||
#: indico_vc_zoom/forms.py:32
|
||||
msgid "The PIN must be a number"
|
||||
msgstr "Le code confidentiel doit être un nombre entier"
|
||||
|
||||
#: indico_vc_zoom/forms.py:37
|
||||
msgid "Show PIN"
|
||||
msgstr "Afficher le code confidentiel"
|
||||
|
||||
#: indico_vc_zoom/forms.py:39
|
||||
msgid "Show the VC Room PIN on the event page (insecure!)"
|
||||
msgstr "Afficher le code confidentiel de la salle Vidyo sur la page de l'événement (peu sûr!)"
|
||||
|
||||
#: indico_vc_zoom/forms.py:40
|
||||
msgid "Show Auto-join URL"
|
||||
msgstr "Afficher l'URL de connexion"
|
||||
|
||||
#: indico_vc_zoom/forms.py:42
|
||||
msgid "Show the auto-join URL on the event page"
|
||||
msgstr "Afficher l'URL de connexion sur la page de l'événement"
|
||||
|
||||
#: indico_vc_zoom/forms.py:43
|
||||
msgid "Show Phone Access numbers"
|
||||
msgstr "Afficher les numéros d'accès téléphonique"
|
||||
|
||||
#: indico_vc_zoom/forms.py:45
|
||||
msgid "Show a link to the list of phone access numbers"
|
||||
msgstr "Afficher un lien vers la liste des numéros d'accès téléphonique"
|
||||
|
||||
#: indico_vc_zoom/forms.py:58 indico_vc_zoom/templates/info_box.html:7
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:6
|
||||
msgid "Description"
|
||||
msgstr "Description"
|
||||
|
||||
#: indico_vc_zoom/forms.py:58
|
||||
msgid "The description of the room"
|
||||
msgstr "La description de la salle"
|
||||
|
||||
#: indico_vc_zoom/forms.py:59 indico_vc_zoom/templates/info_box.html:14
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:10
|
||||
msgid "Owner"
|
||||
msgstr "Responsable"
|
||||
|
||||
#: indico_vc_zoom/forms.py:59
|
||||
msgid "The owner of the room"
|
||||
msgstr "Le responsable de la salle"
|
||||
|
||||
#: indico_vc_zoom/forms.py:60
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:39
|
||||
msgid "Moderation PIN"
|
||||
msgstr "Code confidentiel de modération"
|
||||
|
||||
#: indico_vc_zoom/forms.py:61
|
||||
msgid "Used to moderate the VC Room. Only digits allowed."
|
||||
msgstr "Utilisé pour modérer la salle de VC. Seuls les chiffres sont autorisés."
|
||||
|
||||
#: indico_vc_zoom/forms.py:62 indico_vc_zoom/templates/info_box.html:18
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:32
|
||||
msgid "Room PIN"
|
||||
msgstr "Code confidentiel de la salle"
|
||||
|
||||
#: indico_vc_zoom/forms.py:63
|
||||
msgid ""
|
||||
"Used to protect the access to the VC Room (leave blank for open access). "
|
||||
"Only digits allowed."
|
||||
msgstr "Utilisé pour protéger l'accès à la salle de VC (laisser vide pour un accès ouvert). Seuls les chiffres sont autorisés."
|
||||
|
||||
#: indico_vc_zoom/forms.py:65
|
||||
msgid "Auto mute"
|
||||
msgstr "Coupure automatique des périphériques d'entrée"
|
||||
|
||||
#: indico_vc_zoom/forms.py:66
|
||||
msgid "On"
|
||||
msgstr "Activé"
|
||||
|
||||
#: indico_vc_zoom/forms.py:66
|
||||
msgid "Off"
|
||||
msgstr "Désactivé"
|
||||
|
||||
#: indico_vc_zoom/forms.py:67
|
||||
msgid ""
|
||||
"The VidyoDesktop clients will join the VC room muted by default (audio and "
|
||||
"video)"
|
||||
msgstr "Les clients VidyoDesktop rejoindront la salle Vidyo avec le micro et la caméra coupés par défaut"
|
||||
|
||||
#: indico_vc_zoom/forms.py:82
|
||||
msgid "Unable to find this user in Indico."
|
||||
msgstr "Impossible de trouver cet utilisateur dans Indico."
|
||||
|
||||
#: indico_vc_zoom/forms.py:84
|
||||
msgid "This user does not have a suitable account to use Vidyo."
|
||||
msgstr "Cet utilisateur n'a pas de compte qui lui permet d'utiliser Vidyo."
|
||||
|
||||
#: indico_vc_zoom/plugin.py:49
|
||||
msgid "Vidyo email support"
|
||||
msgstr "Adresse électronique de l'assistance Vidyo"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:50
|
||||
msgid "Username"
|
||||
msgstr "Identifiant"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:50
|
||||
msgid "Indico username for Vidyo"
|
||||
msgstr "Identifiant Indico pour Vidyo"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:51
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:52
|
||||
msgid "Indico password for Vidyo"
|
||||
msgstr "Mot de passe utilisateur pour Vidyo"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:53
|
||||
msgid "Admin API WSDL URL"
|
||||
msgstr "URL WSDL pour l'API admin"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:54
|
||||
msgid "User API WSDL URL"
|
||||
msgstr "URL WSDL pour l'API utilisateur"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:55
|
||||
msgid "Indico tenant prefix"
|
||||
msgstr "Préfixe de tenant pour Indico"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:56
|
||||
msgid "The tenant prefix for Indico rooms created on this server"
|
||||
msgstr "Le préfixe de tenant pour les salles Vidyo créées sur ce serveur Indico"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:57
|
||||
msgid "Public rooms' group name"
|
||||
msgstr "Nom du groupe Vidyo pour les salles Vidyo"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:58
|
||||
msgid "Group name for public videoconference rooms created by Indico"
|
||||
msgstr "Nom du groupe pour les salles publiques de visioconférence créées par Indico"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:59
|
||||
msgid "Authenticators"
|
||||
msgstr "Services d'authentification"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:60
|
||||
msgid "Identity providers to convert Indico users to Vidyo accounts"
|
||||
msgstr "Fournisseurs d'identité pour convertir des utilisateurs Indico en comptes Vidyo"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:61
|
||||
msgid "VC room age threshold"
|
||||
msgstr "Limite d'âge pour les salles Vidyo"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:62
|
||||
msgid ""
|
||||
"Number of days after an Indico event when a videoconference room is "
|
||||
"considered old"
|
||||
msgstr "Nombre de jours à partir de la fin d'un événement dans Indico après lesquels une salle de visioconférence est considérée comme agée"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:64
|
||||
msgid "Max. num. VC rooms before warning"
|
||||
msgstr "Nombre maximum de salles Vidyo avant un message d'alerte"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:65
|
||||
msgid "Maximum number of rooms until a warning is sent to the managers"
|
||||
msgstr "Nombre maximum de salles Vidyo créées sur ce serveur avant qu'un message d'alerte soit envoyé aux administrateurs"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:66
|
||||
msgid "VidyoVoice phone number"
|
||||
msgstr "Numéros de téléphone VidyoVoice"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:67
|
||||
msgid "Link to the list of VidyoVoice phone numbers"
|
||||
msgstr "Lien vers la liste des numéros de téléphones VidyoVoice"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:68
|
||||
msgid "Client Chooser URL"
|
||||
msgstr "URL de sélection du client"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:69
|
||||
msgid ""
|
||||
"URL for client chooser interface. The room key will be passed as a 'url' GET"
|
||||
" query argument"
|
||||
msgstr "L'URL pour l'interface de sélection du client. Le code de la salle sera passé comme argument de requête GET."
|
||||
|
||||
#: indico_vc_zoom/plugin.py:71
|
||||
msgid "Creation email footer"
|
||||
msgstr "Pied de page du courriel de création"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:72
|
||||
msgid "Footer to append to emails sent upon creation of a VC room"
|
||||
msgstr "Pied de page ajouté au courriel envoyé à la création d'une nouvelle salle Vidyo"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:162 indico_vc_zoom/plugin.py:202
|
||||
#: indico_vc_zoom/plugin.py:240 indico_vc_zoom/plugin.py:269
|
||||
msgid "No valid Vidyo account found for this user"
|
||||
msgstr "Pas de compte Vidyo valide pour cet utilisateur"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:198 indico_vc_zoom/plugin.py:263
|
||||
msgid "Room name already in use"
|
||||
msgstr "Ce nom de salle est déjà utilisé"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:213
|
||||
msgid "Could not find newly created room in Vidyo"
|
||||
msgstr "Impossible de trouver la nouvelle salle dans Vidyo"
|
||||
|
||||
#: indico_vc_zoom/plugin.py:232 indico_vc_zoom/plugin.py:259
|
||||
#: indico_vc_zoom/plugin.py:288
|
||||
msgid "This room has been deleted from Vidyo"
|
||||
msgstr "Cette salle a été supprimée de Vidyo"
|
||||
|
||||
#: indico_vc_zoom/templates/buttons.html:7
|
||||
#, python-format
|
||||
msgid "You will be the owner of this Vidyo room, replacing %(name)s."
|
||||
msgstr "Vous deviendrez le responsable de cette salle Vidyo, à la place de %(name)s."
|
||||
|
||||
#: indico_vc_zoom/templates/buttons.html:9
|
||||
msgid "Make me owner"
|
||||
msgstr "Nommez moi responsable"
|
||||
|
||||
#: indico_vc_zoom/templates/buttons.html:19
|
||||
msgid "Join"
|
||||
msgstr "Rejoindre"
|
||||
|
||||
#: indico_vc_zoom/templates/info_box.html:5
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: indico_vc_zoom/templates/info_box.html:10
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:8
|
||||
msgid "Extension"
|
||||
msgstr "Extension"
|
||||
|
||||
#: indico_vc_zoom/templates/info_box.html:22
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:45
|
||||
msgid "Auto-join URL"
|
||||
msgstr "URL pour connexion à la salle"
|
||||
|
||||
#: indico_vc_zoom/templates/info_box.html:29
|
||||
msgid "Useful links"
|
||||
msgstr "Liens utiles"
|
||||
|
||||
#: indico_vc_zoom/templates/info_box.html:33
|
||||
msgid "Phone numbers"
|
||||
msgstr "Numéros de téléphone"
|
||||
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:18
|
||||
msgid "Linked to"
|
||||
msgstr "attachée à"
|
||||
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:24
|
||||
msgid "the whole event"
|
||||
msgstr "l'événement entier"
|
||||
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:26
|
||||
msgid "Contribution"
|
||||
msgstr "La contribution"
|
||||
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:28
|
||||
msgid "Session"
|
||||
msgstr "La session"
|
||||
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:49
|
||||
msgid "Created on"
|
||||
msgstr "Créée le "
|
||||
|
||||
#: indico_vc_zoom/templates/manage_event_info_box.html:52
|
||||
msgid "Modified on"
|
||||
msgstr "Modifiée le "
|
||||
23
vc_zoom/indico_vc_zoom/util.py
Normal file
23
vc_zoom/indico_vc_zoom/util.py
Normal file
@ -0,0 +1,23 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from indico.core.db import db
|
||||
from indico.modules.users.models.emails import UserEmail
|
||||
from indico.modules.users.models.users import User
|
||||
|
||||
|
||||
def find_enterprise_email(user):
|
||||
"""Find a user's first e-mail address which can be used by the Zoom API.
|
||||
|
||||
:param user: the `User` in question
|
||||
:return: the e-mail address if it exists, otherwise `None`
|
||||
"""
|
||||
from indico_vc_zoom.plugin import ZoomPlugin
|
||||
providers = [auth.strip() for auth in ZoomPlugin.settings.get('email_domains').split(',')]
|
||||
result = UserEmail.query.filter(
|
||||
UserEmail.user == user,
|
||||
~User.is_blocked,
|
||||
~User.is_deleted,
|
||||
db.or_(UserEmail.email.ilike("%%@{}".format(provider)) for provider in providers)
|
||||
).join(User).first()
|
||||
return result.email if result else None
|
||||
7
vc_zoom/pytest.ini
Normal file
7
vc_zoom/pytest.ini
Normal file
@ -0,0 +1,7 @@
|
||||
[pytest]
|
||||
; more verbose summary (include skip/fail/error/warning), coverage
|
||||
addopts = -rsfEw --cov . --cov-report html --no-cov-on-fail
|
||||
; only check for tests in suffixed files
|
||||
python_files = *_test.py
|
||||
; we need the livesync plugin to be loaded
|
||||
indico_plugins = vc_zoom
|
||||
2
vc_zoom/setup.cfg
Normal file
2
vc_zoom/setup.cfg
Normal file
@ -0,0 +1,2 @@
|
||||
[pydocstyle]
|
||||
ignore = D100,D101,D102,D103,D104,D105,D107,D203,D213
|
||||
28
vc_zoom/setup.py
Normal file
28
vc_zoom/setup.py
Normal file
@ -0,0 +1,28 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
|
||||
setup(
|
||||
name='indico-plugin-vc-zoom',
|
||||
version='0.3-dev',
|
||||
description='Zoom video-conferencing plugin for Indico',
|
||||
url='',
|
||||
license='MIT',
|
||||
author='Giovanni Mariano - ENEA',
|
||||
author_email='giovanni.mariano@enea.it',
|
||||
packages=find_packages(),
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'indico>=2',
|
||||
'PyJWT'
|
||||
],
|
||||
classifiers=[
|
||||
'Environment :: Plugins',
|
||||
'Environment :: Web Environment',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python :: 2.7'
|
||||
],
|
||||
entry_points={'indico.plugins': {'vc_zoom = indico_vc_zoom.plugin:ZoomPlugin'}}
|
||||
)
|
||||
22
vc_zoom/tests/task_test.py
Normal file
22
vc_zoom/tests/task_test.py
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from pytz import utc
|
||||
|
||||
from indico.modules.vc.models.vc_rooms import VCRoom, VCRoomEventAssociation, VCRoomStatus
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def create_dummy_room(db, dummy_user):
|
||||
"""Returns a callable which lets you create dummy Zoom room occurrences"""
|
||||
pass
|
||||
|
||||
|
||||
def test_room_cleanup(create_event, create_dummy_room, freeze_time, db):
|
||||
"""Test that 'old' Zoom rooms are correctly detected"""
|
||||
freeze_time(datetime(2015, 2, 1))
|
||||
|
||||
pass
|
||||
|
||||
assert {r.id for r in find_old_zoom_rooms(180)} == {2, 3, 5}
|
||||
5
vc_zoom/webpack-bundles.json
Normal file
5
vc_zoom/webpack-bundles.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"entry": {
|
||||
"main": "./index.js"
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user