From b764380ac23f4e9d4677d08acdc3474c2931a16d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 10 Jan 2020 10:11:28 +0100 Subject: Add ability to list redundancies --- client/src/app/+admin/admin.component.html | 2 +- client/src/app/+admin/admin.module.ts | 13 +- .../src/app/+admin/follows/follows.component.html | 6 +- client/src/app/+admin/follows/follows.routes.ts | 5 + client/src/app/+admin/follows/index.ts | 1 + .../shared/redundancy-checkbox.component.ts | 2 +- .../+admin/follows/shared/redundancy.service.ts | 28 ---- .../follows/video-redundancies-list/index.ts | 1 + .../video-redundancies-list.component.html | 82 ++++++++++ .../video-redundancies-list.component.scss | 37 +++++ .../video-redundancies-list.component.ts | 178 +++++++++++++++++++++ .../video-redundancy-information.component.html | 24 +++ .../video-redundancy-information.component.scss | 8 + .../video-redundancy-information.component.ts | 11 ++ .../src/app/+admin/system/jobs/jobs.component.ts | 15 +- 15 files changed, 370 insertions(+), 43 deletions(-) delete mode 100644 client/src/app/+admin/follows/shared/redundancy.service.ts create mode 100644 client/src/app/+admin/follows/video-redundancies-list/index.ts create mode 100644 client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html create mode 100644 client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss create mode 100644 client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts create mode 100644 client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.html create mode 100644 client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.scss create mode 100644 client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.ts (limited to 'client/src/app/+admin') diff --git a/client/src/app/+admin/admin.component.html b/client/src/app/+admin/admin.component.html index 9a3d90c18..0d06aaedc 100644 --- a/client/src/app/+admin/admin.component.html +++ b/client/src/app/+admin/admin.component.html @@ -5,7 +5,7 @@ - Manage follows + Follows & redundancies diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts index 9c56b5750..fdbe70314 100644 --- a/client/src/app/+admin/admin.module.ts +++ b/client/src/app/+admin/admin.module.ts @@ -5,7 +5,7 @@ import { TableModule } from 'primeng/table' import { SharedModule } from '../shared' import { AdminRoutingModule } from './admin-routing.module' import { AdminComponent } from './admin.component' -import { FollowersListComponent, FollowingAddComponent, FollowsComponent } from './follows' +import { FollowersListComponent, FollowingAddComponent, FollowsComponent, VideoRedundanciesListComponent } from './follows' import { FollowingListComponent } from './follows/following-list/following-list.component' import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersComponent, UserUpdateComponent } from './users' import { @@ -16,7 +16,6 @@ import { } from './moderation' import { ModerationComponent } from '@app/+admin/moderation/moderation.component' import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component' -import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' import { JobsComponent } from '@app/+admin/system/jobs/jobs.component' import { JobService, LogsComponent, LogsService, SystemComponent } from '@app/+admin/system' @@ -27,13 +26,18 @@ import { PluginSearchComponent } from '@app/+admin/plugins/plugin-search/plugin- import { PluginShowInstalledComponent } from '@app/+admin/plugins/plugin-show-installed/plugin-show-installed.component' import { SelectButtonModule } from 'primeng/selectbutton' import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' +import { VideoRedundancyInformationComponent } from '@app/+admin/follows/video-redundancies-list/video-redundancy-information.component' +import { ChartModule } from 'primeng/chart' @NgModule({ imports: [ AdminRoutingModule, + + SharedModule, + TableModule, SelectButtonModule, - SharedModule + ChartModule ], declarations: [ @@ -44,6 +48,8 @@ import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' FollowersListComponent, FollowingListComponent, RedundancyCheckboxComponent, + VideoRedundanciesListComponent, + VideoRedundancyInformationComponent, UsersComponent, UserCreateComponent, @@ -78,7 +84,6 @@ import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' ], providers: [ - RedundancyService, JobService, LogsService, DebugService, diff --git a/client/src/app/+admin/follows/follows.component.html b/client/src/app/+admin/follows/follows.component.html index 21d477132..46581daf9 100644 --- a/client/src/app/+admin/follows/follows.component.html +++ b/client/src/app/+admin/follows/follows.component.html @@ -1,5 +1,5 @@
-
Manage follows
+
Follows & redundancies
- \ No newline at end of file + diff --git a/client/src/app/+admin/follows/follows.routes.ts b/client/src/app/+admin/follows/follows.routes.ts index e84c79e82..298733eb0 100644 --- a/client/src/app/+admin/follows/follows.routes.ts +++ b/client/src/app/+admin/follows/follows.routes.ts @@ -6,6 +6,7 @@ import { FollowingAddComponent } from './following-add' import { FollowersListComponent } from './followers-list' import { UserRight } from '../../../../../shared' import { FollowingListComponent } from './following-list/following-list.component' +import { VideoRedundanciesListComponent } from '@app/+admin/follows/video-redundancies-list' export const FollowsRoutes: Routes = [ { @@ -47,6 +48,10 @@ export const FollowsRoutes: Routes = [ title: 'Add follow' } } + }, + { + path: 'video-redundancies-list', + component: VideoRedundanciesListComponent } ] } diff --git a/client/src/app/+admin/follows/index.ts b/client/src/app/+admin/follows/index.ts index e94f33710..4fcb35cb1 100644 --- a/client/src/app/+admin/follows/index.ts +++ b/client/src/app/+admin/follows/index.ts @@ -1,5 +1,6 @@ export * from './following-add' export * from './followers-list' export * from './following-list' +export * from './video-redundancies-list' export * from './follows.component' export * from './follows.routes' diff --git a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts index fa1da26bf..9d7883d97 100644 --- a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts +++ b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts @@ -1,7 +1,7 @@ import { Component, Input } from '@angular/core' import { Notifier } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' -import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' +import { RedundancyService } from '@app/shared/video/redundancy.service' @Component({ selector: 'my-redundancy-checkbox', diff --git a/client/src/app/+admin/follows/shared/redundancy.service.ts b/client/src/app/+admin/follows/shared/redundancy.service.ts deleted file mode 100644 index 87ae01c04..000000000 --- a/client/src/app/+admin/follows/shared/redundancy.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { catchError, map } from 'rxjs/operators' -import { HttpClient } from '@angular/common/http' -import { Injectable } from '@angular/core' -import { RestExtractor } from '@app/shared' -import { environment } from '../../../../environments/environment' - -@Injectable() -export class RedundancyService { - static BASE_USER_SUBSCRIPTIONS_URL = environment.apiUrl + '/api/v1/server/redundancy' - - constructor ( - private authHttp: HttpClient, - private restExtractor: RestExtractor - ) { } - - updateRedundancy (host: string, redundancyAllowed: boolean) { - const url = RedundancyService.BASE_USER_SUBSCRIPTIONS_URL + '/' + host - - const body = { redundancyAllowed } - - return this.authHttp.put(url, body) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - -} diff --git a/client/src/app/+admin/follows/video-redundancies-list/index.ts b/client/src/app/+admin/follows/video-redundancies-list/index.ts new file mode 100644 index 000000000..6a7c7f483 --- /dev/null +++ b/client/src/app/+admin/follows/video-redundancies-list/index.ts @@ -0,0 +1 @@ +export * from './video-redundancies-list.component' diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html new file mode 100644 index 000000000..80c66ec60 --- /dev/null +++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html @@ -0,0 +1,82 @@ +
+
Video redundancies list
+ +
+ + +
+ +
+
+
+ + + + + Strategy + Video name + Video URL + Total size + + + + + + + {{ getRedundancyStrategy(redundancy) }} + + {{ redundancy.name }} + + + {{ redundancy.url }} + + + {{ getTotalSize(redundancy) | bytes: 1 }} + + + + + + + + + + +
+ +
+ + + + + +
+ +
+ + +
+
+ + +
+
Enabled strategies stats
+ +
+ +
+ No redundancy strategy is enabled on your instance. +
+ +
+ +
+ +
+
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss new file mode 100644 index 000000000..05018c281 --- /dev/null +++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss @@ -0,0 +1,37 @@ +@import '_variables'; +@import '_mixins'; + +.expansion-block { + margin-bottom: 20px; +} + +.admin-sub-header { + align-items: flex-end; + + .select-filter-block { + &:not(:last-child) { + margin-right: 10px; + } + + label { + margin-bottom: 2px; + } + + .peertube-select-container { + @include peertube-select-container(auto); + } + } +} + +.redundancies-charts { + margin-top: 50px; + + .chart-blocks { + display: flex; + justify-content: center; + + .chart-block { + margin: 0 20px; + } + } +} diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts new file mode 100644 index 000000000..4b41d1d86 --- /dev/null +++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts @@ -0,0 +1,178 @@ +import { Component, OnInit } from '@angular/core' +import { Notifier, ServerService } from '@app/core' +import { SortMeta } from 'primeng/api' +import { ConfirmService } from '../../../core/confirm/confirm.service' +import { RestPagination, RestTable } from '../../../shared' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' +import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' +import { VideosRedundancyStats } from '@shared/models/server' +import { BytesPipe } from 'ngx-pipes' +import { RedundancyService } from '@app/shared/video/redundancy.service' + +@Component({ + selector: 'my-video-redundancies-list', + templateUrl: './video-redundancies-list.component.html', + styleUrls: [ './video-redundancies-list.component.scss' ] +}) +export class VideoRedundanciesListComponent extends RestTable implements OnInit { + private static LOCAL_STORAGE_DISPLAY_TYPE = 'video-redundancies-list-display-type' + + videoRedundancies: VideoRedundancy[] = [] + totalRecords = 0 + rowsPerPage = 10 + + sort: SortMeta = { field: 'name', order: 1 } + pagination: RestPagination = { count: this.rowsPerPage, start: 0 } + displayType: VideoRedundanciesTarget = 'my-videos' + + redundanciesGraphsData: { stats: VideosRedundancyStats, graphData: object, options: object }[] = [] + + noRedundancies = false + + private bytesPipe: BytesPipe + + constructor ( + private notifier: Notifier, + private confirmService: ConfirmService, + private redundancyService: RedundancyService, + private serverService: ServerService, + private i18n: I18n + ) { + super() + + this.bytesPipe = new BytesPipe() + } + + ngOnInit () { + this.loadSelectLocalStorage() + + this.initialize() + + this.serverService.getServerStats() + .subscribe(res => { + const redundancies = res.videosRedundancy + + if (redundancies.length === 0) this.noRedundancies = true + + for (const r of redundancies) { + this.buildPieData(r) + } + }) + } + + isDisplayingRemoteVideos () { + return this.displayType === 'remote-videos' + } + + getTotalSize (redundancy: VideoRedundancy) { + return redundancy.redundancies.files.reduce((a, b) => a + b.size, 0) + + redundancy.redundancies.streamingPlaylists.reduce((a, b) => a + b.size, 0) + } + + onDisplayTypeChanged () { + this.pagination.start = 0 + this.saveSelectLocalStorage() + + this.loadData() + } + + getRedundancyStrategy (redundancy: VideoRedundancy) { + if (redundancy.redundancies.files.length !== 0) return redundancy.redundancies.files[0].strategy + if (redundancy.redundancies.streamingPlaylists.length !== 0) return redundancy.redundancies.streamingPlaylists[0].strategy + + return '' + } + + buildPieData (stats: VideosRedundancyStats) { + const totalSize = stats.totalSize + ? stats.totalSize - stats.totalUsed + : stats.totalUsed + + if (totalSize === 0) return + + this.redundanciesGraphsData.push({ + stats, + graphData: { + labels: [ this.i18n('Used'), this.i18n('Available') ], + datasets: [ + { + data: [ stats.totalUsed, totalSize ], + backgroundColor: [ + '#FF6384', + '#36A2EB' + ], + hoverBackgroundColor: [ + '#FF6384', + '#36A2EB' + ] + } + ] + }, + options: { + title: { + display: true, + text: stats.strategy + }, + + tooltips: { + callbacks: { + label: (tooltipItem: any, data: any) => { + const dataset = data.datasets[tooltipItem.datasetIndex] + let label = data.labels[tooltipItem.index] + if (label) label += ': ' + else label = '' + + label += this.bytesPipe.transform(dataset.data[tooltipItem.index], 1) + return label + } + } + } + } + }) + } + + async removeRedundancy (redundancy: VideoRedundancy) { + const message = this.i18n('Do you really want to remove this video redundancy?') + const res = await this.confirmService.confirm(message, this.i18n('Remove redundancy')) + if (res === false) return + + this.redundancyService.removeVideoRedundancies(redundancy) + .subscribe( + () => { + this.notifier.success(this.i18n('Video redundancies removed!')) + this.loadData() + }, + + err => this.notifier.error(err.message) + ) + + } + + protected loadData () { + const options = { + pagination: this.pagination, + sort: this.sort, + target: this.displayType + } + + this.redundancyService.listVideoRedundancies(options) + .subscribe( + resultList => { + this.videoRedundancies = resultList.data + this.totalRecords = resultList.total + }, + + err => this.notifier.error(err.message) + ) + } + + private loadSelectLocalStorage () { + const displayType = peertubeLocalStorage.getItem(VideoRedundanciesListComponent.LOCAL_STORAGE_DISPLAY_TYPE) + if (displayType) this.displayType = displayType as VideoRedundanciesTarget + } + + private saveSelectLocalStorage () { + peertubeLocalStorage.setItem(VideoRedundanciesListComponent.LOCAL_STORAGE_DISPLAY_TYPE, this.displayType) + } +} diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.html b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.html new file mode 100644 index 000000000..a379520e3 --- /dev/null +++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.html @@ -0,0 +1,24 @@ +
+ Url + {{ redundancyElement.fileUrl }} +
+ +
+ Created on + {{ redundancyElement.createdAt | date: 'medium' }} +
+ +
+ Expires on + {{ redundancyElement.expiresOn | date: 'medium' }} +
+ +
+ Size + {{ redundancyElement.size | bytes: 1 }} +
+ +
+ Strategy + {{ redundancyElement.strategy }} +
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.scss b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.scss new file mode 100644 index 000000000..6b09fbb01 --- /dev/null +++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.scss @@ -0,0 +1,8 @@ +@import '_variables'; +@import '_mixins'; + +.label { + display: inline-block; + min-width: 100px; + font-weight: $font-semibold; +} diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.ts b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.ts new file mode 100644 index 000000000..6f3090c08 --- /dev/null +++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.ts @@ -0,0 +1,11 @@ +import { Component, Input } from '@angular/core' +import { FileRedundancyInformation, StreamingPlaylistRedundancyInformation } from '@shared/models' + +@Component({ + selector: 'my-video-redundancy-information', + templateUrl: './video-redundancy-information.component.html', + styleUrls: [ './video-redundancy-information.component.scss' ] +}) +export class VideoRedundancyInformationComponent { + @Input() redundancyElement: FileRedundancyInformation | StreamingPlaylistRedundancyInformation +} diff --git a/client/src/app/+admin/system/jobs/jobs.component.ts b/client/src/app/+admin/system/jobs/jobs.component.ts index 20c8ea71a..bc40452cf 100644 --- a/client/src/app/+admin/system/jobs/jobs.component.ts +++ b/client/src/app/+admin/system/jobs/jobs.component.ts @@ -16,8 +16,8 @@ import { JobTypeClient } from '../../../../types/job-type-client.type' styleUrls: [ './jobs.component.scss' ] }) export class JobsComponent extends RestTable implements OnInit { - private static JOB_STATE_LOCAL_STORAGE_STATE = 'jobs-list-state' - private static JOB_STATE_LOCAL_STORAGE_TYPE = 'jobs-list-type' + private static LOCAL_STORAGE_STATE = 'jobs-list-state' + private static LOCAL_STORAGE_TYPE = 'jobs-list-type' jobState: JobStateClient = 'waiting' jobStates: JobStateClient[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed' ] @@ -34,7 +34,8 @@ export class JobsComponent extends RestTable implements OnInit { 'video-file-import', 'video-import', 'videos-views', - 'activitypub-refresher' + 'activitypub-refresher', + 'video-redundancy' ] jobs: Job[] = [] @@ -77,15 +78,15 @@ export class JobsComponent extends RestTable implements OnInit { } private loadJobStateAndType () { - const state = peertubeLocalStorage.getItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_STATE) + const state = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_STATE) if (state) this.jobState = state as JobState - const type = peertubeLocalStorage.getItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_TYPE) + const type = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_TYPE) if (type) this.jobType = type as JobType } private saveJobStateAndType () { - peertubeLocalStorage.setItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_STATE, this.jobState) - peertubeLocalStorage.setItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_TYPE, this.jobType) + peertubeLocalStorage.setItem(JobsComponent.LOCAL_STORAGE_STATE, this.jobState) + peertubeLocalStorage.setItem(JobsComponent.LOCAL_STORAGE_TYPE, this.jobType) } } -- cgit v1.2.3