import { inject } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { Component, OnInit, Inject, ChangeDetectorRef } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { DatePipe } from '@angular/common'; import { ConfigService } from '@services/config.service'; import { ToastrService } from 'ngx-toastr'; import { TranslationService } from '@services/translation.service'; import { OutputDialogComponent } from '../output-dialog/output-dialog.component'; import { InputDialogComponent } from '../input-dialog/input-dialog.component'; import { JoyrideService } from 'ngx-joyride'; import { FormControl } from '@angular/forms'; import { Observable } from 'rxjs'; import { COMMAND_TYPES } from 'src/app/shared/constants/command-types'; import { DeleteModalComponent } from 'src/app/shared/delete_modal/delete-modal/delete-modal.component'; @Component({ selector: 'app-client-pending-tasks', templateUrl: './client-pending-tasks.component.html', styleUrls: ['./client-pending-tasks.component.css'] }) export class ClientPendingTasksComponent implements OnInit { public translate = inject(TranslateService); baseUrl: string; mercureUrl: string; traces: 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'); filters: { [key: string]: any } = {}; filteredCommands!: Observable; commandControl = new FormControl(); columns = [ { columnDef: 'id', header: 'labelId', cell: (trace: any) => `${trace.id}`, }, { columnDef: 'command', header: 'command', cell: (trace: any) => trace.command }, { columnDef: 'status', header: 'state', cell: (trace: any) => trace.status }, { columnDef: 'executedAt', header: 'execution', cell: (trace: any) => this.datePipe.transform(trace.executedAt, 'dd/MM/yyyy HH:mm:ss'), }, { columnDef: 'finishedAt', header: 'ending', cell: (trace: any) => this.datePipe.transform(trace.finishedAt, 'dd/MM/yyyy HH:mm:ss'), }, ]; displayedColumns = [...this.columns.map(column => column.columnDef), 'information']; filteredCommands2 = Object.keys(COMMAND_TYPES).map(key => ({ name: key, value: key, label: COMMAND_TYPES[key] })); today = new Date(); constructor( private http: HttpClient, @Inject(MAT_DIALOG_DATA) public data: { client: any, isOrganizationalUnit?: boolean }, private joyrideService: JoyrideService, private dialog: MatDialog, private cdr: ChangeDetectorRef, private configService: ConfigService, private toastService: ToastrService, private translationService: TranslationService, public dialogRef: MatDialogRef ) { this.baseUrl = this.configService.apiUrl; this.mercureUrl = this.configService.mercureUrl; } ngOnInit(): void { // Si es una unidad organizativa, agregar columna de cliente if (this.data.isOrganizationalUnit) { this.columns.splice(2, 0, { columnDef: 'client', header: 'customer', cell: (trace: any) => trace.client?.name || 'N/A' }); this.displayedColumns = [...this.columns.map(column => column.columnDef), 'information']; } this.loadTraces(); const eventSource = new EventSource(`${this.mercureUrl}?topic=` + encodeURIComponent(`traces`)); eventSource.onmessage = (event) => { const data = JSON.parse(event.data); if (data && data['@id']) { this.updateTraceFromEvent(data['@id'], data); } } } private updateTraceFromEvent(traceId: string, data: any): void { const traceIndex = this.traces.findIndex(trace => trace['@id'] === traceId); if (traceIndex !== -1) { const updatedTraces = [...this.traces]; updatedTraces[traceIndex] = { ...updatedTraces[traceIndex], ...data }; this.traces = updatedTraces; this.cdr.detectChanges(); } } loadTraces(): void { this.loading = true; let params = new HttpParams() .set('status', 'pending') .set('page', (this.page + 1).toString()) .set('itemsPerPage', this.itemsPerPage.toString()); // Si es una unidad organizativa, obtener las trazas de todos sus clientes if (this.data.isOrganizationalUnit && this.data.client?.clients) { const clientIds = this.data.client.clients.map((client: any) => client.id); if (clientIds.length > 0) { // Agregar cada ID de cliente como un parámetro separado clientIds.forEach((id: number) => { params = params.append('client.id[]', id.toString()); }); } else { this.traces = []; this.length = 0; this.loading = false; return; } } else { // Cliente individual const clientId = this.data.client?.id; if (!clientId) { this.loading = false; return; } params = params.set('client.id', clientId.toString()); } const url = `${this.baseUrl}/traces`; console.log('URL con parámetros:', url, params.toString()); this.http.get(url, { params }).subscribe( (data) => { this.traces = data['hydra:member']; this.length = data['hydra:totalItems']; this.loading = false; }, (error) => { console.error(this.translate.instant('errorFetchingTraces'), error); this.loading = false; } ); } 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 } }); } cancelTrace(trace: any): void { if (trace.status !== 'pending' && trace.status !== 'in-progress') { this.toastService.warning(this.translate.instant('onlyTracesCanBeCanceled'), 'Advertencia'); return; } this.dialog.open(DeleteModalComponent, { width: '300px', data: { name: trace.jobId }, }).afterClosed().subscribe((result) => { if (result) { if (trace.status === 'in-progress') { this.http.post(`${this.baseUrl}/traces/${trace['@id']}/kill-job`, { jobId: trace.jobId }).subscribe({ next: () => { this.toastService.success(this.translate.instant('taskCanceledSuccessfully')); this.loadTraces(); }, error: (error) => { this.toastService.error(error.error['hydra:description'] || 'Error al cancelar la tarea'); console.error(this.translate.instant('errorCancelingInprogressTrace'), error); } }); } else { this.http.post(`${this.baseUrl}/traces/server/${trace.uuid}/cancel`, {}).subscribe({ next: () => { this.toastService.success(this.translate.instant('taskCanceledSuccessfully')); this.loadTraces(); }, error: (error) => { this.toastService.error(error.error['hydra:description'] || 'Error al cancelar la tarea'); console.error(this.translate.instant('errorCancelingPendingTrace'), error); } }); } } }); } resetFilters(clientSearchCommandInput: any, clientSearchStatusInput: any) { clientSearchCommandInput.value = ''; clientSearchStatusInput.value = ''; this.loadTraces(); } openOutputModal(outputData: any): void { this.dialog.open(OutputDialogComponent, { width: '70vw', data: { input: outputData } }); } 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 { clientSearchCommandInput.value = ''; this.loadTraces(); } clearStatusFilter(event: Event, clientSearchStatusInput: any): void { clientSearchStatusInput.value = ''; this.loadTraces(); } onDateFilterChange(): void { this.loadTraces(); } iniciarTour(): void { this.joyrideService.startTour({ steps: [ 'tracesTitleStep', 'resetFiltersStep', 'filtersStep', 'tracesProgressStep', 'tracesInfoStep', 'paginationStep' ], showPrevButton: true, themeColor: '#3f51b5' }); } close(): void { this.dialogRef.close(); } clearAllActions(): void { if (this.traces.length === 0) { this.toastService.warning(this.translate.instant('noActionsToClean')); return; } // Mostrar confirmación antes de proceder this.dialog.open(DeleteModalComponent, { width: '400px', data: { name: `Todas las acciones mostradas (${this.traces.length} acciones)`, message : this.translate.instant('areYouSureYouWant') }, }).afterClosed().subscribe((result) => { if (result) { this.loading = true; // Enviar array de traces en el body const tracesToCancel = this.traces.map((trace: any) => trace['@id']); this.http.post(`${this.baseUrl}/traces/cancel-multiple`, { traces: tracesToCancel }).subscribe({ next: () => { this.toastService.success(`Se han cancelado ${this.traces.length} acciones correctamente`); this.loadTraces(); // Recargar las trazas }, error: (error) => { console.error(this.translate.instant('errorCancelingActions1'), error); this.toastService.error(this.translate.instant('errorCancelingActions')); this.loadTraces(); // Recargar las trazas para mostrar el estado actual }, complete: () => { this.loading = false; } }); } }); } }