From 60684d2c5018f4740dc505850e13f885dee92a69 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Thu, 8 May 2025 13:59:24 +0200 Subject: [PATCH] Implement ClientTaskLogs component with enhanced UI and functionality --- .../client-task-logs.component.css | 124 +++++++++ .../client-task-logs.component.html | 162 +++++++++++- .../client-task-logs.component.ts | 241 +++++++++++++++++- 3 files changed, 518 insertions(+), 9 deletions(-) diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css index e69de29..fa2c0ba 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css @@ -0,0 +1,124 @@ +.modal-content { + max-height: 85vh; + overflow-y: auto; + padding: 1rem; +} + +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 10px; + border-bottom: 1px solid #ddd; +} + +.header-container-title { + flex-grow: 1; + text-align: left; + margin-left: 1em; +} + +.calendar-button-row { + display: flex; + gap: 15px; +} + +.lists-container { + padding: 16px; +} + +.imagesLists-container { + flex: 1; +} + +.card.unidad-card { + height: 100%; + box-sizing: border-box; +} + +table { + width: 100%; +} + +.search-container { + display: flex; + justify-content: space-between; + align-items: center; + margin: 1.5rem 0rem 1.5rem 0rem; + box-sizing: border-box; +} + +.search-string { + flex: 1; + padding: 5px; +} + +.search-boolean { + flex: 1; + padding: 5px; +} + +.search-select { + flex: 2; + padding: 5px; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); +} + +.progress-container { + display: flex; + align-items: center; + gap: 10px; +} + +.paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} + +.chip-failed { + background-color: #e87979 !important; + color: white; +} + +.chip-success { + background-color: #46c446 !important; + color: white; +} + +.chip-pending { + background-color: #bebdbd !important; + color: black; +} + +.chip-in-progress { + background-color: #f5a623 !important; + color: white; +} + +.status-progress-flex { + display: flex; + align-items: center; + gap: 8px; +} + +button.cancel-button { + display: flex; + align-items: center; + justify-content: center; + padding: 5px; +} + +.cancel-button { + color: red; + background-color: transparent; + border: none; + padding: 0; +} + +.cancel-button mat-icon { + color: red; +} diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html index 651b9f4..b21a2c3 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html @@ -1 +1,161 @@ -

client-task-logs works!

