mirror of https://github.com/sualko/cloud_bbb
feat: add access policy
public, require password for guests, moderator approval for guests, only Nextcloud users fix #10 fix #24pull/63/head
parent
07f679aa09
commit
70b06aa98c
|
@ -1,3 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
OCP\Util::addScript ( 'bbb', 'filelist');
|
OCP\Util::addScript ( 'bbb', 'filelist');
|
||||||
|
|
||||||
|
$apiUrl = \OC::$server->getConfig()->getAppValue('bbb', 'api.url');
|
||||||
|
$parsedApiUrl = @parse_url($apiUrl);
|
||||||
|
|
||||||
|
if ($parsedApiUrl !== false) {
|
||||||
|
$manager = \OC::$server->getContentSecurityPolicyManager();
|
||||||
|
$policy = new \OCP\AppFramework\Http\EmptyContentSecurityPolicy();
|
||||||
|
|
||||||
|
$policy->addAllowedFormActionDomain(($parsedApiUrl['scheme'] ?: 'https') . '://' . $parsedApiUrl['host']);
|
||||||
|
|
||||||
|
$manager->addDefaultPolicy($policy);
|
||||||
|
}
|
|
@ -58,6 +58,7 @@ class API
|
||||||
$joinMeetingParams->setCreationTime($creationTime);
|
$joinMeetingParams->setCreationTime($creationTime);
|
||||||
$joinMeetingParams->setJoinViaHtml5(true);
|
$joinMeetingParams->setJoinViaHtml5(true);
|
||||||
$joinMeetingParams->setRedirect(true);
|
$joinMeetingParams->setRedirect(true);
|
||||||
|
$joinMeetingParams->setGuest($uid === null);
|
||||||
|
|
||||||
if ($uid) {
|
if ($uid) {
|
||||||
$joinMeetingParams->setUserId($uid);
|
$joinMeetingParams->setUserId($uid);
|
||||||
|
@ -114,6 +115,10 @@ class API
|
||||||
$createMeetingParams->addPresentation($presentation->getUrl(), null, $presentation->getFilename());
|
$createMeetingParams->addPresentation($presentation->getUrl(), null, $presentation->getFilename());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($room->access === Room::ACCESS_WAITING_ROOM) {
|
||||||
|
$createMeetingParams->setGuestPolicyAskModerator();
|
||||||
|
}
|
||||||
|
|
||||||
return $createMeetingParams;
|
return $createMeetingParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ use OCA\BigBlueButton\NotFoundException;
|
||||||
use OCP\AppFramework\Http\RedirectResponse;
|
use OCP\AppFramework\Http\RedirectResponse;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
use OCP\ISession;
|
use OCP\ISession;
|
||||||
|
use OCP\IURLGenerator;
|
||||||
use OCP\IUserSession;
|
use OCP\IUserSession;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCA\BigBlueButton\Service\RoomService;
|
use OCA\BigBlueButton\Service\RoomService;
|
||||||
|
@ -25,6 +26,9 @@ class JoinController extends Controller
|
||||||
/** @var RoomService */
|
/** @var RoomService */
|
||||||
private $service;
|
private $service;
|
||||||
|
|
||||||
|
/** @var IURLGenerator */
|
||||||
|
private $urlGenerator;
|
||||||
|
|
||||||
/** @var IUserSession */
|
/** @var IUserSession */
|
||||||
private $userSession;
|
private $userSession;
|
||||||
|
|
||||||
|
@ -39,6 +43,7 @@ class JoinController extends Controller
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
ISession $session,
|
ISession $session,
|
||||||
RoomService $service,
|
RoomService $service,
|
||||||
|
IURLGenerator $urlGenerator,
|
||||||
IUserSession $userSession,
|
IUserSession $userSession,
|
||||||
IConfig $config,
|
IConfig $config,
|
||||||
API $api
|
API $api
|
||||||
|
@ -46,6 +51,7 @@ class JoinController extends Controller
|
||||||
parent::__construct($appName, $request, $session);
|
parent::__construct($appName, $request, $session);
|
||||||
|
|
||||||
$this->service = $service;
|
$this->service = $service;
|
||||||
|
$this->urlGenerator = $urlGenerator;
|
||||||
$this->userSession = $userSession;
|
$this->userSession = $userSession;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->api = $api;
|
$this->api = $api;
|
||||||
|
@ -68,7 +74,7 @@ class JoinController extends Controller
|
||||||
* @PublicPage
|
* @PublicPage
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function index($displayname, $u = '', $filename = '')
|
public function index($displayname, $u = '', $filename = '', $password = '')
|
||||||
{
|
{
|
||||||
$room = $this->getRoom();
|
$room = $this->getRoom();
|
||||||
|
|
||||||
|
@ -87,10 +93,21 @@ class JoinController extends Controller
|
||||||
if ($userId === $room->userId) {
|
if ($userId === $room->userId) {
|
||||||
$presentation = new Presentation($u, $filename);
|
$presentation = new Presentation($u, $filename);
|
||||||
}
|
}
|
||||||
} elseif (empty($displayname) || strlen($displayname) < 3) {
|
} elseif ($room->access === Room::ACCESS_INTERNAL) {
|
||||||
$response = new TemplateResponse($this->appName, 'publicdisplayname', [
|
return new RedirectResponse(
|
||||||
|
$this->urlGenerator->linkToRoute('core.login.showLoginForm', [
|
||||||
|
'redirect_url' => $this->urlGenerator->linkToRoute(
|
||||||
|
'bbb.join.index',
|
||||||
|
['token' => $this->token]
|
||||||
|
),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
} elseif (empty($displayname) || strlen($displayname) < 3 || ($room->access === Room::ACCESS_PASSWORD && $password !== $room->password)) {
|
||||||
|
$response = new TemplateResponse($this->appName, 'join', [
|
||||||
'room' => $room->name,
|
'room' => $room->name,
|
||||||
'wrongdisplayname' => !empty($displayname) && strlen($displayname) < 3
|
'wrongdisplayname' => !empty($displayname) && strlen($displayname) < 3,
|
||||||
|
'passwordRequired' => $room->access === Room::ACCESS_PASSWORD,
|
||||||
|
'wrongPassword' => $password !== $room->password && $password !== '',
|
||||||
], 'guest');
|
], 'guest');
|
||||||
|
|
||||||
$this->addFormActionDomain($response);
|
$this->addFormActionDomain($response);
|
||||||
|
@ -116,7 +133,7 @@ class JoinController extends Controller
|
||||||
$response->getContentSecurityPolicy()->addAllowedFormActionDomain(($parsedApiUrl['scheme'] ?: 'https') . '://' . $parsedApiUrl['host']);
|
$response->getContentSecurityPolicy()->addAllowedFormActionDomain(($parsedApiUrl['scheme'] ?: 'https') . '://' . $parsedApiUrl['host']);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getRoom(): Room
|
private function getRoom(): ?Room
|
||||||
{
|
{
|
||||||
if ($this->room === null) {
|
if ($this->room === null) {
|
||||||
$this->room = $this->service->findByUid($this->token);
|
$this->room = $this->service->findByUid($this->token);
|
||||||
|
|
|
@ -81,10 +81,11 @@ class RoomApiController extends ApiController
|
||||||
string $name,
|
string $name,
|
||||||
string $welcome,
|
string $welcome,
|
||||||
int $maxParticipants,
|
int $maxParticipants,
|
||||||
bool $record
|
bool $record,
|
||||||
|
string $access
|
||||||
): DataResponse {
|
): DataResponse {
|
||||||
return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record) {
|
return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record, $access) {
|
||||||
return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $this->userId);
|
return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $access, $this->userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,10 +73,11 @@ class RoomController extends Controller
|
||||||
string $name,
|
string $name,
|
||||||
string $welcome,
|
string $welcome,
|
||||||
int $maxParticipants,
|
int $maxParticipants,
|
||||||
bool $record
|
bool $record,
|
||||||
|
string $access
|
||||||
): DataResponse {
|
): DataResponse {
|
||||||
return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record) {
|
return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record, $access) {
|
||||||
return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $this->userId);
|
return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $access, $this->userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,12 @@ use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
class Room extends Entity implements JsonSerializable
|
class Room extends Entity implements JsonSerializable
|
||||||
{
|
{
|
||||||
|
const ACCESS_PUBLIC = 'public';
|
||||||
|
const ACCESS_PASSWORD = 'password';
|
||||||
|
const ACCESS_WAITING_ROOM = 'waiting_room';
|
||||||
|
const ACCESS_INTERNAL = 'internal';
|
||||||
|
const ACCESS_INTERNAL_RESTRICTED = 'internal_restricted';
|
||||||
|
|
||||||
public $uid;
|
public $uid;
|
||||||
public $name;
|
public $name;
|
||||||
public $attendeePassword;
|
public $attendeePassword;
|
||||||
|
@ -15,6 +21,8 @@ class Room extends Entity implements JsonSerializable
|
||||||
public $maxParticipants;
|
public $maxParticipants;
|
||||||
public $record;
|
public $record;
|
||||||
public $userId;
|
public $userId;
|
||||||
|
public $access;
|
||||||
|
public $password;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
@ -31,6 +39,8 @@ class Room extends Entity implements JsonSerializable
|
||||||
'welcome' => $this->welcome,
|
'welcome' => $this->welcome,
|
||||||
'maxParticipants' => (int) $this->maxParticipants,
|
'maxParticipants' => (int) $this->maxParticipants,
|
||||||
'record' => boolval($this->record),
|
'record' => boolval($this->record),
|
||||||
|
'access' => $this->access,
|
||||||
|
'password' => $this->password,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\BigBlueButton\Migration;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use OCA\BigBlueButton\Db\Room;
|
||||||
|
use OCP\DB\ISchemaWrapper;
|
||||||
|
use OCP\Migration\IOutput;
|
||||||
|
use OCP\Migration\SimpleMigrationStep;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated migration step: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
class Version000000Date20200604130935 extends SimpleMigrationStep
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param IOutput $output
|
||||||
|
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||||
|
* @param array $options
|
||||||
|
* @return null|ISchemaWrapper
|
||||||
|
*/
|
||||||
|
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options)
|
||||||
|
{
|
||||||
|
/** @var ISchemaWrapper $schema */
|
||||||
|
$schema = $schemaClosure();
|
||||||
|
|
||||||
|
if ($schema->hasTable('bbb_rooms')) {
|
||||||
|
$table = $schema->getTable('bbb_rooms');
|
||||||
|
|
||||||
|
if (!$table->hasColumn('access')) {
|
||||||
|
$table->addColumn('access', 'string', [
|
||||||
|
'notnull' => true,
|
||||||
|
'default' => Room::ACCESS_PUBLIC,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('password')) {
|
||||||
|
$table->addColumn('password', 'string', [
|
||||||
|
'length' => 64,
|
||||||
|
'notnull' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,15 +75,20 @@ class RoomService
|
||||||
return $this->mapper->insert($room);
|
return $this->mapper->insert($room);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update($id, $name, $welcome, $maxParticipants, $record, $userId)
|
public function update($id, $name, $welcome, $maxParticipants, $record, $access, $userId)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$room = $this->mapper->find($id, $userId);
|
$room = $this->mapper->find($id, $userId);
|
||||||
|
|
||||||
|
if ($room->access !== $access) {
|
||||||
|
$room->setPassword($access === Room::ACCESS_PASSWORD ? $this->humanReadableRandom(8) : null);
|
||||||
|
}
|
||||||
|
|
||||||
$room->setName($name);
|
$room->setName($name);
|
||||||
$room->setWelcome($welcome);
|
$room->setWelcome($welcome);
|
||||||
$room->setMaxParticipants($maxParticipants);
|
$room->setMaxParticipants($maxParticipants);
|
||||||
$room->setRecord($record);
|
$room->setRecord($record);
|
||||||
|
$room->setAccess($access);
|
||||||
$room->setUserId($userId);
|
$room->setUserId($userId);
|
||||||
|
|
||||||
return $this->mapper->update($room);
|
return $this->mapper->update($room);
|
||||||
|
@ -102,4 +107,9 @@ class RoomService
|
||||||
$this->handleException($e);
|
$this->handleException($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function humanReadableRandom($length)
|
||||||
|
{
|
||||||
|
return \OC::$server->getSecureRandom()->generate($length, \OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
/** @var $_ array */
|
||||||
|
/** @var $l \OCP\IL10N */
|
||||||
|
style('core', 'guest');
|
||||||
|
script('bbb', 'join');
|
||||||
|
?>
|
||||||
|
<form method="get" action="?">
|
||||||
|
<fieldset class="warning bbb">
|
||||||
|
<h2><?php p($_['room']) ?></h2>
|
||||||
|
<?php if (!isset($_['wrongdisplayname']) || !$_['wrongdisplayname']): ?>
|
||||||
|
<p><?php p($l->t('Please enter your name!')); ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_['wrongdisplayname']) && $_['wrongdisplayname']): ?>
|
||||||
|
<div class="warning"><?php p($l->t('The name must be at least 3 characters long.')); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_['wrongPassword']) && $_['wrongPassword']): ?>
|
||||||
|
<div class="warning"><?php p($l->t('You have to provide the correct password to join the meeting.')); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="bbb-container">
|
||||||
|
<label for="displayname" class="infield"><?php p($l->t('Display name')); ?></label>
|
||||||
|
<input type="text" name="displayname" id="displayname" class="bbb-input"
|
||||||
|
placeholder="<?php p($l->t('Display name')); ?>" value=""
|
||||||
|
required minlength="3" autofocus />
|
||||||
|
<?php if (isset($_['passwordRequired']) && $_['passwordRequired']): ?>
|
||||||
|
<label for="password" class="infield"><?php p($l->t('Password')); ?></label>
|
||||||
|
<input type="text" name="password" id="password" class="bbb-input"
|
||||||
|
placeholder="<?php p($l->t('Password')); ?>" value=""
|
||||||
|
required minlength="8" />
|
||||||
|
<button class="primary"><?php p($l->t('Join')); ?>
|
||||||
|
<div class="submit-icon icon-confirm-white"></div></button>
|
||||||
|
<?php else: ?>
|
||||||
|
<input type="submit" id="displayname-submit"
|
||||||
|
class="svg icon-confirm input-button-inline" value="" />
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
|
@ -1,25 +0,0 @@
|
||||||
<?php
|
|
||||||
/** @var $_ array */
|
|
||||||
/** @var $l \OCP\IL10N */
|
|
||||||
style('core', 'guest');
|
|
||||||
style('core', 'publicshareauth');
|
|
||||||
?>
|
|
||||||
<form method="get" action="?">
|
|
||||||
<fieldset class="warning">
|
|
||||||
<h2><?php p($_['room']) ?></h2>
|
|
||||||
<?php if (!isset($_['wrongdisplayname']) || !$_['wrongdisplayname']): ?>
|
|
||||||
<p><?php p($l->t('Please enter your name!')); ?></p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_['wrongdisplayname']) && $_['wrongdisplayname']): ?>
|
|
||||||
<div class="warning"><?php p($l->t('The name must be at least 3 characters long.')); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<p>
|
|
||||||
<label for="password" class="infield"><?php p($l->t('Display name')); ?></label>
|
|
||||||
<input type="displayname" name="displayname" id="password"
|
|
||||||
placeholder="<?php p($l->t('Display name')); ?>" value=""
|
|
||||||
required minlength="3" autofocus />
|
|
||||||
<input type="submit" id="displayname-submit"
|
|
||||||
class="svg icon-confirm input-button-inline" value="" />
|
|
||||||
</p>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
|
@ -1,5 +1,13 @@
|
||||||
import axios from '@nextcloud/axios';
|
import axios from '@nextcloud/axios';
|
||||||
|
|
||||||
|
export enum Access {
|
||||||
|
Public = 'public',
|
||||||
|
Password = 'password',
|
||||||
|
WaitingRoom = 'waiting_room',
|
||||||
|
Internal = 'internal',
|
||||||
|
InternalRestricted = 'internal_restricted',
|
||||||
|
}
|
||||||
|
|
||||||
export interface Room {
|
export interface Room {
|
||||||
id: number;
|
id: number;
|
||||||
uid: string;
|
uid: string;
|
||||||
|
@ -7,6 +15,8 @@ export interface Room {
|
||||||
welcome: string;
|
welcome: string;
|
||||||
maxParticipants: number;
|
maxParticipants: number;
|
||||||
record: boolean;
|
record: boolean;
|
||||||
|
access: Access;
|
||||||
|
password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Recording = {
|
export type Recording = {
|
||||||
|
|
|
@ -168,8 +168,14 @@
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
margin: 2em 0;
|
margin: 2em 0;
|
||||||
|
|
||||||
input:not([type="checkbox"]) {
|
input:not([type="checkbox"]),
|
||||||
|
select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
[readonly] {
|
||||||
|
background-color: #f1f1f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
em {
|
em {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import Dialog from './Dialog';
|
import Dialog from './Dialog';
|
||||||
import { Room } from './Api';
|
import { Room, Access } from './Api';
|
||||||
import { SubmitInput } from './SubmitInput';
|
import { SubmitInput } from './SubmitInput';
|
||||||
|
|
||||||
const descriptions: { [key: string]: string } = {
|
const descriptions: { [key: string]: string } = {
|
||||||
|
@ -8,6 +8,7 @@ const descriptions: { [key: string]: string } = {
|
||||||
welcome: t('bbb', 'This message is shown to all users in the chat area after they joined.'),
|
welcome: t('bbb', 'This message is shown to all users in the chat area after they joined.'),
|
||||||
maxParticipants: t('bbb', 'Sets a limit on the number of participants for this room. Zero means there is no limit.'),
|
maxParticipants: t('bbb', 'Sets a limit on the number of participants for this room. Zero means there is no limit.'),
|
||||||
recording: t('bbb', 'If enabled, the moderator is able to start the recording.'),
|
recording: t('bbb', 'If enabled, the moderator is able to start the recording.'),
|
||||||
|
access: t('bbb', 'Public: Everyone knowing the link is able to join. Password: Guests have to provide a password. Waiting room: A moderator has to accept every guest before they can join. Internal: Only Nextcloud users can join.'),
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -18,11 +19,11 @@ type Props = {
|
||||||
const EditRoomDialog: React.FC<Props> = ({ room, updateProperty }) => {
|
const EditRoomDialog: React.FC<Props> = ({ room, updateProperty }) => {
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
function formElement(label: string, field: string, type: 'text' | 'number' = 'text') {
|
function inputElement(label: string, field: string, type: 'text' | 'number' = 'text') {
|
||||||
return (
|
return (
|
||||||
<div className="bbb-form-element">
|
<div className="bbb-form-element">
|
||||||
<label htmlFor={`bbb-${field}`}>
|
<label htmlFor={`bbb-${field}`}>
|
||||||
<h3>{t('bbb', label)}</h3>
|
<h3>{label}</h3>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<SubmitInput initialValue={room[field]} type={type} name={field} onSubmitValue={value => updateProperty(field, value)} />
|
<SubmitInput initialValue={room[field]} type={type} name={field} onSubmitValue={value => updateProperty(field, value)} />
|
||||||
|
@ -31,6 +32,26 @@ const EditRoomDialog: React.FC<Props> = ({ room, updateProperty }) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectElement(label: string, field: string, value: string, options: {[key: string]: string}, onChange: (value: string) => void) {
|
||||||
|
return (
|
||||||
|
<div className="bbb-form-element">
|
||||||
|
<label htmlFor={`bbb-${field}`}>
|
||||||
|
<h3>{label}</h3>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<select name={field} value={value} onChange={(event) => onChange(event.target.value)}>
|
||||||
|
{Object.keys(options).map(key => {
|
||||||
|
const label = options[key];
|
||||||
|
|
||||||
|
return <option key={key} value={key}>{label}</option>;
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
{(value === Access.Password && room.password) && <input type="text" readOnly={true} value={room.password} />}
|
||||||
|
{descriptions[field] && <em>{descriptions[field]}</em>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a className="icon icon-edit icon-visible"
|
<a className="icon icon-edit icon-visible"
|
||||||
|
@ -38,9 +59,20 @@ const EditRoomDialog: React.FC<Props> = ({ room, updateProperty }) => {
|
||||||
title={t('bbb', 'Edit')} />
|
title={t('bbb', 'Edit')} />
|
||||||
|
|
||||||
<Dialog open={open} onClose={() => setOpen(false)} title={t('bbb', 'Edit "{room}"', { room: room.name })}>
|
<Dialog open={open} onClose={() => setOpen(false)} title={t('bbb', 'Edit "{room}"', { room: room.name })}>
|
||||||
{formElement('Name', 'name')}
|
{inputElement(t('bbb', 'Name'), 'name')}
|
||||||
{formElement('Welcome', 'welcome')}
|
{inputElement(t('bbb', 'Welcome'), 'welcome')}
|
||||||
{formElement('Participant limit', 'maxParticipants', 'number')}
|
{inputElement(t('bbb', 'Participant limit'), 'maxParticipants', 'number')}
|
||||||
|
|
||||||
|
{selectElement(t('bbb', 'Access'), 'access', room.access, {
|
||||||
|
[Access.Public]: t('bbb', 'Public'),
|
||||||
|
[Access.Password]: t('bbb', 'Internal + Password protection for guests'),
|
||||||
|
[Access.WaitingRoom]: t('bbb', 'Internal + Waiting room for guests'),
|
||||||
|
[Access.Internal]: t('bbb', 'Internal'),
|
||||||
|
// [Access.InternalRestricted]: t('bbb', 'Restricted'),
|
||||||
|
}, (value) => {
|
||||||
|
console.log('access', value);
|
||||||
|
updateProperty('access', value);
|
||||||
|
})}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
.bbb {
|
||||||
|
|
||||||
|
#displayname,
|
||||||
|
#password {
|
||||||
|
margin: 5px 0;
|
||||||
|
padding-right: 45px;
|
||||||
|
height: 45px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-icon {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bbb-container {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='submit'].icon-confirm {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: -5px;
|
||||||
|
width: 45px !important;
|
||||||
|
height: 45px;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
import './join.scss';
|
|
@ -11,6 +11,9 @@ module.exports = {
|
||||||
manager: [
|
manager: [
|
||||||
path.join(__dirname, 'ts', 'Manager', 'index.tsx'),
|
path.join(__dirname, 'ts', 'Manager', 'index.tsx'),
|
||||||
],
|
],
|
||||||
|
join: [
|
||||||
|
path.join(__dirname, 'ts', 'join.ts'),
|
||||||
|
]
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, './js'),
|
path: path.resolve(__dirname, './js'),
|
||||||
|
|
Loading…
Reference in New Issue