From b437d7c33d4a715d6c57dc29d25938a1b224e391 Mon Sep 17 00:00:00 2001 From: sualko Date: Sun, 17 May 2020 11:09:16 +0200 Subject: [PATCH] feat: manage recordings - list recordings - delete recording - store url to player as url file fix #19 --- appinfo/routes.php | 4 +- composer.lock | 120 +++++++++-------- lib/BigBlueButton/API.php | 68 ++++++++++ lib/Controller/ServerController.php | 79 +++++++++++ ts/Manager/Api.ts | 33 +++++ ts/Manager/App.scss | 21 +++ ts/Manager/App.tsx | 3 + ts/Manager/Nextcloud.d.ts | 6 + ts/Manager/RecordingRow.tsx | 46 +++++++ ts/Manager/RoomRow.tsx | 195 ++++++++++++++++++++++------ 10 files changed, 478 insertions(+), 97 deletions(-) create mode 100644 lib/Controller/ServerController.php create mode 100644 ts/Manager/RecordingRow.tsx diff --git a/appinfo/routes.php b/appinfo/routes.php index f22716d..6cd6bf5 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -2,9 +2,11 @@ return [ 'resources' => [ 'room' => ['url' => '/rooms'], - 'room_api' => ['url' => '/api/0.1/rooms'] + 'room_api' => ['url' => '/api/0.1/rooms'], ], 'routes' => [ + ['name' => 'server#records', 'url' => '/server/{roomUid}/records', 'verb' => 'GET'], + ['name' => 'server#delete_record', 'url' => '/server/record/{recordId}', 'verb' => 'DELETE'], ['name' => 'join#index', 'url' => '/b/{token}', 'verb' => 'GET'], ['name' => 'room_api#preflighted_cors', 'url' => '/api/0.1/{path}', 'verb' => 'OPTIONS', 'requirements' => ['path' => '.+']] diff --git a/composer.lock b/composer.lock index bdde005..a42d51a 100644 --- a/composer.lock +++ b/composer.lock @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/sualko/bigbluebutton-api-php.git", - "reference": "75230993ab7714ef27b7177486dd4c99146b3bf7" + "reference": "3b8da2e1b9469deebe2713bb679e9c88e258ec9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sualko/bigbluebutton-api-php/zipball/75230993ab7714ef27b7177486dd4c99146b3bf7", - "reference": "75230993ab7714ef27b7177486dd4c99146b3bf7", + "url": "https://api.github.com/repos/sualko/bigbluebutton-api-php/zipball/3b8da2e1b9469deebe2713bb679e9c88e258ec9b", + "reference": "3b8da2e1b9469deebe2713bb679e9c88e258ec9b", "shasum": "" }, "require": { @@ -62,7 +62,7 @@ "support": { "source": "https://github.com/sualko/bigbluebutton-api-php/tree/fork" }, - "time": "2020-04-27T14:32:50+00:00" + "time": "2020-05-17T08:37:02+00:00" } ], "packages-dev": [ @@ -1964,7 +1964,7 @@ }, { "name": "symfony/console", - "version": "v5.0.7", + "version": "v5.0.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", @@ -2054,7 +2054,7 @@ }, { "name": "symfony/event-dispatcher", - "version": "v5.0.7", + "version": "v5.0.8", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -2196,16 +2196,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.0.7", + "version": "v5.0.8", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "ca3b87dd09fff9b771731637f5379965fbfab420" + "reference": "7cd0dafc4353a0f62e307df90b48466379c8cc91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/ca3b87dd09fff9b771731637f5379965fbfab420", - "reference": "ca3b87dd09fff9b771731637f5379965fbfab420", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7cd0dafc4353a0f62e307df90b48466379c8cc91", + "reference": "7cd0dafc4353a0f62e307df90b48466379c8cc91", "shasum": "" }, "require": { @@ -2256,11 +2256,11 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:56:45+00:00" + "time": "2020-04-12T14:40:17+00:00" }, { "name": "symfony/finder", - "version": "v5.0.7", + "version": "v5.0.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -2323,16 +2323,16 @@ }, { "name": "symfony/options-resolver", - "version": "v5.0.7", + "version": "v5.0.8", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "09dccfffd24b311df7f184aa80ee7b61ad61ed8d" + "reference": "3707e3caeff2b797c0bfaadd5eba723dd44e6bf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/09dccfffd24b311df7f184aa80ee7b61ad61ed8d", - "reference": "09dccfffd24b311df7f184aa80ee7b61ad61ed8d", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/3707e3caeff2b797c0bfaadd5eba723dd44e6bf1", + "reference": "3707e3caeff2b797c0bfaadd5eba723dd44e6bf1", "shasum": "" }, "require": { @@ -2387,20 +2387,20 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:56:45+00:00" + "time": "2020-04-06T10:40:56+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14" + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14", - "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", "shasum": "" }, "require": { @@ -2412,7 +2412,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -2459,20 +2459,20 @@ "type": "tidelift" } ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-12T16:14:59+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c", + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c", "shasum": "" }, "require": { @@ -2484,7 +2484,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -2532,20 +2532,20 @@ "type": "tidelift" } ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "2a18e37a489803559284416df58c71ccebe50bf0" + "reference": "82225c2d7d23d7e70515496d249c0152679b468e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/2a18e37a489803559284416df58c71ccebe50bf0", - "reference": "2a18e37a489803559284416df58c71ccebe50bf0", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/82225c2d7d23d7e70515496d249c0152679b468e", + "reference": "82225c2d7d23d7e70515496d249c0152679b468e", "shasum": "" }, "require": { @@ -2555,7 +2555,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -2591,20 +2591,34 @@ "portable", "shim" ], - "time": "2020-02-27T09:26:54+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "37b0976c78b94856543260ce09b460a7bc852747" + "reference": "f048e612a3905f34931127360bdd2def19a5e582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", - "reference": "37b0976c78b94856543260ce09b460a7bc852747", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582", + "reference": "f048e612a3905f34931127360bdd2def19a5e582", "shasum": "" }, "require": { @@ -2613,7 +2627,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -2660,20 +2674,20 @@ "type": "tidelift" } ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7" + "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a760d8964ff79ab9bf057613a5808284ec852ccc", + "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc", "shasum": "" }, "require": { @@ -2682,7 +2696,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -2732,20 +2746,20 @@ "type": "tidelift" } ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/process", - "version": "v5.0.7", + "version": "v5.0.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e" + "reference": "3179f68dff5bad14d38c4114a1dab98030801fd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e", - "reference": "c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e", + "url": "https://api.github.com/repos/symfony/process/zipball/3179f68dff5bad14d38c4114a1dab98030801fd7", + "reference": "3179f68dff5bad14d38c4114a1dab98030801fd7", "shasum": "" }, "require": { @@ -2795,7 +2809,7 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:56:45+00:00" + "time": "2020-04-15T15:59:10+00:00" }, { "name": "symfony/service-contracts", @@ -2857,7 +2871,7 @@ }, { "name": "symfony/stopwatch", - "version": "v5.0.7", + "version": "v5.0.8", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", diff --git a/lib/BigBlueButton/API.php b/lib/BigBlueButton/API.php index 2796141..d9c26f6 100644 --- a/lib/BigBlueButton/API.php +++ b/lib/BigBlueButton/API.php @@ -5,6 +5,9 @@ namespace OCA\BigBlueButton\BigBlueButton; use BigBlueButton\BigBlueButton; use BigBlueButton\Parameters\CreateMeetingParameters; use BigBlueButton\Parameters\JoinMeetingParameters; +use BigBlueButton\Parameters\GetRecordingsParameters; +use BigBlueButton\Core\Record; +use BigBlueButton\Parameters\DeleteRecordingsParameters; use OCA\BigBlueButton\Db\Room; use OCP\IConfig; use OCP\IURLGenerator; @@ -112,4 +115,69 @@ class API return $createMeetingParams; } + + public function getRecording(string $recordId) + { + $recordingParams = new GetRecordingsParameters(); + $recordingParams->setRecordId($recordId); + $recordingParams->setState('any'); + + $response = $this->getServer()->getRecordings($recordingParams); + + if (!$response->success()) { + throw new \Exception('Could not process get recording request'); + } + + $records = $response->getRecords(); + + if (count($records) === 0) { + throw new \Exception('Found no record with given id'); + } + + return $this->recordToArray($records[0]); + } + + public function getRecordings(Room $room) + { + $recordingParams = new GetRecordingsParameters(); + $recordingParams->setMeetingId($room->uid); + $recordingParams->setState('processing,processed,published,unpublished'); + + $response = $this->getServer()->getRecordings($recordingParams); + + if (!$response->success()) { + throw new \Exception('Could not process get recordings request'); + } + + $records = $response->getRecords(); + + return array_map(function ($record) { + return $this->recordToArray($record); + }, $records); + } + + public function deleteRecording(string $recordingId): bool + { + $deleteParams = new DeleteRecordingsParameters($recordingId); + + $response = $this->getServer()->deleteRecordings($deleteParams); + + return $response->isDeleted(); + } + + private function recordToArray(Record $record) + { + return [ + 'id' => $record->getRecordId(), + 'name' => $record->getName(), + 'published' => $record->isPublished(), + 'state' => $record->getState(), + 'startTime' => $record->getStartTime(), + 'participants' => $record->getParticipantCount(), + 'type' => $record->getPlaybackType(), + 'length' => $record->getPlaybackLength(), + 'url' => $record->getPlaybackUrl(), + 'metas' => $record->getMetas(), + ]; + } } diff --git a/lib/Controller/ServerController.php b/lib/Controller/ServerController.php new file mode 100644 index 0000000..b2955b2 --- /dev/null +++ b/lib/Controller/ServerController.php @@ -0,0 +1,79 @@ +service = $service; + $this->server = $server; + $this->userId = $UserId; + } + + /** + * @NoAdminRequired + */ + public function records(string $roomUid): DataResponse + { + $room = $this->service->findByUid($roomUid); + + if ($room === null) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + if ($room->userId !== $this->userId) { + return new DataResponse([], Http::STATUS_FORBIDDEN); + } + + $recordings = $this->server->getRecordings($room); + + return new DataResponse($recordings); + } + + /** + * @NoAdminRequired + */ + public function deleteRecord(string $recordId): DataResponse + { + $record = $this->server->getRecording($recordId); + + $room = $this->service->findByUid($record['metas']['meetingId']); + + if ($room === null) { + return new DataResponse(false, Http::STATUS_NOT_FOUND); + } + + if ($room->userId !== $this->userId) { + return new DataResponse(false, Http::STATUS_FORBIDDEN); + } + + $success = $this->server->deleteRecording($recordId); + + return new DataResponse($success); + } +} diff --git a/ts/Manager/Api.ts b/ts/Manager/Api.ts index 9e545d3..6c9b788 100644 --- a/ts/Manager/Api.ts +++ b/ts/Manager/Api.ts @@ -9,6 +9,19 @@ export interface Room { record: boolean; } +export type Recording = { + id: string; + name: string; + published: boolean; + state: 'processing' | 'processed' | 'published' | 'unpublished' | 'deleted'; + startTime: number; + participants: number; + type: string; + length: number; + url: string; + meta: any; +} + class Api { public getUrl(endpoint: string): string { return OC.generateUrl(`apps/bbb/${endpoint}`); @@ -42,6 +55,26 @@ class Api { return response.data; } + + public async getRecordings(uid: string) { + const response = await axios.get(this.getUrl(`server/${uid}/records`)); + + return response.data; + } + + public async deleteRecording(id: string) { + const response = await axios.delete(this.getUrl(`server/record/${id}`)); + + return response.data; + } + + public async storeRecording(recording: Recording, path: string) { + const startDate = new Date(recording.startTime); + const url = `/remote.php/dav/files/${OC.currentUser}${path}/${encodeURIComponent(recording.name + ' ' + startDate.toISOString())}.url`; + const response = await axios.put(url, `[InternetShortcut]\nURL=${recording.url}`); + + return response.data; + } } export const api = new Api(); diff --git a/ts/Manager/App.scss b/ts/Manager/App.scss index e3f4cea..57c5baa 100644 --- a/ts/Manager/App.scss +++ b/ts/Manager/App.scss @@ -102,4 +102,25 @@ background-color: rgba(0, 128, 0, 0.445); } } + + .selected-row, + .recordings-row { + border-left: 3px solid #888; + } + + .selected-row { + background-color: #f0f0f0; + } + + .recordings-row { + background-color: #f7f7f7; + + &> td { + padding: 0; + } + + table { + width: 100%; + } + } } diff --git a/ts/Manager/App.tsx b/ts/Manager/App.tsx index 5d6c98d..b5c8e9e 100644 --- a/ts/Manager/App.tsx +++ b/ts/Manager/App.tsx @@ -109,6 +109,9 @@ const App: React.FC = () => { onOrderBy('record')}> {t('bbb', 'Record')} + + {t('bbb', 'Recordings')} + diff --git a/ts/Manager/Nextcloud.d.ts b/ts/Manager/Nextcloud.d.ts index 7096299..c63bf4a 100644 --- a/ts/Manager/Nextcloud.d.ts +++ b/ts/Manager/Nextcloud.d.ts @@ -10,6 +10,8 @@ declare namespace OC { } namespace dialogs { + function alert(text: string, title: string, callback: () => void, modal?: boolean): void; + function info(text: string, title: string, callback: () => void, modal?: boolean): void; function confirm(text: string, title: string, callback: (result: boolean) => void, modal?: boolean): void; @@ -61,6 +63,8 @@ declare namespace OC { const PERMISSION_SHARE = 16; const PERMISSION_ALL = 31; + const currentUser: string; + const config: { blacklist_files_regex: string; enable_avatars: boolean; @@ -77,6 +81,8 @@ declare namespace OC { declare function t(app: string, string: string, vars?: { [key: string]: string }, count?: number, options?: EscapeOptions): string; +declare function n(app: string, singular: string, plural: string, number: number, vars?: { [key: string]: string }): string; + declare module 'NC' { export interface OCSResult { ocs: { diff --git a/ts/Manager/RecordingRow.tsx b/ts/Manager/RecordingRow.tsx new file mode 100644 index 0000000..e04ac68 --- /dev/null +++ b/ts/Manager/RecordingRow.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import { Recording } from './Api'; + +type Props = { + recording: Recording; + deleteRecording: (recording: Recording) => void; + storeRecording: (recording: Recording) => void; +} + +const RecordingRow: React.FC = ({recording, deleteRecording, storeRecording}) => { + return ( + + + + + + + + + + + storeRecording(recording)} className="icon icon-download icon-visible"> + + + {(new Date(recording.startTime)).toLocaleString()} + + + {recording.length === 0 ? '< 1 min' : (recording.length + ' min')} + + + {n('bbb', '%n participant', '%n participants', recording.participants)} + + + {recording.type} + + + deleteRecording(recording)} + title={t('bbb', 'Delete')} /> + + + ); +}; + +export default RecordingRow; \ No newline at end of file diff --git a/ts/Manager/RoomRow.tsx b/ts/Manager/RoomRow.tsx index 55acfc0..a452650 100644 --- a/ts/Manager/RoomRow.tsx +++ b/ts/Manager/RoomRow.tsx @@ -1,28 +1,51 @@ -import React, { useState } from 'react'; -import {CopyToClipboard} from 'react-copy-to-clipboard'; +import React, { useState, useEffect } from 'react'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; import { SubmitInput } from './SubmitInput'; -import { Room, api } from './Api'; +import { Room, Recording, api } from './Api'; +import RecordingRow from './RecordingRow'; type Props = { - room: Room; - updateRoom: (room: Room) => void; - deleteRoom: (id: number) => void; + room: Room; + updateRoom: (room: Room) => void; + deleteRoom: (id: number) => void; } type EditableValueProps = { - setValue: (key: string, value: string|number) => void; - setActive: (key: string) => void; - active: string; - field: string; - value: string; - type: 'text' | 'number'; + setValue: (key: string, value: string | number) => void; + setActive: (key: string) => void; + active: string; + field: string; + value: string; + type: 'text' | 'number'; } +type RecordingsNumberProps = { + recordings: null | Recording[]; + showRecordings: boolean; + setShowRecordings: (showRecordings: boolean) => void; +} + +const RecordingsNumber: React.FC = ({ recordings, showRecordings, setShowRecordings }) => { + if (recordings === null) { + return ; + } + + if (recordings.length > 0) { + return ( + setShowRecordings(!showRecordings)}> + {recordings.length} {showRecordings ? '▼' : '▲'} + + ); + } + + return 0; +}; + const EditableValue: React.FC = ({ setValue, setActive, active, field, value, type }) => { if (active === field) { return setValue(field, type === 'number' ? parseInt(value):value)} + onSubmitValue={(value) => setValue(field, type === 'number' ? parseInt(value) : value)} onClick={event => event.stopPropagation()} initialValue={value} type={type} @@ -40,9 +63,26 @@ const EditableValue: React.FC = ({ setValue, setActive, acti const RoomRow: React.FC = (props) => { const [activeEdit, setActiveEdit] = useState(''); + const [recordings, setRecordings] = useState(null); + const [showRecordings, setShowRecordings] = useState(false); const room = props.room; + const areRecordingsLoaded = recordings !== null; - function updateRoom(key: string, value: string|boolean|number) { + useEffect(() => { + if (areRecordingsLoaded) { + return; + } + + api.getRecordings(room.uid).then(recordings => { + setRecordings(recordings); + }).catch(err => { + console.warn('Could not request recordings: ' + room.uid, err); + + setRecordings([]); + }); + }, [areRecordingsLoaded]); + + function updateRoom(key: string, value: string | boolean | number) { props.updateRoom({ ...props.room, [key]: value, @@ -66,39 +106,108 @@ const RoomRow: React.FC = (props) => { ); } - function edit(field: string, type: 'text' | 'number' = 'text'){ + function storeRecording(recording: Recording) { + OC.dialogs.filepicker(t('bbb', 'Select target folder'), (path: string) => { + api.storeRecording(recording, path).then(() => { + OC.dialogs.info( + t('bbb', 'URL to presentation was stored in "{path}"', { path: path + '/' }), + t('bbb', 'File stored'), + () => undefined, + ); + }).catch(err => { + console.warn('Could not store recording', err); + + OC.dialogs.alert( + t('bbb', 'URL to presentation could not be stored.'), + t('bbb', 'Error'), + () => undefined + ); + }); + }, undefined, 'httpd/unix-directory'); + } + + function deleteRecording(recording: Recording) { + OC.dialogs.confirm( + t('bbb', 'Are you sure you want to delete the recording from "{startDate}"? This operation can not be undone', { startDate: (new Date(recording.startTime)).toLocaleString() }), + t('bbb', 'Delete?'), + confirmed => { + if (confirmed) { + api.deleteRecording(recording.id).then(success => { + if (!success) { + OC.dialogs.info( + t('bbb', 'Could not delete record'), + t('bbb', 'Error'), + () => undefined, + ); + + return; + } + + if (recordings === null) { + return; + } + + setRecordings(recordings.filter(r => r.id !== recording.id)); + }).catch(err => { + console.warn('Could not delete recording', err); + + OC.dialogs.info( + t('bbb', 'Could not delete record'), + t('bbb', 'Server error'), + () => undefined, + ); + }); + } + }, + true + ); + } + + function edit(field: string, type: 'text' | 'number' = 'text') { return ; } return ( - - - - - - - - - - - {edit('name')} - - - {edit('welcome')} - - - {edit('maxParticipants', 'number')} - - - updateRoom('record', event.target.checked)} /> - - - - - - + <> + + + + + + + + + + + {edit('name')} + + + {edit('welcome')} + + + {edit('maxParticipants', 'number')} + + + updateRoom('record', event.target.checked)} /> + + + + + + + + {showRecordings && + + + + {recordings?.map(recording => )} + +
+ + } + ); };