aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/+admin/admin-routing.module.ts4
-rw-r--r--client/src/app/+admin/admin.component.html8
-rw-r--r--client/src/app/+admin/admin.component.ts4
-rw-r--r--client/src/app/+admin/admin.module.ts15
-rw-r--r--client/src/app/+admin/system/index.ts4
-rw-r--r--client/src/app/+admin/system/jobs/index.ts6
-rw-r--r--client/src/app/+admin/system/jobs/jobs.component.ts14
-rw-r--r--client/src/app/+admin/system/logs/index.ts2
-rw-r--r--client/src/app/+admin/system/logs/log-row.model.ts21
-rw-r--r--client/src/app/+admin/system/logs/logs.component.html31
-rw-r--r--client/src/app/+admin/system/logs/logs.component.scss48
-rw-r--r--client/src/app/+admin/system/logs/logs.component.ts111
-rw-r--r--client/src/app/+admin/system/logs/logs.service.ts33
-rw-r--r--client/src/app/+admin/system/system.component.html11
-rw-r--r--client/src/app/+admin/system/system.component.scss4
-rw-r--r--client/src/app/+admin/system/system.component.ts8
-rw-r--r--client/src/app/+admin/system/system.routes.ts44
-rw-r--r--client/src/app/shared/images/global-icon.component.ts3
18 files changed, 346 insertions, 25 deletions
diff --git a/client/src/app/+admin/admin-routing.module.ts b/client/src/app/+admin/admin-routing.module.ts
index ca31ba585..215da1e4f 100644
--- a/client/src/app/+admin/admin-routing.module.ts
+++ b/client/src/app/+admin/admin-routing.module.ts
@@ -6,9 +6,9 @@ import { MetaGuard } from '@ngx-meta/core'
6 6
7import { AdminComponent } from './admin.component' 7import { AdminComponent } from './admin.component'
8import { FollowsRoutes } from './follows' 8import { FollowsRoutes } from './follows'
9import { JobsRoutes } from './jobs/job.routes'
10import { UsersRoutes } from './users' 9import { UsersRoutes } from './users'
11import { ModerationRoutes } from '@app/+admin/moderation/moderation.routes' 10import { ModerationRoutes } from '@app/+admin/moderation/moderation.routes'
11import { SystemRoutes } from '@app/+admin/system'
12 12
13const adminRoutes: Routes = [ 13const adminRoutes: Routes = [
14 { 14 {
@@ -25,7 +25,7 @@ const adminRoutes: Routes = [
25 ...FollowsRoutes, 25 ...FollowsRoutes,
26 ...UsersRoutes, 26 ...UsersRoutes,
27 ...ModerationRoutes, 27 ...ModerationRoutes,
28 ...JobsRoutes, 28 ...SystemRoutes,
29 ...ConfigRoutes 29 ...ConfigRoutes
30 ] 30 ]
31 } 31 }
diff --git a/client/src/app/+admin/admin.component.html b/client/src/app/+admin/admin.component.html
index 345fb9f5a..065d92509 100644
--- a/client/src/app/+admin/admin.component.html
+++ b/client/src/app/+admin/admin.component.html
@@ -12,13 +12,13 @@
12 Moderation 12 Moderation
13 </a> 13 </a>
14 14
15 <a i18n *ngIf="hasJobsRight()" routerLink="/admin/jobs" routerLinkActive="active" class="title-page">
16 Jobs
17 </a>
18
19 <a i18n *ngIf="hasConfigRight()" routerLink="/admin/config" routerLinkActive="active" class="title-page"> 15 <a i18n *ngIf="hasConfigRight()" routerLink="/admin/config" routerLinkActive="active" class="title-page">
20 Configuration 16 Configuration
21 </a> 17 </a>
18
19 <a i18n *ngIf="hasJobsRight() || hasLogsRight()" routerLink="/admin/system" routerLinkActive="active" class="title-page">
20 System
21 </a>
22 </div> 22 </div>
23 23
24 <div class="margin-content"> 24 <div class="margin-content">
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts
index b4b807c67..fc775a5a4 100644
--- a/client/src/app/+admin/admin.component.ts
+++ b/client/src/app/+admin/admin.component.ts
@@ -28,6 +28,10 @@ export class AdminComponent {
28 return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS) 28 return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS)
29 } 29 }
30 30
31 hasLogsRight () {
32 return this.auth.getUser().hasRight(UserRight.MANAGE_LOGS)
33 }
34
31 hasConfigRight () { 35 hasConfigRight () {
32 return this.auth.getUser().hasRight(UserRight.MANAGE_CONFIGURATION) 36 return this.auth.getUser().hasRight(UserRight.MANAGE_CONFIGURATION)
33 } 37 }
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index 282d59634..ae0af686b 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -7,20 +7,19 @@ import { AdminRoutingModule } from './admin-routing.module'
7import { AdminComponent } from './admin.component' 7import { AdminComponent } from './admin.component'
8import { FollowersListComponent, FollowingAddComponent, FollowsComponent, FollowService } from './follows' 8import { FollowersListComponent, FollowingAddComponent, FollowsComponent, FollowService } from './follows'
9import { FollowingListComponent } from './follows/following-list/following-list.component' 9import { FollowingListComponent } from './follows/following-list/following-list.component'
10import { JobsComponent } from './jobs/job.component' 10import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersComponent, UserUpdateComponent } from './users'
11import { JobsListComponent } from './jobs/jobs-list/jobs-list.component'
12import { JobService } from './jobs/shared/job.service'
13import { UserCreateComponent, UserListComponent, UsersComponent, UserUpdateComponent, UserPasswordComponent } from './users'
14import { 11import {
15 ModerationCommentModalComponent, 12 ModerationCommentModalComponent,
16 VideoAbuseListComponent, 13 VideoAbuseListComponent,
17 VideoBlacklistListComponent, 14 VideoAutoBlacklistListComponent,
18 VideoAutoBlacklistListComponent 15 VideoBlacklistListComponent
19} from './moderation' 16} from './moderation'
20import { ModerationComponent } from '@app/+admin/moderation/moderation.component' 17import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
21import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component' 18import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component'
22import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' 19import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service'
23import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' 20import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist'
21import { JobsComponent } from '@app/+admin/system/jobs/jobs.component'
22import { JobService, LogsComponent, LogsService, SystemComponent } from '@app/+admin/system'
24 23
25@NgModule({ 24@NgModule({
26 imports: [ 25 imports: [
@@ -52,8 +51,9 @@ import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } f
52 InstanceServerBlocklistComponent, 51 InstanceServerBlocklistComponent,
53 InstanceAccountBlocklistComponent, 52 InstanceAccountBlocklistComponent,
54 53
54 SystemComponent,
55 JobsComponent, 55 JobsComponent,
56 JobsListComponent, 56 LogsComponent,
57 57
58 ConfigComponent, 58 ConfigComponent,
59 EditCustomConfigComponent 59 EditCustomConfigComponent
@@ -67,6 +67,7 @@ import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } f
67 FollowService, 67 FollowService,
68 RedundancyService, 68 RedundancyService,
69 JobService, 69 JobService,
70 LogsService,
70 ConfigService 71 ConfigService
71 ] 72 ]
72}) 73})
diff --git a/client/src/app/+admin/system/index.ts b/client/src/app/+admin/system/index.ts
new file mode 100644
index 000000000..226d999d2
--- /dev/null
+++ b/client/src/app/+admin/system/index.ts
@@ -0,0 +1,4 @@
1export * from './jobs'
2export * from './logs'
3export * from './system.component'
4export * from './system.routes'
diff --git a/client/src/app/+admin/system/jobs/index.ts b/client/src/app/+admin/system/jobs/index.ts
index c0e0cc95d..486a745e4 100644
--- a/client/src/app/+admin/system/jobs/index.ts
+++ b/client/src/app/+admin/system/jobs/index.ts
@@ -1,4 +1,2 @@
1export * from './shared' 1export * from './job.service'
2export * from './jobs-list' 2export * from './jobs.component'
3export * from './job.routes'
4export * from './job.component'
diff --git a/client/src/app/+admin/system/jobs/jobs.component.ts b/client/src/app/+admin/system/jobs/jobs.component.ts
index b265e1dd6..ebfb52779 100644
--- a/client/src/app/+admin/system/jobs/jobs.component.ts
+++ b/client/src/app/+admin/system/jobs/jobs.component.ts
@@ -5,15 +5,15 @@ import { SortMeta } from 'primeng/primeng'
5import { Job } from '../../../../../../shared/index' 5import { Job } from '../../../../../../shared/index'
6import { JobState } from '../../../../../../shared/models' 6import { JobState } from '../../../../../../shared/models'
7import { RestPagination, RestTable } from '../../../shared' 7import { RestPagination, RestTable } from '../../../shared'
8import { JobService } from '../shared' 8import { JobService } from './job.service'
9import { I18n } from '@ngx-translate/i18n-polyfill' 9import { I18n } from '@ngx-translate/i18n-polyfill'
10 10
11@Component({ 11@Component({
12 selector: 'my-jobs-list', 12 selector: 'my-jobs',
13 templateUrl: './jobs-list.component.html', 13 templateUrl: './jobs.component.html',
14 styleUrls: [ './jobs-list.component.scss' ] 14 styleUrls: [ './jobs.component.scss' ]
15}) 15})
16export class JobsListComponent extends RestTable implements OnInit { 16export class JobsComponent extends RestTable implements OnInit {
17 private static JOB_STATE_LOCAL_STORAGE_STATE = 'jobs-list-state' 17 private static JOB_STATE_LOCAL_STORAGE_STATE = 'jobs-list-state'
18 18
19 jobState: JobState = 'waiting' 19 jobState: JobState = 'waiting'
@@ -58,12 +58,12 @@ export class JobsListComponent extends RestTable implements OnInit {
58 } 58 }
59 59
60 private loadJobState () { 60 private loadJobState () {
61 const result = peertubeLocalStorage.getItem(JobsListComponent.JOB_STATE_LOCAL_STORAGE_STATE) 61 const result = peertubeLocalStorage.getItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_STATE)
62 62
63 if (result) this.jobState = result as JobState 63 if (result) this.jobState = result as JobState
64 } 64 }
65 65
66 private saveJobState () { 66 private saveJobState () {
67 peertubeLocalStorage.setItem(JobsListComponent.JOB_STATE_LOCAL_STORAGE_STATE, this.jobState) 67 peertubeLocalStorage.setItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_STATE, this.jobState)
68 } 68 }
69} 69}
diff --git a/client/src/app/+admin/system/logs/index.ts b/client/src/app/+admin/system/logs/index.ts
new file mode 100644
index 000000000..7b56d4237
--- /dev/null
+++ b/client/src/app/+admin/system/logs/index.ts
@@ -0,0 +1,2 @@
1export * from './logs.component'
2export * from './logs.service'
diff --git a/client/src/app/+admin/system/logs/log-row.model.ts b/client/src/app/+admin/system/logs/log-row.model.ts
new file mode 100644
index 000000000..9bc7dafdd
--- /dev/null
+++ b/client/src/app/+admin/system/logs/log-row.model.ts
@@ -0,0 +1,21 @@
1import { LogLevel } from '@shared/models/server/log-level.type'
2import omit from 'lodash-es/omit'
3
4export class LogRow {
5 date: Date
6 localeDate: string
7 level: LogLevel
8 message: string
9 meta: string
10
11 constructor (row: any) {
12 this.date = new Date(row.timestamp)
13 this.localeDate = this.date.toLocaleString()
14 this.level = row.level
15 this.message = row.message
16
17 const metaObj = omit(row, 'timestamp', 'level', 'message', 'label')
18
19 if (Object.keys(metaObj).length !== 0) this.meta = JSON.stringify(metaObj, undefined, 2)
20 }
21}
diff --git a/client/src/app/+admin/system/logs/logs.component.html b/client/src/app/+admin/system/logs/logs.component.html
new file mode 100644
index 000000000..45723a655
--- /dev/null
+++ b/client/src/app/+admin/system/logs/logs.component.html
@@ -0,0 +1,31 @@
1<div class="header">
2 <div class="peertube-select-container">
3 <select [(ngModel)]="startDate" (ngModelChange)="refresh()">
4 <option *ngFor="let timeChoice of timeChoices" [value]="timeChoice.id">{{ timeChoice.label }}</option>
5 </select>
6 </div>
7
8 <div class="peertube-select-container">
9 <select [(ngModel)]="level" (ngModelChange)="refresh()">
10 <option *ngFor="let levelChoice of levelChoices" [value]="levelChoice.id">{{ levelChoice.label }}</option>
11 </select>
12 </div>
13
14 <my-button i18n-label label="Refresh" icon="refresh" (click)="refresh()"></my-button>
15</div>
16
17<div class="logs">
18 <div *ngIf="loading">Loading...</div>
19
20 <div #logsElement>
21 <div *ngFor="let log of logs" class="log-row" [ngClass]="{ error: log.level === 'error', warn: log.level === 'warn' }">
22 <span class="log-level">{{ log.level }}</span>
23
24 <span class="log-date">[{{ log.localeDate }}]</span>
25
26 {{ log.message }}
27
28 {{ log.meta }}
29 </div>
30 </div>
31</div>
diff --git a/client/src/app/+admin/system/logs/logs.component.scss b/client/src/app/+admin/system/logs/logs.component.scss
new file mode 100644
index 000000000..ab00fb5ae
--- /dev/null
+++ b/client/src/app/+admin/system/logs/logs.component.scss
@@ -0,0 +1,48 @@
1@import '_variables';
2@import '_mixins';
3
4.logs {
5 font-family: monospace;
6 font-size: 13px;
7 max-height: 500px;
8 overflow-y: auto;
9 background: rgba(0, 0, 0, 0.03);
10 padding: 20px;
11
12 .log-row {
13 margin-top: 1px;
14
15 &:hover {
16 background: rgba(0, 0, 0, 0.07);
17 }
18 }
19
20 .log-level {
21 font-weight: $font-semibold;
22 margin-right: 5px;
23 }
24
25 .warn {
26 color: $orange-color;
27 }
28
29 .error {
30 color: $red;
31 }
32}
33
34.header {
35 display: flex;
36 justify-content: flex-end;
37 margin-bottom: 10px;
38
39 .peertube-select-container {
40 @include peertube-select-container(150px);
41 }
42
43 my-button,
44 .peertube-select-container {
45 margin-left: 10px;
46 }
47}
48
diff --git a/client/src/app/+admin/system/logs/logs.component.ts b/client/src/app/+admin/system/logs/logs.component.ts
new file mode 100644
index 000000000..17abb8409
--- /dev/null
+++ b/client/src/app/+admin/system/logs/logs.component.ts
@@ -0,0 +1,111 @@
1import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
2import { LogsService } from '@app/+admin/system/logs/logs.service'
3import { Notifier } from '@app/core'
4import { LogRow } from '@app/+admin/system/logs/log-row.model'
5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { LogLevel } from '@shared/models/server/log-level.type'
7
8@Component({
9 templateUrl: './logs.component.html',
10 styleUrls: [ './logs.component.scss' ]
11})
12export class LogsComponent implements OnInit {
13 @ViewChild('logsElement') logsElement: ElementRef<HTMLElement>
14
15 loading = false
16
17 logs: LogRow[] = []
18 timeChoices: { id: string, label: string }[] = []
19 levelChoices: { id: LogLevel, label: string }[] = []
20
21 startDate: string
22 level: LogLevel
23
24 constructor (
25 private logsService: LogsService,
26 private notifier: Notifier,
27 private i18n: I18n
28 ) { }
29
30 ngOnInit (): void {
31 this.buildTimeChoices()
32 this.buildLevelChoices()
33
34 this.load()
35 }
36
37 refresh () {
38 this.logs = []
39 this.load()
40 }
41
42 load () {
43 this.loading = true
44
45 this.logsService.getLogs(this.level, this.startDate)
46 .subscribe(
47 logs => {
48 this.logs = logs
49
50 setTimeout(() => {
51 this.logsElement.nativeElement.scrollIntoView({ block: 'end', inline: 'nearest' })
52 })
53 },
54
55 err => this.notifier.error(err.message),
56
57 () => this.loading = false
58 )
59 }
60
61 buildTimeChoices () {
62 const lastHour = new Date()
63 lastHour.setHours(lastHour.getHours() - 1)
64
65 const lastDay = new Date()
66 lastDay.setDate(lastDay.getDate() - 1)
67
68 const lastWeek = new Date()
69 lastWeek.setDate(lastWeek.getDate() - 7)
70
71 this.timeChoices = [
72 {
73 id: lastWeek.toISOString(),
74 label: this.i18n('Last week')
75 },
76 {
77 id: lastDay.toISOString(),
78 label: this.i18n('Last day')
79 },
80 {
81 id: lastHour.toISOString(),
82 label: this.i18n('Last hour')
83 }
84 ]
85
86 this.startDate = lastHour.toISOString()
87 }
88
89 buildLevelChoices () {
90 this.levelChoices = [
91 {
92 id: 'debug',
93 label: this.i18n('Debug')
94 },
95 {
96 id: 'info',
97 label: this.i18n('Info')
98 },
99 {
100 id: 'warn',
101 label: this.i18n('Warning')
102 },
103 {
104 id: 'error',
105 label: this.i18n('Error')
106 }
107 ]
108
109 this.level = 'info'
110 }
111}
diff --git a/client/src/app/+admin/system/logs/logs.service.ts b/client/src/app/+admin/system/logs/logs.service.ts
new file mode 100644
index 000000000..4db79a1fa
--- /dev/null
+++ b/client/src/app/+admin/system/logs/logs.service.ts
@@ -0,0 +1,33 @@
1import { catchError, map } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { Observable } from 'rxjs'
5import { environment } from '../../../../environments/environment'
6import { RestExtractor, RestService } from '../../../shared'
7import { LogRow } from '@app/+admin/system/logs/log-row.model'
8import { LogLevel } from '@shared/models/server/log-level.type'
9
10@Injectable()
11export class LogsService {
12 private static BASE_JOB_URL = environment.apiUrl + '/api/v1/server/logs'
13
14 constructor (
15 private authHttp: HttpClient,
16 private restService: RestService,
17 private restExtractor: RestExtractor
18 ) {}
19
20 getLogs (level: LogLevel, startDate: string, endDate?: string): Observable<any> {
21 let params = new HttpParams()
22 params = params.append('startDate', startDate)
23 params = params.append('level', level)
24
25 if (endDate) params.append('endDate', endDate)
26
27 return this.authHttp.get<any[]>(LogsService.BASE_JOB_URL, { params })
28 .pipe(
29 map(rows => rows.map(r => new LogRow(r))),
30 catchError(err => this.restExtractor.handleError(err))
31 )
32 }
33}
diff --git a/client/src/app/+admin/system/system.component.html b/client/src/app/+admin/system/system.component.html
new file mode 100644
index 000000000..345a101e6
--- /dev/null
+++ b/client/src/app/+admin/system/system.component.html
@@ -0,0 +1,11 @@
1<div class="admin-sub-header">
2 <div i18n class="form-sub-title">System</div>
3
4 <div class="admin-sub-nav">
5 <a i18n routerLink="jobs" routerLinkActive="active">Jobs</a>
6
7 <a i18n routerLink="logs" routerLinkActive="active">Logs</a>
8 </div>
9</div>
10
11<router-outlet></router-outlet>
diff --git a/client/src/app/+admin/system/system.component.scss b/client/src/app/+admin/system/system.component.scss
new file mode 100644
index 000000000..766d7853b
--- /dev/null
+++ b/client/src/app/+admin/system/system.component.scss
@@ -0,0 +1,4 @@
1.form-sub-title {
2 flex-grow: 0;
3 margin-right: 30px;
4}
diff --git a/client/src/app/+admin/system/system.component.ts b/client/src/app/+admin/system/system.component.ts
new file mode 100644
index 000000000..992d9c8af
--- /dev/null
+++ b/client/src/app/+admin/system/system.component.ts
@@ -0,0 +1,8 @@
1import { Component } from '@angular/core'
2
3@Component({
4 templateUrl: './system.component.html',
5 styleUrls: [ './system.component.scss' ]
6})
7export class SystemComponent {
8}
diff --git a/client/src/app/+admin/system/system.routes.ts b/client/src/app/+admin/system/system.routes.ts
new file mode 100644
index 000000000..e6d45b760
--- /dev/null
+++ b/client/src/app/+admin/system/system.routes.ts
@@ -0,0 +1,44 @@
1import { Routes } from '@angular/router'
2import { UserRightGuard } from '../../core'
3import { UserRight } from '../../../../../shared'
4import { JobsComponent } from '@app/+admin/system/jobs/jobs.component'
5import { LogsComponent } from '@app/+admin/system/logs'
6import { SystemComponent } from '@app/+admin/system/system.component'
7
8export const SystemRoutes: Routes = [
9 {
10 path: 'system',
11 component: SystemComponent,
12 data: {
13 },
14 children: [
15 {
16 path: '',
17 redirectTo: 'jobs',
18 pathMatch: 'full'
19 },
20 {
21 path: 'jobs',
22 canActivate: [ UserRightGuard ],
23 component: JobsComponent,
24 data: {
25 meta: {
26 userRight: UserRight.MANAGE_JOBS,
27 title: 'Jobs'
28 }
29 }
30 },
31 {
32 path: 'logs',
33 canActivate: [ UserRightGuard ],
34 component: LogsComponent,
35 data: {
36 meta: {
37 userRight: UserRight.MANAGE_LOGS,
38 title: 'Logs'
39 }
40 }
41 }
42 ]
43 }
44]
diff --git a/client/src/app/shared/images/global-icon.component.ts b/client/src/app/shared/images/global-icon.component.ts
index bd5b11bb0..03cf3d7ae 100644
--- a/client/src/app/shared/images/global-icon.component.ts
+++ b/client/src/app/shared/images/global-icon.component.ts
@@ -44,7 +44,8 @@ const icons = {
44 'folder': require('../../../assets/images/global/folder.html'), 44 'folder': require('../../../assets/images/global/folder.html'),
45 'administration': require('../../../assets/images/menu/administration.html'), 45 'administration': require('../../../assets/images/menu/administration.html'),
46 'subscriptions': require('../../../assets/images/menu/subscriptions.html'), 46 'subscriptions': require('../../../assets/images/menu/subscriptions.html'),
47 'users': require('../../../assets/images/global/users.html') 47 'users': require('../../../assets/images/global/users.html'),
48 'refresh': require('../../../assets/images/global/refresh.html')
48} 49}
49 50
50export type GlobalIconName = keyof typeof icons 51export type GlobalIconName = keyof typeof icons