From 15a7037b0451ca8047a34c7c5812f47dfcb4e5e7 Mon Sep 17 00:00:00 2001 From: csantosm <4a.santos@gmail.com> Date: Fri, 10 Jun 2022 11:27:43 +0200 Subject: [PATCH] openvidu-components: Added admin dashboard --- .../openvidu-angular/doc/tsconfig.doc.json | 1 + .../admin/dashboard/dashboard.component.css | 186 ++++++++++++++++++ .../admin/dashboard/dashboard.component.html | 145 ++++++++++++++ .../dashboard/dashboard.component.spec.ts | 25 +++ .../admin/dashboard/dashboard.component.ts | 153 ++++++++++++++ .../src/lib/admin/login/login.component.css | 68 +++++++ .../src/lib/admin/login/login.component.html | 44 +++++ .../lib/admin/login/login.component.spec.ts | 25 +++ .../src/lib/admin/login/login.component.ts | 110 +++++++++++ .../src/lib/directives/api/admin.directive.ts | 86 ++++++++ .../directives/api/api.directive.module.ts | 10 +- .../src/lib/openvidu-angular.module.ts | 11 +- .../src/lib/pipes/recording.pipe.ts | 31 +++ .../config/openvidu-angular.config.service.ts | 7 + .../openvidu-angular/src/public-api.ts | 3 + 15 files changed, 900 insertions(+), 5 deletions(-) create mode 100644 openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.css create mode 100644 openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.html create mode 100644 openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.spec.ts create mode 100644 openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.ts create mode 100644 openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.css create mode 100644 openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.html create mode 100644 openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.spec.ts create mode 100644 openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.ts create mode 100644 openvidu-components-angular/projects/openvidu-angular/src/lib/directives/api/admin.directive.ts diff --git a/openvidu-components-angular/projects/openvidu-angular/doc/tsconfig.doc.json b/openvidu-components-angular/projects/openvidu-angular/doc/tsconfig.doc.json index eee9aaee..40ed982c 100644 --- a/openvidu-components-angular/projects/openvidu-angular/doc/tsconfig.doc.json +++ b/openvidu-components-angular/projects/openvidu-angular/doc/tsconfig.doc.json @@ -1,5 +1,6 @@ { "include": [ + "../src/lib/admin/**/*.ts", "../src/lib/components/**/*.ts", "../src/lib/directives/**/*.ts", "../src/lib/services/**/*.ts", diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.css b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.css new file mode 100644 index 00000000..6d98062f --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.css @@ -0,0 +1,186 @@ +.dashboard-container { + height: 100%; +} + +.header { + height: 50px; + background-color: var(--ov-secondary-color); + color: var(--ov-text-color); +} +.dashboard-body { + height: calc(100% - 75px); +} + +#toolbar-search { + width: 100%; + height: 40px; + background-color: var(--ov-light-color); + padding: 6px; + display: flex; + align-items: center; +} + +#toolbar-sort-div { + display: flex; + flex-direction: row; + align-items: center; +} + +#sort-menu-btn { + margin-left: 5px; + background-color: var(--ov-panel-background); +} + +.search-bar { + height: 95%; + width: 30%; + display: flex; + background-color: var(--ov-panel-background); + padding: 0px 10px; + border-radius: var(--ov-panel-radius); +} + +#search-input { + width: 100%; + height: 16px; + margin: auto; + background-color: transparent; + display: block; + border: none; + padding: 0; + word-wrap: break-word; + white-space: pre-wrap; + resize: none; + outline: none; + + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif; +} +#refresh-btn { + float: right; +} + +#refresh-btn mat-icon { + color: inherit; +} + +.refresh-btn { + position: absolute; + right: 0; + display: inline-flex; +} + +.recordings-container { + height: calc(100% - 51px); + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + overflow-y: auto; + overflow-x: hidden; +} + +.recording-card { + background-color: var(--ov-panel-background); +} + +.video-div-container { + position: relative; + text-align: center; + width: 100%; + height: 42%; + overflow: hidden; + background-color: var(--ov-light-color); + display: flex; + justify-content: center; + align-items: center; +} +.video-div-container img { + border-radius: var(--ov-video-radius); + display: inline; + position: relative; + max-width: 100%; + max-height: 100%; +} + +.item { + flex-grow: 1; + margin: 10px; +} + +.item + .item { + margin-left: 2%; +} + +.video-btns { + position: absolute; + transform: translate(-50%, -50%); + margin-right: -50%; + top: 50%; + left: 50%; + background-color: var(--ov-logo-background-color); + border-radius: var(--ov-panel-radius); +} + +.video-btns button #play { + color: var(--ov-text-color); +} +.video-btns button #download { + color: var(--ov-tertiary-color); +} +.video-btns button #delete { + color: var(--ov-warn-color); +} + +.video-info-container > div { + width: 100%; + height: 100%; + display: table; + table-layout: fixed; + box-sizing: border-box; + margin-top: 20px; +} +.video-div-tag:first-child { + margin-top: 20px; +} + +.video-div-tag { + display: table-row; +} + +.video-card-tag { + font-size: 13px; + color: var(--ov-panel-text-color); +} +.video-card-value { + float: right; + font-size: 13.5px; +} + +.footer { + height: 25px; + background-color: var(--ov-secondary-color); + color: var(--ov-text-color); + position: absolute; + bottom: 0; + left: 0; + font-size: 12px; +} +.footer a { + color: var(--ov-tertiary-color); +} + +.no-recordings-warn { + height: calc(100% - 52px); + width: 100%; + display: table; + text-align: center; +} +::ng-deep .mat-form-field-appearance-fill .mat-form-field-flex { + padding: 0px !important; + background-color: var(--ov-light-color) !important; +} + +::ng-deep .mat-form-field-wrapper { + height: 100% !important; +} diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.html b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.html new file mode 100644 index 00000000..59f13eaf --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.html @@ -0,0 +1,145 @@ +
+ + {{ 'ADMIN.DASHBOARD' | translate }} + + +
+ + +
+ {{ 'ADMIN.NO_RECORDINGS' | translate }} +
+ +
+
+ + +
+ +
+ + + + +
+
+
+
+
+ {{ 'ADMIN.NAME' | translate }}{{ recording.properties.name }} +
+
+ {{ 'ADMIN.SESSION' | translate }}{{ recording.sessionId }} +
+
+ {{ 'ADMIN.OUTPUT' | translate }}{{ recording.properties.outputMode }} +
+
+ {{ 'ADMIN.DATE' | translate }}{{ recording.createdAt | date: 'M/d/yy, H:mm' }} +
+
+ {{ 'ADMIN.DURATION' | translate }}{{ recording.duration | duration }} +
+
+ {{ 'ADMIN.SIZE' | translate }}{{ recording.size / 1024 / 1024 | number: '1.1-2' }} MBs +
+
+ {{ 'ADMIN.STATUS' | translate }}{{ recording.status }} +
+
+
+
+
+
+
+
+ + + {{ 'ADMIN.POWERED_BY' | translate }} + OpenVidu + +
diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.spec.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.spec.ts new file mode 100644 index 00000000..5ec4ff8f --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DashboardComponent } from './dashboard.component'; + +describe('DashboardComponent', () => { + let component: DashboardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DashboardComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DashboardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.ts new file mode 100644 index 00000000..24fb5bc6 --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/dashboard/dashboard.component.ts @@ -0,0 +1,153 @@ +import { Component, OnInit, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { RecordingInfo } from '../../models/recording.model'; +import { ActionService } from '../../services/action/action.service'; +import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service'; +import { RecordingService } from '../../services/recording/recording.service'; + +@Component({ + selector: 'ov-admin-dashboard', + templateUrl: './dashboard.component.html', + styleUrls: ['./dashboard.component.css'] +}) +export class AdminDashboardComponent implements OnInit, OnDestroy { + /** + * Provides event notifications that fire when download recording button has been clicked. + * The recording should be downloaded using the REST API. + */ + @Output() onDownloadRecordingClicked: EventEmitter = new EventEmitter(); + + /** + * Provides event notifications that fire when delete recording button has been clicked. + * The recording should be deleted using the REST API. + */ + @Output() onDeleteRecordingClicked: EventEmitter = new EventEmitter(); + + /** + * Provides event notifications that fire when play recording button has been clicked. + */ + @Output() onPlayRecordingClicked: EventEmitter = new EventEmitter(); + + /** + * @internal + */ + recordings: RecordingInfo[] = []; + /** + * @internal + */ + sortDescendent = true; + /** + * @internal + */ + sortByLegend = 'Sort by'; + /** + * @internal + */ + searchValue = ''; + private adminSubscription: Subscription; + /** + * @internal + */ + constructor( + private actionService: ActionService, + private recordingService: RecordingService, + private libService: OpenViduAngularConfigService + ) {} + + /** + * @internal + */ + ngOnInit(): void { + this.subscribeToAdminDirectives(); + } + ngOnDestroy() { + if (this.adminSubscription) this.adminSubscription.unsubscribe(); + } + + /** + * @internal + */ + sortRecordingsByDate() { + this.recordings.sort((a, b) => { + if (a.createdAt > b.createdAt) { + return this.sortDescendent ? -1 : 1; + } else if (a.createdAt < b.createdAt) { + return this.sortDescendent ? 1 : -1; + } else { + return 0; + } + }); + this.sortByLegend = 'Date'; + } + + /** + * @internal + */ + sortRecordingsByDuration() { + this.recordings.sort((a, b) => { + if (a.duration > b.duration) { + return this.sortDescendent ? -1 : 1; + } else if (a.duration < b.duration) { + return this.sortDescendent ? 1 : -1; + } else { + return 0; + } + }); + this.sortByLegend = 'Duration'; + } + + /** + * @internal + */ + sortRecordingsBySize() { + this.recordings.sort((a, b) => { + if (a.size > b.size) { + return this.sortDescendent ? -1 : 1; + } else if (a.size < b.size) { + return this.sortDescendent ? 1 : -1; + } else { + return 0; + } + }); + this.sortByLegend = 'Size'; + } + + /** + * @internal + */ + getThumbnailSrc(recording: RecordingInfo): string { + return !recording.url ? undefined : recording.url.substring(0, recording.url.lastIndexOf('/')) + '/' + recording.id + '.jpg'; + } + + /** + * @internal + */ + deleteRecording(recordingId: string) { + const succsessCallback = () => { + this.onDeleteRecordingClicked.emit(recordingId); + }; + this.actionService.openDeleteRecordingDialog(succsessCallback); + } + + /** + * @internal + */ + download(recordingId: string) { + //TODO solucionar el tema del login. + // TODO Si soy capaz de loguearme en openvidu al hacer login en el dashboard, no necesitaria emitir evento + this.onDownloadRecordingClicked.emit(recordingId); + } + + /** + * @internal + */ + async play(recording: RecordingInfo) { + this.actionService.openRecordingPlayerDialog(recording.url, 'video/mp4', true); + } + + private subscribeToAdminDirectives() { + this.adminSubscription = this.libService.adminRecordingsListObs.subscribe((recordings: RecordingInfo[]) => { + this.recordings = recordings; + }); + } +} diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.css b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.css new file mode 100644 index 00000000..aaa938cb --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.css @@ -0,0 +1,68 @@ +mat-card { + max-width: 220px; + margin: auto; + margin-top: 10vh; +} + +mat-card-content { + margin-bottom: 8px; +} + +mat-card-actions { + padding-top: 0px; +} + +.header { + height: 50px; + background-color: var(--ov-secondary-color); + color: var(--ov-text-color); +} + +mat-spinner { + margin: auto; +} +.mat-card-actions { + margin: 0; +} + +.full-width { + width: 100%; +} + +.outer { + display: table; + position: absolute; + height: 100%; + width: 100%; +} + +.middle { + display: table-cell; + vertical-align: middle; +} + +.inner { + margin-left: auto; + margin-right: auto; +} + +#login-btn { + text-transform: none; + font-size: 17px; + width: 100%; +} + +::ng-deep .mat-input-element { + caret-color: #000000; +} +::ng-deep .mat-primary .mat-option.mat-selected:not(.mat-option-disabled) { + color: #000000; +} + +::ng-deep .mat-form-field-label { + color: var(--ov-panel-text-color) !important; +} + +::ng-deep .mat-form-field.mat-focused .mat-form-field-ripple { + background-color: var(--ov-panel-text-color) !important; +} diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.html b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.html new file mode 100644 index 00000000..1134fa83 --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.html @@ -0,0 +1,44 @@ + + + +
+
+
+ +
+
+
+ + + +
+ + + + +
+ + {{ 'ADMIN.SECRET' | translate }} + + {{ 'ADMIN.SECRET_REQURED' | translate }} + +
+ +
+ +
+ + + +
diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.spec.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.spec.ts new file mode 100644 index 00000000..ccb7deae --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminLoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: AdminLoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminLoginComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminLoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.ts new file mode 100644 index 00000000..62c4f5e4 --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/admin/login/login.component.ts @@ -0,0 +1,110 @@ +import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'; +import { FormControl, Validators, FormGroupDirective, NgForm } from '@angular/forms'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { Subscription } from 'rxjs'; +import { ActionService } from '../../services/action/action.service'; +import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service'; + +@Component({ + selector: 'ov-admin-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.css'] +}) +export class AdminLoginComponent implements OnInit { + /** + * Provides event notifications that fire when login button has been clicked. + * The event will contain the password value. + */ + @Output() onLoginButtonClicked: EventEmitter = new EventEmitter(); + + /** + * @internal + */ + checkingLogged = false; + /** + * @internal + */ + secret: string; + /** + * @internal + */ + showSpinner = false; + + /** + * @internal + */ + loginFormControl = new FormControl('', [Validators.required]); + /** + * @internal + */ + matcher = new FormErrorStateMatcher(); + + /** + * @internal + */ + @ViewChild('submitBtn') submitBtn: ElementRef; + /** + * @internal + */ + @ViewChild('loginForm', { read: ElementRef }) loginForm: ElementRef; + + private errorSub: Subscription; + + /** + * @internal + */ + constructor(private libService: OpenViduAngularConfigService, private actionService: ActionService) {} + + /** + * @internal + */ + ngOnInit() { + this.subscribeToAdminLoginDirectives(); + } + + /** + * @internal + */ + ngOnDestroy() { + this.showSpinner = false; + if (this.errorSub) this.errorSub.unsubscribe(); + } + + /** + * @internal + */ + login() { + this.showSpinner = true; + this.onLoginButtonClicked.emit(this.secret); + } + + /** + * @internal + */ + submitForm() { + if (this.loginForm.nativeElement.checkValidity()) { + this.login(); + } else { + this.submitBtn.nativeElement.click(); + } + } + + private subscribeToAdminLoginDirectives() { + this.errorSub = this.libService.adminLoginErrorObs.subscribe((value) => { + const errorExists = !!value; + if (errorExists) { + this.showSpinner = false; + this.actionService.openDialog(value.error, value.message, true); + } + }); + } +} +/** + * @internal + */ +export class FormErrorStateMatcher implements ErrorStateMatcher { + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const isSubmitted = form && form.submitted; + return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted)); + } +} diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/directives/api/admin.directive.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/directives/api/admin.directive.ts new file mode 100644 index 00000000..05c5ce7e --- /dev/null +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/directives/api/admin.directive.ts @@ -0,0 +1,86 @@ +import { Directive, AfterViewInit, OnDestroy, Input, ElementRef } from '@angular/core'; +import { RecordingInfo } from '../../models/recording.model'; +import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service'; + +/** + * The **recordingsList** directive allows show all recordings saved in your OpenVidu deployment in {@link AdminDashboardComponent}. + * + * Default: `[]` + * + * @example + * + * + */ +@Directive({ + selector: 'ov-admin-dashboard[recordingsList]' +}) +export class AdminRecordingsListDirective implements AfterViewInit, OnDestroy { + + @Input() set recordingsList(value: RecordingInfo[]) { + this.recordingsValue = value; + this.update(this.recordingsValue); + } + + recordingsValue: RecordingInfo [] = []; + + constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {} + + ngAfterViewInit() { + this.update(this.recordingsValue); + } + ngOnDestroy(): void { + this.clear(); + } + clear() { + this.recordingsValue = null; + this.update(null); + } + + update(value: RecordingInfo[]) { + if (this.libService.adminRecordingsList.getValue() !== value) { + this.libService.adminRecordingsList.next(value); + } + } +} + +/** + * The **error** directive allows show the authentication error in {@link AdminLoginComponent}. + * + * Default: `null` + * + * @example + * + * + */ + @Directive({ + selector: 'ov-admin-login[error]' +}) +export class AdminLoginDirective implements AfterViewInit, OnDestroy { + + @Input() set error(value: any) { + this.errorValue = value; + this.update(this.errorValue); + } + + errorValue: any = null; + + constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {} + + ngAfterViewInit() { + this.update(this.errorValue); + } + ngOnDestroy(): void { + this.clear(); + } + clear() { + this.errorValue = null; + this.update(null); + } + + update(value: any) { + if (this.libService.adminLoginError.getValue() !== value) { + this.libService.adminLoginError.next(value); + } + } +} + diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/directives/api/api.directive.module.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/directives/api/api.directive.module.ts index fb2e7909..a4a3a0c9 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/directives/api/api.directive.module.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/directives/api/api.directive.module.ts @@ -1,5 +1,6 @@ import { NgModule } from '@angular/core'; import { ActivitiesPanelRecordingActivityDirective } from './activities-panel.directive'; +import { AdminLoginDirective, AdminRecordingsListDirective } from './admin.directive'; import { LogoDirective } from './internals.directive'; import { ParticipantPanelItemMuteButtonDirective } from './participant-panel-item.directive'; import { RecordingActivityRecordingErrorDirective, RecordingActivityRecordingsListDirective } from './recording-activity.directive'; @@ -29,7 +30,6 @@ import { LangDirective } from './videoconference.directive'; - @NgModule({ declarations: [ MinimalDirective, @@ -55,7 +55,9 @@ import { ParticipantNameDirective, ActivitiesPanelRecordingActivityDirective, RecordingActivityRecordingsListDirective, - RecordingActivityRecordingErrorDirective + RecordingActivityRecordingErrorDirective, + AdminRecordingsListDirective, + AdminLoginDirective ], exports: [ MinimalDirective, @@ -81,7 +83,9 @@ import { ParticipantNameDirective, ActivitiesPanelRecordingActivityDirective, RecordingActivityRecordingsListDirective, - RecordingActivityRecordingErrorDirective + RecordingActivityRecordingErrorDirective, + AdminRecordingsListDirective, + AdminLoginDirective ] }) export class ApiDirectiveModule {} diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/openvidu-angular.module.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/openvidu-angular.module.ts index 46925bb8..a8515a21 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/openvidu-angular.module.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/openvidu-angular.module.ts @@ -40,7 +40,7 @@ import { DeleteDialogComponent } from './components/dialogs/delete-recording.com import { LinkifyPipe } from './pipes/linkify.pipe'; import { TranslatePipe } from './pipes/translate.pipe'; import { StreamTypesEnabledPipe, ParticipantStreamsPipe } from './pipes/participant.pipe'; -import { DurationFromSecondsPipe } from './pipes/recording.pipe'; +import { DurationFromSecondsPipe, SearchByStringPropertyPipe } from './pipes/recording.pipe'; import { OpenViduAngularConfig } from './config/openvidu-angular.config'; import { CdkOverlayContainer } from './config/custom-cdk-overlay'; @@ -72,6 +72,8 @@ import { ApiDirectiveModule } from './directives/api/api.directive.module'; import { BackgroundEffectsPanelComponent } from './components/panel/background-effects-panel/background-effects-panel.component'; import { ActivitiesPanelComponent } from './components/panel/activities-panel/activities-panel.component'; import { RecordingActivityComponent } from './components/panel/activities-panel/recording-activity-panel/recording-activity.component'; +import { AdminDashboardComponent } from './admin/dashboard/dashboard.component'; +import { AdminLoginComponent } from './admin/login/login.component'; @NgModule({ declarations: [ @@ -87,6 +89,7 @@ import { RecordingActivityComponent } from './components/panel/activities-panel/ LinkifyPipe, ParticipantStreamsPipe, DurationFromSecondsPipe, + SearchByStringPropertyPipe, StreamTypesEnabledPipe, TranslatePipe, ParticipantPanelItemComponent, @@ -98,7 +101,9 @@ import { RecordingActivityComponent } from './components/panel/activities-panel/ PreJoinComponent, BackgroundEffectsPanelComponent, ActivitiesPanelComponent, - RecordingActivityComponent + RecordingActivityComponent, + AdminDashboardComponent, + AdminLoginComponent ], imports: [ CommonModule, @@ -162,6 +167,8 @@ import { RecordingActivityComponent } from './components/panel/activities-panel/ VideoComponent, AudioWaveComponent, PreJoinComponent, + AdminDashboardComponent, + AdminLoginComponent, ParticipantStreamsPipe, DurationFromSecondsPipe, StreamTypesEnabledPipe, diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/pipes/recording.pipe.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/pipes/recording.pipe.ts index 92fbb1f0..0c771064 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/pipes/recording.pipe.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/pipes/recording.pipe.ts @@ -19,3 +19,34 @@ export class DurationFromSecondsPipe implements PipeTransform { } } } + +/** + * @internal + */ + @Pipe({ + name: 'searchByStringProperty' +}) +export class SearchByStringPropertyPipe implements PipeTransform { + transform(items: any[], props: { properties: string[], filter: string }): any { + if (!items || !props || props.properties.length === 0 || !props.filter) { + return items; + } + return items.filter(item => { + return props.properties.some(prop => { + const multipleProps = prop.split('.'); + let recursiveProp = item; + try { + multipleProps.forEach(p => { + recursiveProp = recursiveProp[p]; + if (recursiveProp === null || recursiveProp === undefined) { + throw new Error('Property not found'); + } + }); + } catch (error) { + return false; + } + return recursiveProp.indexOf(props.filter) !== -1; + }) + }); + } +} diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/config/openvidu-angular.config.service.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/config/openvidu-angular.config.service.ts index 5130f809..92f82d9c 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/config/openvidu-angular.config.service.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/config/openvidu-angular.config.service.ts @@ -63,6 +63,10 @@ export class OpenViduAngularConfigService { recordingActivityObs: Observable; recordingError = >new BehaviorSubject(null); recordingErrorObs: Observable; + adminRecordingsList = >new BehaviorSubject([]); + adminRecordingsListObs: Observable; + adminLoginError = >new BehaviorSubject(null); + adminLoginErrorObs: Observable; constructor(@Inject('OPENVIDU_ANGULAR_CONFIG') config: OpenViduAngularConfig) { this.configuration = config; @@ -94,6 +98,9 @@ export class OpenViduAngularConfigService { this.recordingActivityObs = this.recordingActivity.asObservable(); this.recordingsListObs = this.recordingsList.asObservable(); this.recordingErrorObs = this.recordingError.asObservable(); + // Admin dashboard + this.adminRecordingsListObs = this.adminRecordingsList.asObservable(); + this.adminLoginErrorObs = this.adminLoginError.asObservable(); } getConfig(): OpenViduAngularConfig { diff --git a/openvidu-components-angular/projects/openvidu-angular/src/public-api.ts b/openvidu-components-angular/projects/openvidu-angular/src/public-api.ts index 1f44f6c8..4af12474 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/public-api.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/public-api.ts @@ -36,6 +36,8 @@ export * from './lib/components/stream/stream.component'; export * from './lib/components/video/video.component'; export * from './lib/components/audio-wave/audio-wave.component'; export * from './lib/components/pre-join/pre-join.component'; +export * from './lib/admin/dashboard/dashboard.component'; +export * from './lib/admin/login/login.component'; // Models export * from './lib/models/participant.model'; @@ -63,3 +65,4 @@ export * from './lib/directives/api/videoconference.directive'; export * from './lib/directives/api/participant-panel-item.directive'; export * from './lib/directives/api/activities-panel.directive'; export * from './lib/directives/api/recording-activity.directive'; +export * from './lib/directives/api/admin.directive';