From 212c4f9eec83bb70036883741870f1e57dde5270 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 26 Jun 2025 15:57:04 +0200 Subject: [PATCH] refs #2252. Queue actions --- .../client-pending-tasks.component.css | 143 ++++++ .../client-pending-tasks.component.html | 158 +++++++ .../client-pending-tasks.component.ts | 328 ++++++++++++++ .../client-task-logs.component.css | 422 +++++++++++++++++- .../client-task-logs.component.html | 18 +- .../client-task-logs.component.ts | 44 +- .../queue-confirmation-modal.component.css | 54 +++ .../queue-confirmation-modal.component.html | 9 + .../queue-confirmation-modal.component.ts | 24 + 9 files changed, 1167 insertions(+), 33 deletions(-) create mode 100644 ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.css create mode 100644 ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.html create mode 100644 ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.ts create mode 100644 ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.css create mode 100644 ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.html create mode 100644 ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.ts diff --git a/ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.css b/ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.css new file mode 100644 index 0000000..308009a --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.css @@ -0,0 +1,143 @@ +.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; + } + + .header-actions { + display: flex; + gap: 10px; + align-items: center; + } + + .header-actions button { + display: flex; + align-items: center; + gap: 5px; + } + + .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 0.5rem 0rem; + box-sizing: border-box; + } + + .search-boolean { + flex: 1; + padding: 5px; + } + + .search-select { + flex: 2; + padding: 5px; + } + + .search-date { + flex: 1; + 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; + } + + /* Ajuste para el botón de cancelar en la barra de progreso */ + .progress-container .cancel-button { + margin-left: auto; + flex-shrink: 0; + } + + .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; + } + \ No newline at end of file diff --git a/ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.html b/ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.html new file mode 100644 index 0000000..23da054 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.html @@ -0,0 +1,158 @@ + +
+ +
+ \ No newline at end of file diff --git a/ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.ts b/ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.ts new file mode 100644 index 0000000..e176810 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/client-pending-tasks/client-pending-tasks.component.ts @@ -0,0 +1,328 @@ +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 { + 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: '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']; + + 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: 'Cliente', + 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.updateTracesStatus(data['@id'], data.status); + } + } + } + + private updateTracesStatus(clientUuid: string, newStatus: string): void { + const traceIndex = this.traces.findIndex(trace => trace['@id'] === clientUuid); + if (traceIndex !== -1) { + const updatedTraces = [...this.traces]; + + updatedTraces[traceIndex] = { + ...updatedTraces[traceIndex], + status: newStatus + }; + + 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('Error fetching traces', 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('Solo se pueden cancelar trazas pendientes o en ejecución', '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('Tarea cancelada correctamente'); + this.loadTraces(); + }, + error: (error) => { + this.toastService.error(error.error['hydra:description'] || 'Error al cancelar la tarea'); + console.error('Error cancelling in-progress trace:', error); + } + }); + } else { + this.http.post(`${this.baseUrl}/traces/server/${trace.uuid}/cancel`, {}).subscribe({ + next: () => { + this.toastService.success('Tarea cancelada correctamente'); + this.loadTraces(); + }, + error: (error) => { + this.toastService.error(error.error['hydra:description'] || 'Error al cancelar la tarea'); + console.error('Error cancelling pending trace:', error); + } + }); + } + } + }); + } + + resetFilters(clientSearchCommandInput: any, clientSearchStatusInput: any) { + clientSearchCommandInput.value = ''; + clientSearchStatusInput.value = ''; + this.loadTraces(); + } + + openOutputModal(outputData: any): void { + this.dialog.open(OutputDialogComponent, { + width: '500px', + 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('No hay acciones para limpiar'); + return; + } + + // Mostrar confirmación antes de proceder + this.dialog.open(DeleteModalComponent, { + width: '400px', + data: { + name: `Todas las acciones mostradas (${this.traces.length} acciones)`, + message: '¿Estás seguro de que quieres cancelar todas las acciones mostradas?' + }, + }).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('Error al cancelar las acciones:', error); + this.toastService.error('Error al cancelar las acciones'); + this.loadTraces(); // Recargar las trazas para mostrar el estado actual + }, + complete: () => { + this.loading = false; + } + }); + } + }); + } +} \ No newline at end of file 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 4a3ab81..4520ce9 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 @@ -10,6 +10,15 @@ align-items: center; padding: 10px 10px; border-bottom: 1px solid #ddd; + background: white; + color: #333; + border-radius: 8px 8px 0 0; +} + +.header-right { + display: flex; + gap: 8px; + align-items: center; } .header-container-title { @@ -18,6 +27,133 @@ margin-left: 1em; } +.header-container-title h2 { + margin: 0; + font-weight: 500; +} + +.header-actions { + display: flex; + gap: 10px; + align-items: center; +} + +.action-button { + display: flex; + align-items: center; + gap: 5px; + padding: 8px 16px; + border-radius: 20px; + border: 1px solid #ddd; + cursor: pointer; + font-weight: 500; + transition: all 0.3s ease; + background: white; + color: #333; +} + +.action-button:hover { + background: #f8f9fa; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.action-button.secondary { + background: #f8f9fa; + border-color: #adb5bd; +} + +.action-button.secondary:hover { + background: #e9ecef; +} + +/* Estadísticas */ +.stats-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 15px; + margin: 20px 0; + padding: 0 10px; +} + +.stat-card { + background: white; + border-radius: 12px; + padding: 20px; + text-align: center; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease, box-shadow 0.3s ease; + border-left: 4px solid #667eea; +} + +.stat-card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15); +} + +.stat-number { + font-size: 2rem; + font-weight: bold; + color: #667eea; + margin-bottom: 5px; +} + +.stat-label { + font-size: 0.9rem; + color: #666; + font-weight: 500; +} + +/* Filtros mejorados */ +.filters-section { + background: #f8f9fa; + border-radius: 8px; + margin: 20px 0; + padding: 20px; + border: 1px solid #e9ecef; +} + +.filters-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +.filters-header h3 { + margin: 0; + color: #495057; + font-weight: 500; +} + +.search-container { + display: flex; + flex-direction: row; + gap: 15px; + transition: all 0.3s ease; + margin: 1.5rem 0rem 0.5rem 0rem; + box-sizing: border-box; + align-items: center; + justify-content: space-between; +} + +.search-container.expanded { + gap: 20px; +} + +.filter-row { + display: flex; + gap: 15px; + align-items: center; + width: 100%; +} + +.advanced-filters { + border-top: 1px solid #dee2e6; + padding-top: 15px; + margin-top: 10px; +} + .calendar-button-row { display: flex; gap: 15px; @@ -40,12 +176,9 @@ table { width: 100%; } -.search-container { - display: flex; - justify-content: space-between; - align-items: center; - margin: 1.5rem 0rem 0.5rem 0rem; - box-sizing: border-box; +.search-string { + flex: 1; + padding: 5px; } .search-boolean { @@ -64,39 +197,142 @@ table { } .mat-elevation-z8 { - box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + border-radius: 8px; + overflow: hidden; +} + +/* Tabla mejorada */ +.table-container { + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.table-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 20px; + background: #f8f9fa; + border-bottom: 1px solid #dee2e6; +} + +.table-info { + color: #6c757d; + font-size: 0.9rem; +} + +.table-actions { + display: flex; + gap: 10px; +} + +.column-header { + display: flex; + align-items: center; + gap: 5px; +} + +.sort-button { + opacity: 0.5; + transition: opacity 0.3s ease; +} + +.sort-button:hover { + opacity: 1; +} + +.sort-button.active { + opacity: 1; + color: #667eea; +} + +/* Celdas mejoradas */ +.command-cell, .client-cell, .date-cell { + display: flex; + flex-direction: column; + gap: 2px; +} + +.command-name, .client-name { + font-weight: 500; + color: #212529; +} + +.command-id, .client-ip { + font-size: 0.75rem; + color: #6c757d; +} + +.date-time { + font-size: 0.85rem; + color: #212529; +} + +.date-relative { + font-size: 0.7rem; + color: #6c757d; + font-style: italic; } .progress-container { display: flex; align-items: center; gap: 10px; + width: 100%; +} + +.progress-text { + font-size: 0.8rem; + font-weight: 500; + color: #667eea; + min-width: 35px; +} + +/* Ajuste para el botón de cancelar en la barra de progreso */ +.progress-container .cancel-button { + margin-left: auto; + flex-shrink: 0; } .paginator-container { display: flex; justify-content: end; - margin-bottom: 30px; + margin: 20px 0; + padding: 0 10px; } +/* Chips de estado mejorados */ .chip-failed { - background-color: #e87979 !important; - color: white; + background-color: #ff6b6b !important; + color: white !important; + font-weight: 500; } .chip-success { - background-color: #46c446 !important; - color: white; + background-color: #51cf66 !important; + color: white !important; + font-weight: 500; } .chip-pending { - background-color: #bebdbd !important; - color: black; + background-color: #74c0fc !important; + color: white !important; + font-weight: 500; } .chip-in-progress { - background-color: #f5a623 !important; - color: white; + background-color: #ffd43b !important; + color: #212529 !important; + font-weight: 500; +} + +.chip-cancelled { + background-color: #adb5bd !important; + color: white !important; + font-weight: 500; } .status-progress-flex { @@ -105,6 +341,48 @@ table { gap: 8px; } +/* Opciones de estado */ +.status-option { + display: flex; + align-items: center; + gap: 8px; +} + +.status-indicator { + width: 12px; + height: 12px; + border-radius: 50%; +} + +.status-indicator.failed { background-color: #dc3545; } +.status-indicator.success { background-color: #28a745; } +.status-indicator.pending { background-color: #17a2b8; } +.status-indicator.in-progress { background-color: #ffc107; } +.status-indicator.cancelled { background-color: #6c757d; } + +/* Opciones de cliente */ +.client-option { + display: flex; + flex-direction: column; + gap: 2px; +} + +.client-name { + font-weight: 500; +} + +.client-details { + font-size: 0.8rem; + color: #6c757d; +} + +/* Botones de acción */ +.action-buttons { + display: flex; + gap: 5px; + justify-content: center; +} + button.cancel-button { display: flex; align-items: center; @@ -113,12 +391,120 @@ button.cancel-button { } .cancel-button { - color: red; + color: #dc3545; background-color: transparent; border: none; padding: 0; + transition: all 0.3s ease; +} + +.cancel-button:hover { + background-color: rgba(220, 53, 69, 0.1); + border-radius: 50%; } .cancel-button mat-icon { - color: red; + color: #dc3545; +} + +/* Filas seleccionadas */ +.selected-row { + background-color: rgba(102, 126, 234, 0.1) !important; +} + +.mat-row:hover { + background-color: rgba(102, 126, 234, 0.05); + cursor: pointer; +} + +/* Responsive */ +@media (max-width: 768px) { + .header-container { + flex-direction: column; + gap: 15px; + text-align: center; + background: white; + color: #333; + } + + .header-actions { + width: 100%; + justify-content: center; + } + + .stats-container { + grid-template-columns: repeat(2, 1fr); + } + + .filter-row { + grid-template-columns: 1fr; + } + + .table-header { + flex-direction: column; + gap: 10px; + text-align: center; + } + + .action-buttons { + flex-direction: column; + gap: 2px; + } +} + +/* Animaciones */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +.stats-container, .filters-section, .table-container { + animation: fadeIn 0.5s ease-out; +} + +/* Estados de carga */ +.loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +/* Botón de cerrar en footer */ +.footer-actions { + display: flex; + justify-content: flex-end; + padding: 20px 0; + border-top: 1px solid #e9ecef; + margin-top: 20px; +} + +.footer-actions button { + min-width: 120px; + padding: 10px 24px; + font-weight: 500; + border-radius: 8px; + transition: all 0.3s ease; + background-color: white; + color: #333; + border: 1px solid #ddd; +} + +.footer-actions button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + background-color: #f8f9fa; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; } 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 843bd76..ab1e27b 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,4 +1,4 @@ - - \ No newline at end of file + + + + + \ 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 3b28e20..551a731 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 @@ -175,21 +175,41 @@ export class ClientTaskLogsComponent implements OnInit { } cancelTrace(trace: any): void { + if (trace.status !== 'pending' && trace.status !== 'in-progress') { + this.toastService.warning('Solo se pueden cancelar trazas pendientes o en ejecución', 'Advertencia'); + return; + } + 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']); - } - }); + if (trace.status === 'in-progress') { + this.http.post(`${this.baseUrl}/traces/${trace['@id']}/cancel`, { + job_id: trace.jobId + }).subscribe({ + next: () => { + this.toastService.success('Tarea cancelada correctamente'); + this.loadTraces(); + }, + error: (error) => { + this.toastService.error(error.error['hydra:description'] || 'Error al cancelar la tarea'); + console.error('Error cancelling in-progress trace:', error); + } + }); + } else { + this.http.post(`${this.baseUrl}/traces/server/${trace.uuid}/cancel`, {}).subscribe({ + next: () => { + this.toastService.success('Tarea cancelada correctamente'); + this.loadTraces(); + }, + error: (error) => { + this.toastService.error(error.error['hydra:description'] || 'Error al cancelar la tarea'); + console.error('Error cancelling pending trace:', error); + } + }); + } } }); } @@ -311,4 +331,8 @@ export class ClientTaskLogsComponent implements OnInit { themeColor: '#3f51b5' }); } + + closeDialog(): void { + this.dialog.closeAll(); + } } diff --git a/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.css b/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.css new file mode 100644 index 0000000..835ba25 --- /dev/null +++ b/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.css @@ -0,0 +1,54 @@ +mat-dialog-content { + font-size: 16px; + margin-bottom: 20px; + color: #555; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 0.5em 1.5em 1.5em 1.5em; +} + +.action-button { + background-color: #2196f3; + color: white; + padding: 8px 18px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition: transform 0.3s ease; + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +.action-button:hover:not(:disabled) { + background-color: #3f51b5; +} + +.action-button:disabled { + background-color: #ced0df; + cursor: not-allowed; +} + +.ordinary-button { + background-color: #f5f5f5; + color: #333; + padding: 8px 18px; + border: 1px solid #ddd; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition: transform 0.3s ease; + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +.ordinary-button:hover:not(:disabled) { + background-color: #e0e0e0; +} + +mat-checkbox { + margin-top: 15px; + display: block; +} \ No newline at end of file diff --git a/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.html b/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.html new file mode 100644 index 0000000..d199d55 --- /dev/null +++ b/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.html @@ -0,0 +1,9 @@ +

Confirmación

+
+

¿Quieres que se encolen las acciones para cuyos PCs no se pueda ejecutar?

+ Encolar acciones +
+
+ + +
\ No newline at end of file diff --git a/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.ts b/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.ts new file mode 100644 index 0000000..2e6571b --- /dev/null +++ b/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.ts @@ -0,0 +1,24 @@ +import { Component, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; + +@Component({ + selector: 'app-queue-confirmation-modal', + templateUrl: './queue-confirmation-modal.component.html', + styleUrl: './queue-confirmation-modal.component.css' +}) +export class QueueConfirmationModalComponent { + shouldQueue: boolean = false; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any + ) {} + + onNoClick(): void { + this.dialogRef.close(false); + } + + onYesClick(): void { + this.dialogRef.close(this.shouldQueue); + } +} \ No newline at end of file