import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, forkJoin } from 'rxjs'; import { FormControl } from '@angular/forms'; import { map, startWith } from 'rxjs/operators'; import { DatePipe } from '@angular/common'; import { JoyrideService } from 'ngx-joyride'; import { MatDialog } from "@angular/material/dialog"; import { InputDialogComponent } from "./input-dialog/input-dialog.component"; import { ProgressBarMode } from '@angular/material/progress-bar'; import { DeleteModalComponent } from "../../shared/delete_modal/delete-modal/delete-modal.component"; import { ToastrService } from "ngx-toastr"; import { ConfigService } from '@services/config.service'; import { OutputDialogComponent } from "./output-dialog/output-dialog.component"; import { TranslationService } from "@services/translation.service"; import { COMMAND_TYPES } from '../../shared/constants/command-types'; @Component({ selector: 'app-task-logs', templateUrl: './task-logs.component.html', styleUrls: ['./task-logs.component.css'] }) export class TaskLogsComponent implements OnInit { baseUrl: string; mercureUrl: string; traces: any[] = []; groupedTraces: any[] = []; commands: any[] = []; clients: 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; 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: 'client', header: 'Cliente', cell: (trace: any) => trace.client?.name }, { 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]: string } = {}; filteredClients!: Observable; clientControl = new FormControl(); filteredCommands!: Observable; commandControl = new FormControl(); constructor(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.loadClients(); this.filteredCommands = this.commandControl.valueChanges.pipe( startWith(''), map(value => (typeof value === 'string' ? value : value?.name)), map(name => (name ? this._filterCommands(name) : this.commands.slice())) ); this.filteredClients = this.clientControl.valueChanges.pipe( startWith(''), map(value => (typeof value === 'string' ? value : value?.name)), map(name => (name ? this._filterClients(name) : this.clients.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 _filterClients(value: string): any[] { const filterValue = value.toLowerCase(); return this.clients.filter(client => client.name?.toLowerCase().includes(filterValue) || client.ip?.toLowerCase().includes(filterValue) || client.mac?.toLowerCase().includes(filterValue) ); } private _filterCommands(name: string): any[] { const filterValue = name.toLowerCase(); return this.commands.filter(command => command.name.toLowerCase().includes(filterValue)); } displayFnClient(client: any): string { return client && client.name ? client.name : ''; } onOptionCommandSelected(selectedCommand: any): void { this.filters['command'] = selectedCommand.name; this.loadTraces(); } onOptionStatusSelected(selectedStatus: any): void { this.filters['status'] = selectedStatus; this.loadTraces(); } onOptionClientSelected(selectedClient: any): void { this.filters['client.id'] = selectedClient.id; 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 { this.loading = true; const url = `${this.baseUrl}/traces?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`; const params = { ...this.filters }; if (params['status'] === undefined) { delete params['status']; } this.http.get(url, { params }).subscribe( (data) => { this.traces = data['hydra:member']; this.length = data['hydra:totalItems']; this.groupedTraces = this.groupByCommandId(this.traces); this.loading = false; }, (error) => { console.error('Error fetching 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; } ); } loadClients() { this.loading = true; this.http.get(`${this.baseUrl}/clients?page=1&itemsPerPage=10000`).subscribe( response => { this.clients = response['hydra:member']; this.loading = false; }, error => { console.error('Error fetching clients:', error); this.loading = false; } ); } resetFilters(clientSearchCommandInput: any, clientSearchStatusInput: any, clientSearchClientInput: any) { this.loading = true; clientSearchCommandInput.value = null; clientSearchStatusInput.value = null; clientSearchClientInput.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() } clearClientFilter(event: Event, clientSearchClientInput: any): void { event.stopPropagation(); delete this.filters['client.id']; clientSearchClientInput.value = null; this.loadTraces() } iniciarTour(): void { this.joyrideService.startTour({ steps: [ 'tracesTitleStep', 'resetFiltersStep', 'filtersStep', 'tracesProgressStep', 'tracesInfoStep', 'paginationStep' ], showPrevButton: true, themeColor: '#3f51b5' }); } }