diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index a079e76..2d13964 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -127,6 +127,8 @@ import { CreateMultipleClientComponent } from './components/groups/shared/client import { ExportImageComponent } from './components/images/export-image/export-image.component'; import {ImportImageComponent} from "./components/repositories/import-image/import-image.component"; import { LoadingComponent } from './shared/loading/loading.component'; +import { RepositoryImagesComponent } from './components/repositories/repository-images/repository-images.component'; +import { InputDialogComponent } from './components/commands/commands-task/task-logs/input-dialog/input-dialog.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); } @@ -212,6 +214,8 @@ export function HttpLoaderFactory(http: HttpClient) { ExportImageComponent, ImportImageComponent, LoadingComponent, + RepositoryImagesComponent, + InputDialogComponent, ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.css b/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.html b/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.html new file mode 100644 index 0000000..e12c176 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.html @@ -0,0 +1,7 @@ +

{{ 'inputDetails' | translate }}

+
+
{{ data.input | json }}
+
+
+ +
diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.spec.ts b/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.spec.ts new file mode 100644 index 0000000..7168136 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.spec.ts @@ -0,0 +1,47 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InputDialogComponent } from './input-dialog.component'; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog"; +import {FormBuilder} from "@angular/forms"; +import {ToastrService} from "ngx-toastr"; +import {provideHttpClient} from "@angular/common/http"; +import {provideHttpClientTesting} from "@angular/common/http/testing"; +import {TranslateModule} from "@ngx-translate/core"; + +describe('InputDialogComponent', () => { + let component: InputDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [InputDialogComponent], + imports: [ + MatDialogModule, + TranslateModule.forRoot(), + ], + providers: [ + FormBuilder, + ToastrService, + provideHttpClient(), + provideHttpClientTesting(), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: {} + } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(InputDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.ts b/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.ts new file mode 100644 index 0000000..744b415 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-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-input-dialog', + templateUrl: './input-dialog.component.html', + styleUrl: './input-dialog.component.css' +}) +export class InputDialogComponent { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { input: any } + ) {} + + close(): void { + this.dialogRef.close(); + } +} diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html index f26735f..cded0da 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html +++ b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html @@ -61,30 +61,42 @@ {{ column.header }} - - - {{ - trace.status === 'failed' ? 'Fallido' : - trace.status === 'success' ? 'Finalizado con éxito' : - trace.status === 'pending' ? 'Pendiente de ejecutar' : - trace.status === 'in-progress' ? 'Ejecutando' : - trace.status - }} - - + + + + + {{ + trace.status === 'failed' ? 'Fallido' : + trace.status === 'success' ? 'Finalizado con éxito' : + trace.status === 'pending' ? 'Pendiente de ejecutar' : + trace.status === 'in-progress' ? 'Ejecutando' : + trace.status + }} + + - - {{ column.cell(trace) }} - + + + + + + + + {{ column.cell(trace) }} + + + @@ -94,4 +106,4 @@ - \ No newline at end of file + diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts index d605f41..e78cb96 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts +++ b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts @@ -5,6 +5,8 @@ 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"; @Component({ selector: 'app-task-logs', @@ -50,6 +52,11 @@ export class TaskLogsComponent implements OnInit { header: 'Hilo de trabajo', cell: (trace: any) => `${trace.jobId}` }, + { + columnDef: 'input', + header: 'Input', + cell: (trace: any) => `${trace.input}` + }, { columnDef: 'output', header: 'Logs', @@ -75,7 +82,9 @@ export class TaskLogsComponent implements OnInit { commandControl = new FormControl(); constructor(private http: HttpClient, - private joyrideService: JoyrideService) {} + private joyrideService: JoyrideService, + private dialog: MatDialog + ) {} ngOnInit(): void { this.loadTraces(); @@ -121,6 +130,13 @@ export class TaskLogsComponent implements OnInit { this.loadTraces(); } + openInputModal(inputData: any): void { + this.dialog.open(InputDialogComponent, { + width: '700px', + data: { input: inputData } + }); + } + loadTraces(): void { this.loading = true; const url = `${this.baseUrl}/traces?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`; diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html index a68c21d..0fd1c85 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html @@ -52,8 +52,9 @@ Seleccione imagen - {{ image.name }} + {{ image.image?.name }} + Imágenes alojadas en {{ clientData[0].repository?.name }} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts index caaa02d..841f960 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts @@ -19,7 +19,7 @@ export class DeployImageComponent { partitions: any[] = []; images: any[] = []; clientName: string = ''; - selectedImage: string | null = null; + selectedImage: any = null; selectedOption: string | null = 'deploy-image'; selectedMethod: string | null = null; selectedPartition: any = null; @@ -139,10 +139,25 @@ export class DeployImageComponent { } loadImages() { - const url = `${this.baseUrl}/images?status=success&page=1&itemsPerPage=1000`; + if (!this.clientData || this.clientData.length === 0 || !this.clientData[0]) { + console.error('Error: clientData es nulo, indefinido o vacío.'); + return; + } + + const repositoryId = + this.clientData[0]?.repository?.id ?? + this.clientData[0]?.organizationalUnit?.networkSettings?.repository?.id; + + if (!repositoryId) { + console.error('Error: No se encontró repositoryId en clientData.'); + return; + } + + const url = `${this.baseUrl}/image-image-repositories?status=success&repository.id=${repositoryId}&page=1&itemsPerPage=1000`; + this.http.get(url).subscribe( (response: any) => { - this.images = response['hydra:member']; + this.images = response?.['hydra:member'] || []; }, (error) => { console.error('Error al cargar las imágenes:', error); @@ -189,7 +204,7 @@ export class DeployImageComponent { maxClients: this.mcastMaxClients, }; - this.http.post(`${this.baseUrl}${this.selectedImage}/deploy-image`, payload) + this.http.post(`${this.baseUrl}/image-image-repositories/${this.selectedImage.uuid}/deploy-image`, payload) .subscribe({ next: (response) => { this.toastService.success('Petición de despliegue enviada correctamente'); diff --git a/ogWebconsole/src/app/components/images/create-image/create-image.component.html b/ogWebconsole/src/app/components/images/create-image/create-image.component.html index baea718..2f3a3a2 100644 --- a/ogWebconsole/src/app/components/images/create-image/create-image.component.html +++ b/ogWebconsole/src/app/components/images/create-image/create-image.component.html @@ -10,7 +10,7 @@ {{ 'repositoryLabel' | translate }} - + {{ imageRepository.name }} diff --git a/ogWebconsole/src/app/components/images/create-image/create-image.component.ts b/ogWebconsole/src/app/components/images/create-image/create-image.component.ts index 00ed9cb..793ec40 100644 --- a/ogWebconsole/src/app/components/images/create-image/create-image.component.ts +++ b/ogWebconsole/src/app/components/images/create-image/create-image.component.ts @@ -54,7 +54,7 @@ export class CreateImageComponent implements OnInit { remotePc: [response.remotePc], isGlobal: [response.isGlobal], softwareProfile: [response.softwareProfile ? response.softwareProfile['@id'] : null, Validators.required], - imageRepositories: [response.imageRepositories ? response.imageRepositories.map((r: any) => r['@id']) : [], Validators.required], + imageRepositories: [response.imageRepositories ? response.imageRepositories.map((r: any) => r.imageRepository['@id']) : [], Validators.required], }); this.imageId = response['@id']; this.partitionInfo = response.partitionInfo; diff --git a/ogWebconsole/src/app/components/images/images.component.html b/ogWebconsole/src/app/components/images/images.component.html index ec22f52..989b53a 100644 --- a/ogWebconsole/src/app/components/images/images.component.html +++ b/ogWebconsole/src/app/components/images/images.component.html @@ -25,18 +25,6 @@ search {{ 'searchHint' | translate }} - - Estado - - Fallido - Pendiente - Transfiriendo - Creado con éxito - En progreso - Papelera - Creando archivos auxiliares - - Ver repositorios @@ -62,19 +50,7 @@ {{ image[column.columnDef] ? 'check_circle' : 'cancel' }} - - - {{ getStatusLabel(image[column.columnDef]) }} - - - + {{ column.cell(image) }} @@ -83,24 +59,15 @@ @@ -112,4 +79,4 @@ - \ No newline at end of file + diff --git a/ogWebconsole/src/app/components/images/images.component.ts b/ogWebconsole/src/app/components/images/images.component.ts index 6380e6c..aa3cfb3 100644 --- a/ogWebconsole/src/app/components/images/images.component.ts +++ b/ogWebconsole/src/app/components/images/images.component.ts @@ -52,11 +52,6 @@ export class ImagesComponent implements OnInit { header: 'Imagen Global', cell: (image: any) => `${image.isGlobal}` }, - { - columnDef: 'status', - header: 'Estado', - cell: (image: any) => `${image.status}` - }, { columnDef: 'createdAt', header: 'Fecha de creación', @@ -96,27 +91,6 @@ export class ImagesComponent implements OnInit { ) } - getStatusLabel(status: string): string { - switch (status) { - case 'pending': - return 'Pendiente'; - case 'in-progress': - return 'En progreso'; - case 'aux-file-pending': - return 'Archivos auxiliares pendientes'; - case 'success': - return 'Creado con éxito'; - case 'trash': - return 'Papelera temporal'; - case 'failed': - return 'Fallido'; - case 'trasnferring': - return 'Transfiriendo'; - default: - return 'Estado desconocido'; - } - } - addImage(): void { const dialogRef = this.dialog.open(CreateImageComponent, { width: '800px' @@ -157,78 +131,10 @@ export class ImagesComponent implements OnInit { this.search(); } - loadImageAlert(image: any): Observable { - return this.http.get(`${this.apiUrl}/server/${image.uuid}/get`, {}); - } - - showImageInfo(event: MouseEvent, image:any) { - event.stopPropagation(); - this.loading = true; - this.loadImageAlert(image).subscribe( - response => { - this.alertMessage = response; - - this.dialog.open(ServerInfoDialogComponent, { - width: '800px', - data: { - message: this.alertMessage - } - }); - this.loading = false; - }, - error => { - this.toastService.error(error.error['hydra:description']); - this.loading = false; - } - ); - } - toggleAction(image: any, action:string): void { switch (action) { - case 'get-aux': - this.http.post(`${this.baseUrl}/images/server/${image.uuid}/create-aux-files`, {}).subscribe({ - next: () => { - this.toastService.success('Petición de creación de archivos auxiliares enviada'); - this.search() - }, - error: (error) => { - this.toastService.error(error.error['hydra:description']); - } - }); - break; case 'delete-trash': - if (!image.imageFullsum) { - const dialogRef = this.dialog.open(DeleteModalComponent, { - width: '400px', - data: { name: image.name }, - }); - - dialogRef.afterClosed().subscribe((result) => { - this.http.delete(`${this.baseUrl}${image['@id']}`).subscribe({ - next: () => { - this.toastService.success('Image deleted successfully'); - this.search() - }, - error: (error) => { - this.toastService.error('Error deleting image'); - } - }); - }); - } else { - this.http.post(`${this.baseUrl}/images/server/${image.uuid}/delete-trash`, {}).subscribe({ - next: () => { - this.toastService.success('Petición de eliminación de la papelera temporal enviada'); - this.search() - }, - error: (error) => { - this.toastService.error(error.error['hydra:description']); - } - }); - } - - break; - case 'delete-permanent': - this.http.post(`${this.baseUrl}/images/server/${image.uuid}/delete-permanent`, {}).subscribe({ + this.http.post(`${this.baseUrl}/images/server/${image.uuid}/delete-trash`, {}).subscribe({ next: () => { this.toastService.success('Petición de eliminación de la papelera temporal enviada'); this.search() @@ -238,10 +144,10 @@ export class ImagesComponent implements OnInit { } }); break; - case 'recover': - this.http.post(`${this.baseUrl}/images/server/${image.uuid}/recover`, {}).subscribe({ + case 'delete-permanent': + this.http.post(`${this.baseUrl}/images/server/${image.uuid}/delete-permanent`, {}).subscribe({ next: () => { - this.toastService.success('Petición de recuperación de la imagen enviada'); + this.toastService.success('Petición de eliminación de la papelera temporal enviada'); this.search() }, error: (error) => { diff --git a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html index e27dbc9..62a05fc 100644 --- a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html +++ b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html @@ -113,6 +113,6 @@ - + \ No newline at end of file diff --git a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts index 15f3c75..b756e28 100644 --- a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts +++ b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts @@ -119,7 +119,6 @@ export class MainRepositoryViewComponent implements OnInit { comments: [response.comments], }); this.loading = false; - this.searchImages(); }, (error) => { console.error('Error al cargar los datos del cliente:', error); @@ -207,21 +206,6 @@ export class MainRepositoryViewComponent implements OnInit { return this.processesStatus ? this.processesStatus : []; } - searchImages(): void { - this.loading = true; - this.http.get(`${this.apiUrl}?repositoryId=${this.repositoryData.id}&page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe( - data => { - this.dataSource.data = data['hydra:member']; - this.length = data['hydra:totalItems']; - this.loading = false; - }, - error => { - console.error('Error fetching images', error); - this.loading = false; - } - ); - } - loadAlert(): Observable { return this.http.get(`${this.baseUrl}/image-repositories/server/get-collection`); } diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.css b/ogWebconsole/src/app/components/repositories/repositories.component.css index 053704a..d7f88b1 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.css +++ b/ogWebconsole/src/app/components/repositories/repositories.component.css @@ -95,4 +95,21 @@ table { .example-button-row .mat-mdc-button-base { margin: 8px 8px 8px 0; -} \ No newline at end of file + +} + +.header-container-title { + flex-grow: 1; + text-align: left; + margin-left: 1em; +} + +.chip-success { + background-color: #63d165 !important; + color: white ; +} + +.chip-failed { + background-color: #ff6b63 !important; + color: white; +} diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.html b/ogWebconsole/src/app/components/repositories/repositories.component.html index 4433542..1a51e54 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.html +++ b/ogWebconsole/src/app/components/repositories/repositories.component.html @@ -37,8 +37,19 @@ @@ -61,4 +72,4 @@ - \ No newline at end of file + diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.ts b/ogWebconsole/src/app/components/repositories/repositories.component.ts index 58e85f3..eedb828 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.ts +++ b/ogWebconsole/src/app/components/repositories/repositories.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import {Component, OnInit} from '@angular/core'; import {MatTableDataSource} from "@angular/material/table"; import {DatePipe} from "@angular/common"; import {MatDialog} from "@angular/material/dialog"; @@ -8,14 +8,13 @@ import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delet import { JoyrideService } from 'ngx-joyride'; import {CreateRepositoryComponent} from "./create-repository/create-repository.component"; import { Router } from '@angular/router'; -import {ImportImageComponent} from "./import-image/import-image.component"; @Component({ selector: 'app-repositories', templateUrl: './repositories.component.html', styleUrl: './repositories.component.css' }) -export class RepositoriesComponent { +export class RepositoriesComponent implements OnInit { baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; dataSource = new MatTableDataSource(); length: number = 0; @@ -40,6 +39,11 @@ export class RepositoriesComponent { header: 'Ip', cell: (repository: any) => `${repository.ip}` }, + { + columnDef: 'status', + header: 'Estado', + cell: (repository: any) => `${repository.status}` + }, { columnDef: 'createdAt', header: 'Fecha de creación', diff --git a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.css b/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.css new file mode 100644 index 0000000..af5ea91 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.css @@ -0,0 +1,104 @@ +.title { + font-size: 24px; +} + +.images-button-row { + display: flex; + justify-content: flex-start; + margin-top: 16px; +} + +.divider { + margin: 20px 0; +} + +table { + width: 100%; + margin-top: 50px; +} + +.search-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 0 5px; + box-sizing: border-box; +} + +.search-string { + flex: 2; + padding: 5px; +} + +.search-boolean { + flex: 1; + padding: 5px; +} + +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +.paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} + +.example-headers-align .mat-expansion-panel-header-description { + justify-content: space-between; + align-items: center; +} + +.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field { + margin-left: 8px; +} + +.button-row { + display: table-cell; + max-width: 600px; +} + +.button-row .mat-mdc-button-base { + margin: 8px 8px 8px 0; +} + +.chip-failed { + background-color: #e87979 !important; + color: white; +} + +.chip-success { + background-color: #46c446 !important; + color: white; +} + +.chip-pending { + background-color: lightgrey !important; + color: black; +} + +.chip-in-progress { + background-color: #f5a623 !important; + color: white; +} + +.chip-transferring { + background-color: #f5a623 !important; + color: white; +} + +.header-container-title { + flex-grow: 1; + text-align: left; + margin-left: 1em; +} + diff --git a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.html b/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.html new file mode 100644 index 0000000..8347221 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.html @@ -0,0 +1,93 @@ + + +
+ +
+

+ {{ 'imagesTitle' | translate }} en {{ repository?.name }} +

+
+
+ + + +
+ + {{ 'searchLabel' | translate }} + + search + {{ 'searchHint' | translate }} + + + Estado + + Fallido + Pendiente + Transfiriendo + Creado con éxito + En progreso + Papelera + Creando archivos auxiliares + + +
+ +
Acciones - - - + - - - - + {{ column.header }} - - {{ column.cell(repository) }} + + + + {{ repository.status === true ? 'Conectado' : 'Fallido' }} + + + + + {{ column.cell(repository) }} +
+ + + + + + + + + + + +
{{ column.header }} + + + {{ image[column.columnDef] ? 'check_circle' : 'cancel' }} + + + + + {{ getStatusLabel(image[column.columnDef]) }} + + + + {{ column.cell(image) }} + + Acciones + + + + + + + + + +
+ +
+ + +
+ diff --git a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.spec.ts b/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.spec.ts new file mode 100644 index 0000000..c18a2a3 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.spec.ts @@ -0,0 +1,78 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { ReactiveFormsModule, FormsModule, FormBuilder } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatTableModule } from '@angular/material/table'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatIconModule } from '@angular/material/icon'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateModule } from '@ngx-translate/core'; +import { ToastrModule, ToastrService } from 'ngx-toastr'; +import { JoyrideModule } from 'ngx-joyride'; +import { CommonModule } from '@angular/common'; +import {MatProgressSpinner} from "@angular/material/progress-spinner"; +import {RepositoryImagesComponent} from "./repository-images.component"; +import {LoadingComponent} from "../../../shared/loading/loading.component"; +import {MatOptionModule} from "@angular/material/core"; +import {MatSelectModule} from "@angular/material/select"; + +describe('RepositoriesComponent', () => { + let component: RepositoryImagesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [RepositoryImagesComponent, LoadingComponent], + imports: [ + ReactiveFormsModule, + FormsModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + MatCheckboxModule, + MatButtonModule, + MatTableModule, + MatPaginatorModule, + MatDividerModule, + MatIconModule, + MatOptionModule, + BrowserAnimationsModule, + MatProgressSpinner, + MatSelectModule , + ToastrModule.forRoot(), + TranslateModule.forRoot(), + JoyrideModule.forRoot(), + CommonModule + ], + providers: [ + FormBuilder, + ToastrService, + provideHttpClient(), + provideHttpClientTesting(), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: {} + } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(RepositoryImagesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.ts b/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.ts new file mode 100644 index 0000000..0362379 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.ts @@ -0,0 +1,257 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {MatTableDataSource} from "@angular/material/table"; +import {DatePipe} from "@angular/common"; +import {MatDialog} from "@angular/material/dialog"; +import {HttpClient} from "@angular/common/http"; +import {ToastrService} from "ngx-toastr"; +import {JoyrideService} from "ngx-joyride"; +import {CreateImageComponent} from "../../images/create-image/create-image.component"; +import {Observable} from "rxjs"; +import {ServerInfoDialogComponent} from "../../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component"; +import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/delete-modal.component"; +import {ExportImageComponent} from "../../images/export-image/export-image.component"; + +@Component({ + selector: 'app-repository-images', + templateUrl: './repository-images.component.html', + styleUrl: './repository-images.component.css' +}) +export class RepositoryImagesComponent implements OnInit { + baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; + dataSource = new MatTableDataSource(); + length: number = 0; + itemsPerPage: number = 10; + page: number = 0; + loading: boolean = false; + filters: { [key: string]: string } = {}; + alertMessage: string | null = null; + repository: any = {}; + datePipe: DatePipe = new DatePipe('es-ES'); + columns = [ + { + columnDef: 'id', + header: 'Id', + cell: (image: any) => `${image.id}` + }, + { + columnDef: 'image.image.name', + header: 'Nombre de imagen', + cell: (image: any) => `${image.image.name}` + }, + { + columnDef: 'remotePc', + header: 'Remote Pc', + cell: (image: any) => `${image.remotePc}` + }, + { + columnDef: 'status', + header: 'Estado', + cell: (image: any) => `${image.status}` + }, + { + columnDef: 'createdAt', + header: 'Fecha de creación', + cell: (image: any) => `${this.datePipe.transform(image.createdAt, 'dd/MM/yyyy hh:mm:ss')}` + } + ]; + displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; + + private apiUrl = `${this.baseUrl}/image-image-repositories`; + @Input() repositoryUuid: any + private repositoryId: any; + + constructor( + public dialog: MatDialog, + private http: HttpClient, + private toastService: ToastrService, + private joyrideService: JoyrideService, + ) {} + + ngOnInit(): void { + if (this.repositoryUuid) { + this.loadRepository() + } else { + this.search(); + } + } + + loadRepository(): void { + this.http.get(`${this.baseUrl}/image-repositories/${this.repositoryUuid}`, {}).subscribe( + data => { + this.repositoryId = data.id; + this.repository = data + this.search(); + }, + error => { + console.error('Error fetching image repositories', error); + } + ) + } + + getStatusLabel(status: string): string { + switch (status) { + case 'pending': + return 'Pendiente'; + case 'in-progress': + return 'En progreso'; + case 'aux-files-pending': + return 'Archivos auxiliares pendientes'; + case 'success': + return 'Creado con éxito'; + case 'trash': + return 'Papelera temporal'; + case 'failed': + return 'Fallido'; + case 'transferring': + return 'Transfiriendo'; + default: + return 'Estado desconocido'; + } + } + + search(): void { + this.loading = true; + this.http.get(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}&repositoryId=${this.repositoryId}`, { params: this.filters }).subscribe( + data => { + this.dataSource.data = data['hydra:member']; + this.length = data['hydra:totalItems']; + this.loading = false; + }, + error => { + console.error('Error fetching images', error); + this.loading = false; + } + ); + } + + onPageChange(event: any): void { + this.page = event.pageIndex; + this.itemsPerPage = event.pageSize; + this.length = event.length; + this.search(); + } + + loadImageAlert(image: any): Observable { + return this.http.get(`${this.apiUrl}/server/${image.uuid}/get`, {}); + } + + showImageInfo(event: MouseEvent, image:any) { + event.stopPropagation(); + this.loading = true; + this.loadImageAlert(image).subscribe( + response => { + this.alertMessage = response; + + this.dialog.open(ServerInfoDialogComponent, { + width: '800px', + data: { + message: this.alertMessage + } + }); + this.loading = false; + }, + error => { + this.toastService.error(error.error['hydra:description']); + this.loading = false; + } + ); + } + + toggleAction(image: any, action:string): void { + switch (action) { + case 'get-aux': + this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/create-aux-files`, {}).subscribe({ + next: () => { + this.toastService.success('Petición de creación de archivos auxiliares enviada'); + this.search() + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }); + break; + case 'delete-trash': + if (!image.imageFullsum) { + const dialogRef = this.dialog.open(DeleteModalComponent, { + width: '400px', + data: { name: image.name }, + }); + + dialogRef.afterClosed().subscribe((result) => { + this.http.delete(`${this.baseUrl}${image['@id']}`).subscribe({ + next: () => { + this.toastService.success('Image deleted successfully'); + this.search() + }, + error: (error) => { + this.toastService.error('Error deleting image'); + } + }); + }); + } else { + this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/delete-trash`, + { repository: `/image-repositories/${this.repositoryUuid}` }) + .subscribe({ + next: () => { + this.toastService.success('Petición de eliminación de la papelera temporal enviada'); + this.search() + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }); + } + + break; + case 'delete-permanent': + this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/delete-permanent`, {}).subscribe({ + next: () => { + this.toastService.success('Petición de eliminación de la papelera temporal enviada'); + this.search() + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }); + break; + case 'recover': + this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/recover`, {}).subscribe({ + next: () => { + this.toastService.success('Petición de recuperación de la imagen enviada'); + this.search() + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }); + break; + case 'transfer': + this.dialog.open(ExportImageComponent, { + width: '600px', + data: { + image: image + } + }); + break; + default: + console.error('Acción no soportada:', action); + break; + } + } + iniciarTour(): void { + this.joyrideService.startTour({ + steps: [ + 'imagesTitleStep', + 'addImageButton', + 'searchImagesField', + 'imagesTable', + 'actionsHeader', + 'editImageButton', + 'deleteImageButton', + 'imagesPagination' + ], + showPrevButton: true, + themeColor: '#3f51b5' + }); + } +} diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index 08ac54e..d3a179c 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -181,6 +181,7 @@ "viewTreeTitle": "View organizational unit tree", "toggleNodeAriaLabel": "Toggle node", "closeButton": "Close", + "inputDetails": "Input details", "orgUnitPropertiesTitle": "Organizational unit properties", "generalDataTab": "General data", "propertyHeader": "Property", diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index dd1f26a..6a58707 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -240,6 +240,7 @@ "repositoryLabel": "Repositorios", "serialNumberLabel": "Número de Serie", "netifaceLabel": "Interfaz de red", + "inputDetails": "Detalles", "netDriverLabel": "Controlador de red", "macLabel": "MAC", "macError": "Formato de MAC inválido. Ejemplo válido: 00:11:22:33:44:55",