aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+admin/system
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-04-11 10:05:43 +0200
committerChocobozzz <me@florianbigard.com>2019-04-11 10:14:08 +0200
commit2c22613c2fe6f7f9c8c7de66e42be54b27cc7edd (patch)
tree4692ec7ef34bf418a47624704006e61200e174df /client/src/app/+admin/system
parentfd8710b897a67518d3a61c319e54b6a65ba443ef (diff)
downloadPeerTube-2c22613c2fe6f7f9c8c7de66e42be54b27cc7edd.tar.gz
PeerTube-2c22613c2fe6f7f9c8c7de66e42be54b27cc7edd.tar.zst
PeerTube-2c22613c2fe6f7f9c8c7de66e42be54b27cc7edd.zip
Add logs page in client
Diffstat (limited to 'client/src/app/+admin/system')
-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
13 files changed, 326 insertions, 11 deletions
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]