diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 7b63f13..07f2dcb 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -149,6 +149,7 @@ import { ShowTaskScheduleComponent } from './components/commands/commands-task/s import { ShowTaskScriptComponent } from './components/commands/commands-task/show-task-script/show-task-script.component'; import { CreateTaskScriptComponent } from './components/commands/commands-task/create-task-script/create-task-script.component'; import { ViewParametersModalComponent } from './components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component'; +import { OutputDialogComponent } from './components/task-logs/output-dialog/output-dialog.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); @@ -255,7 +256,8 @@ registerLocaleData(localeEs, 'es-ES'); ShowTaskScheduleComponent, ShowTaskScriptComponent, CreateTaskScriptComponent, - ViewParametersModalComponent + ViewParametersModalComponent, + OutputDialogComponent ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.css b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.html b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.html new file mode 100644 index 0000000..eed69e9 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.html @@ -0,0 +1,7 @@ +

{{ 'inputDetails' | translate }}

+
+
{{ data.input | json }}
+
+
+ +
diff --git a/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.spec.ts b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.spec.ts new file mode 100644 index 0000000..d690f31 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OutputDialogComponent } from './output-dialog.component'; + +describe('OutputDialogComponent', () => { + let component: OutputDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [OutputDialogComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(OutputDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.ts b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.ts new file mode 100644 index 0000000..fe47c50 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.ts @@ -0,0 +1,18 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; + +@Component({ + selector: 'app-output-dialog', + templateUrl: './output-dialog.component.html', + styleUrl: './output-dialog.component.css' +}) +export class OutputDialogComponent { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { input: any } + ) {} + + close(): void { + this.dialogRef.close(); + } +} diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.html b/ogWebconsole/src/app/components/task-logs/task-logs.component.html index f7ceea4..37276fa 100644 --- a/ogWebconsole/src/app/components/task-logs/task-logs.component.html +++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.html @@ -19,38 +19,54 @@
- - {{ client.name }} +
+ {{ client.name }} + + {{ client.ip }} — {{ client.mac }} + +
+
+ + Por favor, ingrese el nombre del cliente
- - - - - {{ command.name }} + + {{ 'commandSelectStepText' | translate }} + + + {{ translateCommand(command.name) }} - + + + Estado - - Todos + Fallido Pendiente de ejecutar Ejecutando Completado con éxito Cancelado +
@@ -82,10 +98,10 @@ 'chip-cancelled': trace.status === 'cancelled' }"> {{ - trace.status === 'failed' ? 'Fallido' : + trace.status === 'failed' ? 'Error' : trace.status === 'in-progress' ? 'En ejecución' : - trace.status === 'success' ? 'Finalizado con éxito' : - trace.status === 'pending' ? 'Pendiente de ejecutar' : + trace.status === 'success' ? 'Completado' : + trace.status === 'pending' ? 'Pendiente' : trace.status === 'cancelled' ? 'Cancelado' : trace.status }} @@ -101,20 +117,59 @@ - - + +
+ {{ translateCommand(trace.command) }} + {{ trace.jobId }} +
+ +
+ {{ trace.client?.name }} + {{ trace.client?.ip }} +
+
+ + + +
+ {{ trace.executedAt |date: 'dd/MM/yyyy hh:mm:ss'}} +
+
+ + +
+ {{ trace.finishedAt |date: 'dd/MM/yyyy hh:mm:ss'}} +
+
{{ column.cell(trace) }} -
+ + + {{ 'columnActions' | translate }} + + + + + diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts index 7da0b89..d2f1353 100644 --- a/ogWebconsole/src/app/components/task-logs/task-logs.component.ts +++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts @@ -11,6 +11,9 @@ 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', @@ -34,6 +37,12 @@ export class TaskLogsComponent implements OnInit { progress = 0; bufferValue = 0; + filteredCommands2 = Object.keys(COMMAND_TYPES).map(key => ({ + name: key, + value: key, + label: COMMAND_TYPES[key] + })); + columns = [ { columnDef: 'id', @@ -47,7 +56,7 @@ export class TaskLogsComponent implements OnInit { }, { columnDef: 'client', - header: 'Client', + header: 'Cliente', cell: (trace: any) => trace.client?.name }, { @@ -55,24 +64,9 @@ export class TaskLogsComponent implements OnInit { header: 'Estado', cell: (trace: any) => trace.status }, - { - columnDef: 'jobId', - header: 'Hilo de trabajo', - cell: (trace: any) => trace.jobId - }, - { - columnDef: 'input', - header: 'Input', - cell: (trace: any) => trace.input - }, - { - columnDef: 'output', - header: 'Logs', - cell: (trace: any) => trace.output - }, { columnDef: 'executedAt', - header: 'Programación de ejecución', + header: 'Ejecución', cell: (trace: any) => this.datePipe.transform(trace.executedAt, 'dd/MM/yyyy hh:mm:ss'), }, { @@ -81,7 +75,7 @@ export class TaskLogsComponent implements OnInit { cell: (trace: any) => this.datePipe.transform(trace.finishedAt, 'dd/MM/yyyy hh:mm:ss'), }, ]; - displayedColumns = [...this.columns.map(column => column.columnDef)]; + displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; filters: { [key: string]: string } = {}; filteredClients!: Observable; @@ -94,7 +88,8 @@ export class TaskLogsComponent implements OnInit { private dialog: MatDialog, private cdr: ChangeDetectorRef, private configService: ConfigService, - private toastService: ToastrService + private toastService: ToastrService, + private translationService: TranslationService ) { this.baseUrl = this.configService.apiUrl; this.mercureUrl = this.configService.mercureUrl; @@ -103,7 +98,7 @@ export class TaskLogsComponent implements OnInit { ngOnInit(): void { this.loadTraces(); this.loadCommands(); - //this.loadClients(); + this.loadClients(); this.filteredCommands = this.commandControl.valueChanges.pipe( startWith(''), map(value => (typeof value === 'string' ? value : value?.name)), @@ -147,11 +142,17 @@ export class TaskLogsComponent implements OnInit { } - private _filterClients(name: string): any[] { - const filterValue = name.toLowerCase(); - return this.clients.filter(client => client.name.toLowerCase().includes(filterValue)); + 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)); @@ -161,12 +162,13 @@ export class TaskLogsComponent implements OnInit { return client && client.name ? client.name : ''; } - displayFnCommand(command: any): string { - return command && command.name ? command.name : ''; + onOptionCommandSelected(selectedCommand: any): void { + this.filters['command'] = selectedCommand.name; + this.loadTraces(); } - onOptionCommandSelected(selectedCommand: any): void { - this.filters['command.id'] = selectedCommand.id; + onOptionStatusSelected(selectedStatus: any): void { + this.filters['status'] = selectedStatus; this.loadTraces(); } @@ -177,11 +179,19 @@ export class TaskLogsComponent implements OnInit { openInputModal(inputData: any): void { this.dialog.open(InputDialogComponent, { - width: '700px', + 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', @@ -239,28 +249,19 @@ export class TaskLogsComponent implements OnInit { loadClients() { this.loading = true; - this.http.get(`${this.baseUrl}/clients?&page=1&itemsPerPage=10000`).subscribe( + this.http.get(`${this.baseUrl}/clients?page=1&itemsPerPage=10000`).subscribe( response => { - const clientIds = response['hydra:member'].map((client: any) => client['@id']); - const clientDetailsRequests: Observable[] = clientIds.map((id: string) => this.http.get(`${this.baseUrl}${id}`)); - forkJoin(clientDetailsRequests).subscribe( - (clients: any[]) => { - this.clients = clients; - this.loading = false; - }, - (error: any) => { - console.error('Error fetching client details:', error); - this.loading = false; - } - ); + this.clients = response['hydra:member']; + this.loading = false; }, - (error: any) => { + error => { console.error('Error fetching clients:', error); this.loading = false; } ); } + resetFilters() { this.loading = true; this.filters = {}; @@ -291,6 +292,31 @@ export class TaskLogsComponent implements OnInit { 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: [ diff --git a/ogWebconsole/src/app/services/translation.service.ts b/ogWebconsole/src/app/services/translation.service.ts new file mode 100644 index 0000000..b7144a9 --- /dev/null +++ b/ogWebconsole/src/app/services/translation.service.ts @@ -0,0 +1,37 @@ + +import { Injectable } from '@angular/core'; +import { COMMAND_TYPES } from '../shared/constants/command-types'; + +@Injectable({ + providedIn: 'root' +}) +export class TranslationService { + private currentLang: string = 'es'; + + constructor() {} + + getCommandTranslation(command: string): string { + switch(command) { + case 'install-oglive': + return COMMAND_TYPES['install-oglive'][this.currentLang]; + case 'create-aux-file': + return COMMAND_TYPES['create-aux-file'][this.currentLang]; + case 'convert-image-to-virtual': + return COMMAND_TYPES['convert-image-to-virtual'][this.currentLang]; + case 'deploy-image': + return COMMAND_TYPES['deploy-image'][this.currentLang]; + case 'create-image': + return COMMAND_TYPES['create-image'][this.currentLang]; + case 'reboot': + return COMMAND_TYPES.reboot[this.currentLang]; + case 'shutdown': + return COMMAND_TYPES.shutdown[this.currentLang]; + case 'partition-and-format': + return COMMAND_TYPES['partition-and-format'][this.currentLang]; + case 'run-script': + return COMMAND_TYPES['run-script'][this.currentLang]; + default: + return command; + } + } +} diff --git a/ogWebconsole/src/app/shared/constants/command-types.ts b/ogWebconsole/src/app/shared/constants/command-types.ts new file mode 100644 index 0000000..ef81cb5 --- /dev/null +++ b/ogWebconsole/src/app/shared/constants/command-types.ts @@ -0,0 +1,46 @@ +export const COMMAND_TYPES: any = { + 'create-image': { + en: 'Create Image', + es: 'Crear Imagen' + }, + + 'deploy-image': { + en: 'Deploy Image', + es: 'Desplegar Imagen' + }, + + 'install-oglive': { + en: 'Install OGLive', + es: 'Instalar OGLive' + }, + + 'create-aux-file': { + en: 'Create Auxiliary File', + es: 'Crear archivos auxiliares' + }, + + 'convert-image-to-virtual': { + en: 'Convert Image to Virtual', + es: 'Convertir imagen a virtual' + }, + + reboot: { + en: 'Reboot', + es: 'Reiniciar' + }, + + shutdown: { + en: 'Shutdown', + es: 'Apagar' + }, + + 'partition-and-format': { + en: 'Partition and Format', + es: 'Particionar y Formatear' + }, + + 'run-script': { + en: 'Run Script', + es: 'Ejecutar Script' + } +}; diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index e829ced..b1de3d8 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -51,12 +51,14 @@ "checkboxOrgMinimal": "Minimal Organizational Unit", "checkboxUserRole": "User", "groupsTitleStepText": "On this screen, you can manage the main organizational units (Faculties, Classrooms, Classroom Groups, and clients).", + "repositoryTitleStepText": "On this screen, you can manage the repositories.", + "tableDateRepositoryText": "Use the table to view the details of the repositories.", "titleStepText": "On this screen, you can manage the calendars of remote teams connected to the UDS service", "groupsAddStepText": "Click to add a new organizational unit or clients.", "adminCalendarsTitle": "Manage calendars", "addButtonStepText": "Click here to add a new calendar.", "addCalendar": "Add calendar", - "searchStepText": "Use this search bar to filter existing calendars.", + "searchStepText": "Use this search bar to filter existing entities.", "searchCalendarLabel": "Search calendar name", "tableStepText": "Here are the existing calendars with their characteristics and settings.", "actionsStepText": "Access the available actions for each calendar here.", @@ -107,7 +109,7 @@ "statusColumn": "Status", "enabled": "Enabled", "disabled": "Disabled", - "adminCommandsTitle": "Command and procedure traces", + "adminCommandsTitle": "Trace Commands", "resetFiltersStepText": "Click to reset the applied filters and see all traces.", "resetFilters": "Reset filters", "clientSelectStepText": "Select a client to see the associated traces.", @@ -183,6 +185,7 @@ "editUnitTooltip": "Edit this organizational unit", "viewUnitTooltip": "View organizational unit details", "viewUnitMenu": "View data", + "tableDateStep": "Use the table to view the details", "addInternalUnitTooltip": "Create a new internal organizational unit", "addClientTooltip": "Register a client in this organizational unit", "deleteElementTooltip": "Delete this element", @@ -359,10 +362,12 @@ "nameColumnHeader": "Name", "templateColumnHeader": "Template", "pxeImageTitle": "Information on ogBoot server", + "tableDatePxeTemplateText": "Use the table to view the details of the PXE templates.", + "searchIsDefaultText": "Search for default images", "serverInfoDescription": "Access information and synchronization options on the OgBoot server.", "syncDatabaseButton": "Sync database", "viewInfoButton": "View Information", - "adminImagesDescription": "From here you can manage the images configured on the OgBoot server.", + "adminImagesDescription": "From here you can manage the ogLive images configured on the OgBoot server.", "actionsDescription": "Manage each image with options to view, edit, delete, and more.", "addClientButton": "Add client", "searchClientNameLabel": "Search client name", @@ -427,6 +432,7 @@ "boot": "Boot", "TOOLTIP_BOOT": "Configure and manage boot options", "ogLive": "ogLive", + "viewInfoStepText": "View information about the OgLive server", "TOOLTIP_PXE_IMAGES": "View available PXE boot images", "pxeTemplates": "PXE Templates", "pxeTemplate": "Template", @@ -488,6 +494,9 @@ "processes": "Processes", "usedPercentageLabel": "Used", "errorLoadingData": "Error fetching data. Service not available", + "repository": "Repository", + "addRepository": "Add repository", + "addRepositoryStepText": "Click to add a new repository.", "repositoryTitleStep": "On this screen you can manage image repositories.", "partitions": "Partitions", "clientsViewStepText": "Display of the selected organizational unit's clients", @@ -510,5 +519,13 @@ }, "remoteAccess": "Remote access available", "noRemoteAccess": "Remote access not available", - "capacityWarning": "The capacity cannot be negative" -} \ No newline at end of file + "capacityWarning": "The capacity cannot be negative", + "monolithicImageStep": "Monolithic image", + "monolithicImageStepText": "On this screen you can manage monolithic images.", + "monolithicImage": "Monolithic image", + "gitImage": "Git images", + "gitImageStep": "Git images", + "gitImageStepText": "On this screen you can manage git images.", + "partitions": "Particiones", + "isDefaultLabel": "Default" +} diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 92d37a3..0ebb6e3 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -1,7 +1,7 @@ { "Administration": "Administración", "changePassword": "Cambiar contraseña", - "logout": "Salir", + "logout": "Cerrar sesión", "loginlabelUsername": "Introduce tu usuario", "loginlabelPassword": "Introduce tu contraseña", "buttonLogin": "Login", @@ -56,7 +56,7 @@ "adminCalendarsTitle": "Administrar calendarios", "addButtonStepText": "Haz clic aquí para añadir un nuevo calendario.", "addCalendar": "Añadir calendario", - "searchStepText": "Utiliza esta barra de búsqueda para filtrar los calendarios existentes.", + "searchStepText": "Utiliza esta barra de búsqueda para filtrar las entidades existentes.", "searchCalendarLabel": "Buscar nombre de calendario", "tableStepText": "Aquí se muestran los calendarios existentes con sus características y configuraciones.", "actionsStepText": "Accede a las acciones disponibles para cada calendario aquí.", @@ -71,6 +71,7 @@ "reasonPlaceholder": "Razón para la excepción", "startDate": "Fecha de inicio", "endDate": "Fecha de fin", + "tableDateStep": "Esta tabla muestra los datos asociados al modulo en concreto", "buttonSave": "Guardar", "adminCommandGroupsTitle": "Administrar Grupos de Comandos", "addCommandGroupStepText": "Haz clic para añadir un nuevo grupo de comandos.", @@ -107,13 +108,13 @@ "statusColumn": "Estado", "enabled": "Habilitado", "disabled": "Deshabilitado", - "adminCommandsTitle": "Trazas de comandos y procedimientos", + "adminCommandsTitle": "Traza de ejecución", "CommandsTitle": "Administrar Comandos", "resetFiltersStepText": "Haz clic para reiniciar los filtros aplicados y ver todas las trazas.", "resetFilters": "Reiniciar filtros", "clientSelectStepText": "Selecciona un cliente para ver las trazas asociadas.", "filterClientPlaceholder": "Filtrar por cliente", - "commandSelectStepText": "Selecciona un comando para ver las trazas específicas de ese comando.", + "commandSelectStepText": "Selecciona un comando para ver las trazas.", "filterCommandPlaceholder": "Filtrar por comando", "taskDetailsTitle": "Detalles de la Tarea", "taskId": "ID de la Tarea", @@ -263,6 +264,7 @@ "ipLabel": "Dirección IP", "ipError": "Formato de dirección IP inválido. Ejemplo válido: 127.0.0.1", "templateLabel": "Plantilla PXE", + "templateContent": "Contenido de la plantilla PXE", "digitalBoard": "Pizarra digital", "projectorAlt": "Proyector", "clientAlt": "Cliente", @@ -360,10 +362,12 @@ "nameColumnHeader": "Nombre", "templateColumnHeader": "Plantilla", "pxeImageTitle": "Información en servidor ogBoot", + "tableDatePxeTemplateText": "Esta tabla muestra los datos asociados a las plantillas PXE existentes.", + "searchIsDefaultText": "Filtra para mostrar solo las imágenes por defecto o no por defecto.", "serverInfoDescription": "Accede a información y opciones de sincronización en el servidor OgBoot.", "syncDatabaseButton": "Sincronizar base de datos", "viewInfoButton": "Ver Información", - "adminImagesDescription": "Desde aquí puedes gestionar las imágenes configuradas en el servidor OgBoot.", + "adminImagesDescription": "Desde aquí puedes gestionar las imágenes ogLive.", "actionsDescription": "Administra cada imagen con opciones para ver, editar, eliminar y más.", "addClientButton": "Añadir cliente", "searchClientNameLabel": "Buscar nombre de cliente", @@ -429,6 +433,7 @@ "boot": "Boot", "TOOLTIP_BOOT": "Configurar y administrar opciones de arranque", "ogLive": "ogLive", + "viewInfoStepText": "Accede a información y opciones en la API de OgBoot.", "TOOLTIP_PXE_IMAGES": "Ver imágenes disponibles para arranque PXE", "pxeTemplates": "Plantillas PXE", "pxeTemplate": "Plantilla", @@ -490,6 +495,9 @@ "processes": "Procesos", "usedPercentageLabel": "Usado", "errorLoadingData": "Error al cargar los datos. Servicio inactivo", + "repository": "Repositorio", + "addRepository": "Añadir repositorio", + "addRepositoryStepText": "Haz clic para añadir un nuevo repositorio.", "repositoryTitleStep": "En esta pantalla se pueden gestionar los repositorios de imágenes.", "partitions": "Particiones", "clientsViewStepText": "Visualización de los clientes de la unidad organizativa seleccionada", @@ -512,5 +520,15 @@ }, "remoteAccess": "Disponible acceso remoto", "noRemoteAccess": "No disponible acceso remoto", - "capacityWarning": "El aforo no puede ser" -} \ No newline at end of file + "capacityWarning": "El aforo no puede ser", + "tableDateRepositoryText": "Esta tabla muestra los datos asociados a los repositorios existentes.", + "repositoryTitleStepText": "En esta pantalla se pueden gestionar los repositorios de imágenes.", + "monolithicImageStep": "Imagen monolítica", + "monolithicImageStepText": "Esta opción permite visualizar las imágenes monolíticas disponibles en el servidor.", + "gitImageStep": "Imágenes Git", + "monolithicImage": "Imagenes monolíticas", + "gitImage": "Imágenes Git", + "gitImageStepText": "Esta opción permite visualizar las imágenes Git disponibles en el servidor.", + "partitions": "Particiones", + "isDefaultLabel": "Por defecto" +}