+ \ No newline at end of file diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts index 3502328..397247c 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts @@ -1,7 +1,19 @@ -import { Component, Inject, OnInit } from '@angular/core'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; import { HttpClient, HttpParams } from '@angular/common/http'; import { ConfigService } from '@services/config.service'; +import { DatePipe } from '@angular/common'; +import { DeleteModalComponent } from 'src/app/shared/delete_modal/delete-modal/delete-modal.component'; +import { ToastrService } from 'ngx-toastr'; +import { TranslationService } from '@services/translation.service'; +import { Observable } from 'rxjs'; +import { FormControl } from '@angular/forms'; +import { OutputDialogComponent } from '../output-dialog/output-dialog.component'; +import { InputDialogComponent } from '../input-dialog/input-dialog.component'; +import { ProgressBarMode } from '@angular/material/progress-bar'; +import { JoyrideService } from 'ngx-joyride'; +import { map, startWith } from 'rxjs/operators'; +import { COMMAND_TYPES } from 'src/app/shared/constants/command-types'; @Component({ selector: 'app-client-task-logs', @@ -9,24 +21,161 @@ import { ConfigService } from '@services/config.service'; styleUrls: ['./client-task-logs.component.css'] }) export class ClientTaskLogsComponent implements OnInit { - + baseUrl: string; + mercureUrl: string; traces: any[] = []; - loading = false; + groupedTraces: any[] = []; + commands: any[] = []; + length: number = 0; itemsPerPage: number = 20; page: number = 0; - length: number = 0; - baseUrl: string; + loading: boolean = true; + pageSizeOptions: number[] = [10, 20, 30, 50]; + datePipe: DatePipe = new DatePipe('es-ES'); + mode: ProgressBarMode = 'buffer'; + progress = 0; + bufferValue = 0; - constructor( + filteredCommands2 = Object.keys(COMMAND_TYPES).map(key => ({ + name: key, + value: key, + label: COMMAND_TYPES[key] + })); + + columns = [ + { + columnDef: 'id', + header: 'ID', + cell: (trace: any) => `${trace.id}`, + }, + { + columnDef: 'command', + header: 'Comando', + cell: (trace: any) => trace.command + }, + { + columnDef: 'status', + header: 'Estado', + cell: (trace: any) => trace.status + }, + { + columnDef: 'executedAt', + header: 'Ejecución', + cell: (trace: any) => this.datePipe.transform(trace.executedAt, 'dd/MM/yyyy hh:mm:ss'), + }, + { + columnDef: 'finishedAt', + header: 'Finalización', + cell: (trace: any) => this.datePipe.transform(trace.finishedAt, 'dd/MM/yyyy hh:mm:ss'), + }, + ]; + displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; + + filters: { [key: string]: string } = {}; + filteredCommands!: Observable; + commandControl = new FormControl(); + + constructor(private http: HttpClient, @Inject(MAT_DIALOG_DATA) public data: { client: any }, - private http: HttpClient, + private joyrideService: JoyrideService, + private dialog: MatDialog, + private cdr: ChangeDetectorRef, private configService: ConfigService, + private toastService: ToastrService, + private translationService: TranslationService ) { this.baseUrl = this.configService.apiUrl; + this.mercureUrl = this.configService.mercureUrl; } ngOnInit(): void { this.loadTraces(); + this.loadCommands(); + this.filteredCommands = this.commandControl.valueChanges.pipe( + startWith(''), + map(value => (typeof value === 'string' ? value : value?.name)), + map(name => (name ? this._filterCommands(name) : this.commands.slice())) + ); + + const eventSource = new EventSource(`${this.mercureUrl}?topic=` + + encodeURIComponent(`traces`)); + + eventSource.onmessage = (event) => { + const data = JSON.parse(event.data); + if (data && data['@id']) { + this.updateTracesStatus(data['@id'], data.status, data.progress); + } + } + } + + private updateTracesStatus(clientUuid: string, newStatus: string, progress: Number): void { + const traceIndex = this.traces.findIndex(trace => trace['@id'] === clientUuid); + if (traceIndex !== -1) { + const updatedTraces = [...this.traces]; + + updatedTraces[traceIndex] = { + ...updatedTraces[traceIndex], + status: newStatus, + progress: progress + }; + + this.traces = updatedTraces; + this.cdr.detectChanges(); + + console.log(`Estado actualizado para la traza ${clientUuid}: ${newStatus}`); + } else { + console.warn(`Traza con UUID ${clientUuid} no encontrado en la lista.`); + } + } + + private _filterCommands(name: string): any[] { + const filterValue = name.toLowerCase(); + return this.commands.filter(command => command.name.toLowerCase().includes(filterValue)); + } + + onOptionCommandSelected(selectedCommand: any): void { + this.filters['command'] = selectedCommand.name; + this.loadTraces(); + } + + onOptionStatusSelected(selectedStatus: any): void { + this.filters['status'] = selectedStatus; + this.loadTraces(); + } + + openInputModal(inputData: any): void { + this.dialog.open(InputDialogComponent, { + width: '70vw', + height: '60vh', + data: { input: inputData } + }); + } + + openOutputModal(outputData: any): void { + this.dialog.open(OutputDialogComponent, { + width: '500px', + data: { input: outputData } + }); + } + + cancelTrace(trace: any): void { + this.dialog.open(DeleteModalComponent, { + width: '300px', + data: { name: trace.jobId }, + }).afterClosed().subscribe((result) => { + if (result) { + this.http.post(`${this.baseUrl}/traces/server/${trace.uuid}/cancel`, {}).subscribe({ + next: () => { + this.toastService.success('Transmision de imagen cancelada'); + this.loadTraces(); + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + console.error(error.error['hydra:description']); + } + }); + } + }); } loadTraces(): void { @@ -55,4 +204,80 @@ export class ClientTaskLogsComponent implements OnInit { ); } + loadCommands() { + this.loading = true; + this.http.get(`${this.baseUrl}/commands?&page=1&itemsPerPage=10000`).subscribe( + response => { + this.commands = response['hydra:member']; + this.loading = false; + }, + error => { + console.error('Error fetching commands:', error); + this.loading = false; + } + ); + } + + resetFilters() { + this.loading = true; + this.filters = {}; + this.loadTraces(); + } + + groupByCommandId(traces: any[]): any[] { + const grouped: { [key: string]: any[] } = {}; + + traces.forEach(trace => { + const commandId = trace.command.id; + if (!grouped[commandId]) { + grouped[commandId] = []; + } + grouped[commandId].push(trace); + }); + + return Object.keys(grouped).map(key => ({ + commandId: key, + traces: grouped[key] + })); + } + + onPageChange(event: any): void { + this.page = event.pageIndex; + this.itemsPerPage = event.pageSize; + this.length = event.length; + this.loadTraces(); + } + + translateCommand(command: string): string { + return this.translationService.getCommandTranslation(command); + } + + clearCommandFilter(event: Event, clientSearchCommandInput: any): void { + event.stopPropagation(); + delete this.filters['command']; + clientSearchCommandInput.value = null; + this.loadTraces() + } + + clearStatusFilter(event: Event, clientSearchStatusInput: any): void { + event.stopPropagation(); + delete this.filters['status']; + clientSearchStatusInput.value = null; + this.loadTraces() + } + + iniciarTour(): void { + this.joyrideService.startTour({ + steps: [ + 'titleStep', + 'resetFiltersStep', + 'clientSelectStep', + 'commandSelectStep', + 'tableStep', + 'paginationStep' + ], + showPrevButton: true, + themeColor: '#3f51b5' + }); + } }