mirror of https://github.com/Chocobozzz/PeerTube
Add ability to copy admin logs
parent
5207faeeae
commit
9f57427a79
|
@ -37,8 +37,15 @@
|
||||||
<div *ngIf="loading" i18n>Loading...</div>
|
<div *ngIf="loading" i18n>Loading...</div>
|
||||||
|
|
||||||
<div #logsElement>
|
<div #logsElement>
|
||||||
|
<my-copy-button
|
||||||
|
*ngIf="logs.length !== 0"
|
||||||
|
i18n-notification notification="Logs copied" i18n-title title="Copy logs"
|
||||||
|
withBorder="true" [elementContent]="logsContent"
|
||||||
|
></my-copy-button>
|
||||||
|
|
||||||
<div *ngIf="!loading && logs.length === 0" i18n>No log.</div>
|
<div *ngIf="!loading && logs.length === 0" i18n>No log.</div>
|
||||||
|
|
||||||
|
<div #logsContent>
|
||||||
<div *ngFor="let log of logs" class="log-row" [ngClass]="{ error: log.level === 'error', warn: log.level === 'warn' }">
|
<div *ngFor="let log of logs" class="log-row" [ngClass]="{ error: log.level === 'error', warn: log.level === 'warn' }">
|
||||||
<span class="log-level">{{ log.level }}</span>
|
<span class="log-level">{{ log.level }}</span>
|
||||||
|
|
||||||
|
@ -53,3 +60,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -9,6 +9,10 @@
|
||||||
background: rgba(0, 0, 0, 0.03);
|
background: rgba(0, 0, 0, 0.03);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.log-row {
|
.log-row {
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
@ -81,7 +85,11 @@
|
||||||
&.error {
|
&.error {
|
||||||
color: rgb(250, 5, 5);
|
color: rgb(250, 5, 5);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my-copy-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include on-small-main-col {
|
@include on-small-main-col {
|
||||||
|
|
|
@ -1,28 +1,42 @@
|
||||||
|
import { DatePipe, NgClass, NgFor, NgIf } from '@angular/common'
|
||||||
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
|
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
import { LocalStorageService, Notifier } from '@app/core'
|
import { LocalStorageService, Notifier } from '@app/core'
|
||||||
|
import { NgSelectModule } from '@ng-select/ng-select'
|
||||||
import { ServerLogLevel } from '@peertube/peertube-models'
|
import { ServerLogLevel } from '@peertube/peertube-models'
|
||||||
|
import { SelectTagsComponent } from '../../../shared/shared-forms/select/select-tags.component'
|
||||||
|
import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
|
||||||
|
import { CopyButtonComponent } from '../../../shared/shared-main/buttons/copy-button.component'
|
||||||
import { LogRow } from './log-row.model'
|
import { LogRow } from './log-row.model'
|
||||||
import { LogsService } from './logs.service'
|
import { LogsService } from './logs.service'
|
||||||
import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
|
|
||||||
import { SelectTagsComponent } from '../../../shared/shared-forms/select/select-tags.component'
|
|
||||||
import { NgSelectModule } from '@ng-select/ng-select'
|
|
||||||
import { NgFor, NgIf, NgClass, DatePipe } from '@angular/common'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './logs.component.html',
|
templateUrl: './logs.component.html',
|
||||||
styleUrls: [ './logs.component.scss' ],
|
styleUrls: [ './logs.component.scss' ],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ FormsModule, NgFor, NgSelectModule, NgIf, NgClass, SelectTagsComponent, ButtonComponent, DatePipe ]
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
NgFor,
|
||||||
|
NgSelectModule,
|
||||||
|
NgIf,
|
||||||
|
NgClass,
|
||||||
|
SelectTagsComponent,
|
||||||
|
ButtonComponent,
|
||||||
|
DatePipe,
|
||||||
|
CopyButtonComponent
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class LogsComponent implements OnInit {
|
export class LogsComponent implements OnInit {
|
||||||
private static LOCAL_STORAGE_LOG_TYPE_CHOICE_KEY = 'admin-logs-log-type-choice'
|
private static LOCAL_STORAGE_LOG_TYPE_CHOICE_KEY = 'admin-logs-log-type-choice'
|
||||||
|
|
||||||
@ViewChild('logsElement', { static: true }) logsElement: ElementRef<HTMLElement>
|
@ViewChild('logsElement', { static: true }) logsElement: ElementRef<HTMLElement>
|
||||||
|
@ViewChild('logsContent', { static: true }) logsContent: ElementRef<HTMLElement>
|
||||||
|
|
||||||
loading = false
|
loading = false
|
||||||
|
|
||||||
|
rawLogs: string
|
||||||
logs: LogRow[] = []
|
logs: LogRow[] = []
|
||||||
|
|
||||||
timeChoices: { id: string, label: string, dateFormat: string }[] = []
|
timeChoices: { id: string, label: string, dateFormat: string }[] = []
|
||||||
levelChoices: { id: ServerLogLevel, label: string }[] = []
|
levelChoices: { id: ServerLogLevel, label: string }[] = []
|
||||||
logTypeChoices: { id: 'audit' | 'standard', label: string }[] = []
|
logTypeChoices: { id: 'audit' | 'standard', label: string }[] = []
|
||||||
|
@ -72,6 +86,8 @@ export class LogsComponent implements OnInit {
|
||||||
next: logs => {
|
next: logs => {
|
||||||
this.logs = logs
|
this.logs = logs
|
||||||
|
|
||||||
|
this.rawLogs = this.logs.map(l => `${l.level} ${l.localeDate} ${l.message} ${l.meta}`).join('\n')
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.logsElement.nativeElement.scrollIntoView({ block: 'end', inline: 'nearest' })
|
this.logsElement.nativeElement.scrollIntoView({ block: 'end', inline: 'nearest' })
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { Observable } from 'rxjs'
|
|
||||||
import { catchError, map } from 'rxjs/operators'
|
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http'
|
import { HttpClient, HttpParams } from '@angular/common/http'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { RestExtractor, RestService } from '@app/core'
|
import { RestExtractor, RestService } from '@app/core'
|
||||||
import { ServerLogLevel } from '@peertube/peertube-models'
|
import { ServerLogLevel } from '@peertube/peertube-models'
|
||||||
|
import { catchError, map } from 'rxjs/operators'
|
||||||
import { environment } from '../../../../environments/environment'
|
import { environment } from '../../../../environments/environment'
|
||||||
import { LogRow } from './log-row.model'
|
import { LogRow } from './log-row.model'
|
||||||
|
|
||||||
|
@ -24,7 +23,7 @@ export class LogsService {
|
||||||
tagsOneOf?: string[]
|
tagsOneOf?: string[]
|
||||||
level?: ServerLogLevel
|
level?: ServerLogLevel
|
||||||
endDate?: string
|
endDate?: string
|
||||||
}): Observable<any[]> {
|
}) {
|
||||||
const { isAuditLog, startDate, endDate, tagsOneOf } = options
|
const { isAuditLog, startDate, endDate, tagsOneOf } = options
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
|
@ -38,7 +37,7 @@ export class LogsService {
|
||||||
? LogsService.BASE_AUDIT_LOG_URL
|
? LogsService.BASE_AUDIT_LOG_URL
|
||||||
: LogsService.BASE_LOG_URL
|
: LogsService.BASE_LOG_URL
|
||||||
|
|
||||||
return this.authHttp.get<any[]>(path, { params })
|
return this.authHttp.get<LogRow[]>(path, { params })
|
||||||
.pipe(
|
.pipe(
|
||||||
map(rows => rows.map(r => new LogRow(r))),
|
map(rows => rows.map(r => new LogRow(r))),
|
||||||
catchError(err => this.restExtractor.handleError(err))
|
catchError(err => this.restExtractor.handleError(err))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline-secondary btn-sm copy-button"
|
class="btn btn-outline-secondary btn-sm copy-button"
|
||||||
[cdkCopyToClipboard]="value" (click)="activateCopiedMessage()"
|
(click)="copy()"
|
||||||
[title]="title" [ngClass]="{ 'is-input-group': isInputGroup }"
|
[title]="title" [ngClass]="{ 'is-input-group': isInputGroup, 'with-border': withBorder }"
|
||||||
>
|
>
|
||||||
<my-global-icon iconName="copy"></my-global-icon>
|
<my-global-icon iconName="copy"></my-global-icon>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
@use '_variables' as *;
|
@use '_variables' as *;
|
||||||
@use '_mixins' as *;
|
@use '_mixins' as *;
|
||||||
|
|
||||||
button:not(.is-input-group) {
|
button:not(.with-border, .is-input-group) {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,35 @@
|
||||||
import { Component, Input } from '@angular/core'
|
import { Clipboard } from '@angular/cdk/clipboard'
|
||||||
|
import { NgClass } from '@angular/common'
|
||||||
|
import { booleanAttribute, Component, Input } from '@angular/core'
|
||||||
import { Notifier } from '@app/core'
|
import { Notifier } from '@app/core'
|
||||||
import { GlobalIconComponent } from '../../shared-icons/global-icon.component'
|
import { GlobalIconComponent } from '../../shared-icons/global-icon.component'
|
||||||
import { NgClass } from '@angular/common'
|
|
||||||
import { CdkCopyToClipboard } from '@angular/cdk/clipboard'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-copy-button',
|
selector: 'my-copy-button',
|
||||||
styleUrls: [ './copy-button.component.scss' ],
|
styleUrls: [ './copy-button.component.scss' ],
|
||||||
templateUrl: './copy-button.component.html',
|
templateUrl: './copy-button.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ CdkCopyToClipboard, NgClass, GlobalIconComponent ]
|
providers: [ Clipboard ],
|
||||||
|
imports: [ NgClass, GlobalIconComponent ]
|
||||||
})
|
})
|
||||||
export class CopyButtonComponent {
|
export class CopyButtonComponent {
|
||||||
@Input() value: string
|
@Input() value: string
|
||||||
|
@Input() elementContent: HTMLElement
|
||||||
|
|
||||||
@Input() title: string
|
@Input() title: string
|
||||||
@Input() notification: string
|
@Input() notification: string
|
||||||
@Input() isInputGroup = false
|
|
||||||
|
|
||||||
constructor (private notifier: Notifier) {
|
@Input({ transform: booleanAttribute }) withBorder = false
|
||||||
|
@Input({ transform: booleanAttribute }) isInputGroup = false
|
||||||
|
|
||||||
|
constructor (private notifier: Notifier, private clipboard: Clipboard) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activateCopiedMessage () {
|
copy () {
|
||||||
|
this.clipboard.copy(this.value || this.elementContent?.innerText)
|
||||||
|
|
||||||
if (this.notification) this.notifier.success(this.notification)
|
if (this.notification) this.notifier.success(this.notification)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue