Add ability to copy admin logs

pull/6544/head
Chocobozzz 2024-08-08 11:44:30 +02:00
parent 5207faeeae
commit 9f57427a79
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
7 changed files with 66 additions and 27 deletions

View File

@ -37,8 +37,15 @@
<div *ngIf="loading" i18n>Loading...</div>
<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 #logsContent>
<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>
@ -52,4 +59,5 @@
<pre>{{ log.meta }}</pre>
</div>
</div>
</div>
</div>

View File

@ -9,6 +9,10 @@
background: rgba(0, 0, 0, 0.03);
padding: 20px;
> div {
position: relative;
}
.log-row {
margin-top: 1px;
word-break: break-word;
@ -81,7 +85,11 @@
&.error {
color: rgb(250, 5, 5);
}
}
my-copy-button {
position: absolute;
right: 5px;
}
@include on-small-main-col {

View File

@ -1,28 +1,42 @@
import { DatePipe, NgClass, NgFor, NgIf } from '@angular/common'
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { LocalStorageService, Notifier } from '@app/core'
import { NgSelectModule } from '@ng-select/ng-select'
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 { 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({
templateUrl: './logs.component.html',
styleUrls: [ './logs.component.scss' ],
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 {
private static LOCAL_STORAGE_LOG_TYPE_CHOICE_KEY = 'admin-logs-log-type-choice'
@ViewChild('logsElement', { static: true }) logsElement: ElementRef<HTMLElement>
@ViewChild('logsContent', { static: true }) logsContent: ElementRef<HTMLElement>
loading = false
rawLogs: string
logs: LogRow[] = []
timeChoices: { id: string, label: string, dateFormat: string }[] = []
levelChoices: { id: ServerLogLevel, label: string }[] = []
logTypeChoices: { id: 'audit' | 'standard', label: string }[] = []
@ -72,6 +86,8 @@ export class LogsComponent implements OnInit {
next: logs => {
this.logs = logs
this.rawLogs = this.logs.map(l => `${l.level} ${l.localeDate} ${l.message} ${l.meta}`).join('\n')
setTimeout(() => {
this.logsElement.nativeElement.scrollIntoView({ block: 'end', inline: 'nearest' })
})

View File

@ -1,9 +1,8 @@
import { Observable } from 'rxjs'
import { catchError, map } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { RestExtractor, RestService } from '@app/core'
import { ServerLogLevel } from '@peertube/peertube-models'
import { catchError, map } from 'rxjs/operators'
import { environment } from '../../../../environments/environment'
import { LogRow } from './log-row.model'
@ -24,7 +23,7 @@ export class LogsService {
tagsOneOf?: string[]
level?: ServerLogLevel
endDate?: string
}): Observable<any[]> {
}) {
const { isAuditLog, startDate, endDate, tagsOneOf } = options
let params = new HttpParams()
@ -38,7 +37,7 @@ export class LogsService {
? LogsService.BASE_AUDIT_LOG_URL
: LogsService.BASE_LOG_URL
return this.authHttp.get<any[]>(path, { params })
return this.authHttp.get<LogRow[]>(path, { params })
.pipe(
map(rows => rows.map(r => new LogRow(r))),
catchError(err => this.restExtractor.handleError(err))

View File

@ -1,7 +1,7 @@
<button
class="btn btn-outline-secondary btn-sm copy-button"
[cdkCopyToClipboard]="value" (click)="activateCopiedMessage()"
[title]="title" [ngClass]="{ 'is-input-group': isInputGroup }"
(click)="copy()"
[title]="title" [ngClass]="{ 'is-input-group': isInputGroup, 'with-border': withBorder }"
>
<my-global-icon iconName="copy"></my-global-icon>

View File

@ -1,7 +1,7 @@
@use '_variables' as *;
@use '_mixins' as *;
button:not(.is-input-group) {
button:not(.with-border, .is-input-group) {
border: 0;
}

View File

@ -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 { GlobalIconComponent } from '../../shared-icons/global-icon.component'
import { NgClass } from '@angular/common'
import { CdkCopyToClipboard } from '@angular/cdk/clipboard'
@Component({
selector: 'my-copy-button',
styleUrls: [ './copy-button.component.scss' ],
templateUrl: './copy-button.component.html',
standalone: true,
imports: [ CdkCopyToClipboard, NgClass, GlobalIconComponent ]
providers: [ Clipboard ],
imports: [ NgClass, GlobalIconComponent ]
})
export class CopyButtonComponent {
@Input() value: string
@Input() elementContent: HTMLElement
@Input() title: 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)
}
}