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', templateUrl: './client-task-logs.component.html', styleUrls: ['./client-task-logs.component.css'] }) export class ClientTaskLogsComponent implements OnInit { baseUrl: string; mercureUrl: string; traces: any[] = []; groupedTraces: any[] = []; commands: any[] = []; length: number = 0; itemsPerPage: number = 20; page: number = 0; loading: boolean = true; pageSizeOptions: number[] = [10, 20, 30, 50]; datePipe: DatePipe = new DatePipe('es-ES'); mode: ProgressBarMode = 'buffer'; progress = 0; bufferValue = 0; today = new Date(); 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), 'information']; filters: { [key: string]: any } = {}; filteredCommands!: Observable; commandControl = new FormControl(); constructor(private http: HttpClient, @Inject(MAT_DIALOG_DATA) public data: { client: any }, 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(); } onDateFilterChange(): void { const start = this.filters['startDate']; const end = this.filters['endDate']; if (!start || !end) { return; } if (start && end && start > end) { this.toastService.warning('La fecha de inicio no puede ser mayor que la fecha de fin'); return; } 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 { const clientId = this.data.client?.id; if (!clientId) return; this.loading = true; const params: any = { 'client.id': clientId, page: this.page + 1, itemsPerPage: this.itemsPerPage, ...this.filters }; if (params['startDate']) { params['executedAt[after]'] = this.datePipe.transform(params['startDate'], 'yyyy-MM-dd'); delete params['startDate']; } if (params['endDate']) { params['executedAt[before]'] = this.datePipe.transform(params['endDate'], 'yyyy-MM-dd'); delete params['endDate']; } console.log('🌐 GET', `${this.baseUrl}/traces`, params); const url = `${this.baseUrl}/traces`; this.http.get(url, { params }).subscribe( (data) => { this.traces = data['hydra:member']; this.length = data['hydra:totalItems']; this.loading = false; }, (error) => { console.error('Error fetching client traces', error); this.loading = false; } ); } 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(clientSearchCommandInput: any, clientSearchStatusInput: any) { this.loading = true; clientSearchCommandInput.value = null; clientSearchStatusInput.value = null; 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: [ 'tracesTitleStep', 'resetFiltersStep', 'filtersStep', 'tracesProgressStep', 'tracesInfoStep', 'paginationStep' ], showPrevButton: true, themeColor: '#3f51b5' }); } }