From fcfcd58d9384a477a145eb2deddbb4fb3ed959cd Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Fri, 23 May 2025 13:28:22 +0200 Subject: [PATCH] refs #2085. RemoveCacheImage --- ogWebconsole/src/app/app.module.ts | 6 +- .../execute-command.component.ts | 69 ++++++-- .../remove-cache-image.component.css | 117 +++++++++++++ .../remove-cache-image.component.html | 105 ++++++++++++ .../remove-cache-image.component.spec.ts | 78 +++++++++ .../remove-cache-image.component.ts | 154 ++++++++++++++++++ 6 files changed, 515 insertions(+), 14 deletions(-) create mode 100644 ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.css create mode 100644 ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.html create mode 100644 ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.spec.ts create mode 100644 ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.ts diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index bd95fa4..c1c0e66 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -151,6 +151,8 @@ import { CreateTaskScriptComponent } from './components/commands/commands-task/c 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'; import { ClientTaskLogsComponent } from './components/task-logs/client-task-logs/client-task-logs.component'; +import { BootSoPartitionComponent } from './components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component'; +import { RemoveCacheImageComponent } from './components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); @@ -259,7 +261,9 @@ registerLocaleData(localeEs, 'es-ES'); CreateTaskScriptComponent, ViewParametersModalComponent, OutputDialogComponent, - ClientTaskLogsComponent + ClientTaskLogsComponent, + BootSoPartitionComponent, + RemoveCacheImageComponent ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts index 1b871a4..8ac5030 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts @@ -3,6 +3,9 @@ import { HttpClient } from '@angular/common/http'; import { Router } from "@angular/router"; import { ToastrService } from "ngx-toastr"; import { ConfigService } from '@services/config.service'; +import {BootSoPartitionComponent} from "./boot-so-partition/boot-so-partition.component"; +import {MatDialog} from "@angular/material/dialog"; +import {RemoveCacheImageComponent} from "./remove-cache-image/remove-cache-image.component"; @Component({ selector: 'app-execute-command', @@ -27,7 +30,7 @@ export class ExecuteCommandComponent implements OnInit { { translationKey: 'executeCommands.login', slug: 'login', disabled: true }, { translationKey: 'executeCommands.createImage', slug: 'create-image', disabled: false }, { translationKey: 'executeCommands.deployImage', slug: 'deploy-image', disabled: false }, - { translationKey: 'executeCommands.deleteImageCache', slug: 'delete-image-cache', disabled: true }, + { translationKey: 'executeCommands.deleteImageCache', slug: 'remove-cache-image', disabled: false }, { translationKey: 'executeCommands.partition', slug: 'partition', disabled: false }, { translationKey: 'executeCommands.softwareInventory', slug: 'software-inventory', disabled: true }, { translationKey: 'executeCommands.hardwareInventory', slug: 'hardware-inventory', disabled: true }, @@ -40,7 +43,8 @@ export class ExecuteCommandComponent implements OnInit { private http: HttpClient, private router: Router, private configService: ConfigService, - private toastService: ToastrService + private toastService: ToastrService, + private dialog: MatDialog, ) { this.baseUrl = this.configService.apiUrl; } @@ -74,13 +78,13 @@ export class ExecuteCommandComponent implements OnInit { if (states[0] === 'off' || states[0] === 'disconnected') { command.disabled = command.slug !== 'power-on'; } else { - command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'partition', 'run-script'].includes(command.slug); + command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'remove-cache-image', 'partition', 'run-script'].includes(command.slug); } } else { if (command.slug === 'create-image') { command.disabled = multipleClients; } else if ( - ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug) + ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'remove-cache-image', 'run-script'].includes(command.slug) ) { command.disabled = false; } else { @@ -123,6 +127,10 @@ export class ExecuteCommandComponent implements OnInit { if (action === 'power-on') { this.powerOnClient(); } + + if (action === 'remove-cache-image') { + this.removeImageCache(); + } } rebootClient(): void { @@ -139,16 +147,51 @@ export class ExecuteCommandComponent implements OnInit { } loginClient(): void { - this.http.post(`${this.baseUrl}/clients/server/login-client`, { - clients: this.clientData.map((client: any) => client['@id']) - }).subscribe( - response => { - this.toastService.success('Cliente actualizado correctamente'); - }, - error => { - this.toastService.error('Error de conexión con el cliente'); + const clientDataToSend = this.clientData.map(client => ({ + name: client.name, + mac: client.mac, + uuid: '/clients/' + client.uuid, + status: client.status, + partitions: client.partitions, + firmwareType: client.firmwareType, + ip: client.ip + })); + + const dialogRef = this.dialog.open(BootSoPartitionComponent, { + width: '70vw', + height: 'auto', + data: { clients: clientDataToSend } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.toastService.success('Petición de arranque de SO enviada correctamente'); } - ); + }); + } + + removeImageCache(): void { + const clientDataToSend = this.clientData.map(client => ({ + name: client.name, + mac: client.mac, + uuid: '/clients/' + client.uuid, + status: client.status, + partitions: client.partitions, + firmwareType: client.firmwareType, + ip: client.ip + })); + + const dialogRef = this.dialog.open(RemoveCacheImageComponent, { + width: '70vw', + height: 'auto', + data: { clients: clientDataToSend } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.toastService.success('Petición de borrado de caché de imagen enviada correctamente'); + } + }); } powerOnClient(): void { diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.css b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.css new file mode 100644 index 0000000..9f58e44 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.css @@ -0,0 +1,117 @@ +.dialog-content { + display: flex; + flex-direction: column; + padding: 40px; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} + +.select-container { + margin-top: 20px; + align-items: center; + padding: 20px; + box-sizing: border-box; +} + +.clients-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 8px; +} + +.client-card { + background: #ffffff; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; + position: relative; + padding: 8px; + text-align: center; + cursor: pointer; + transition: background-color 0.3s, transform 0.2s; + + &:hover { + background-color: #f0f0f0; + transform: scale(1.02); + } +} + +.button-row { + display: flex; + padding-right: 1em; +} + +.action-button { + margin-top: 10px; + margin-bottom: 10px; +} + +.client-item { + position: relative; +} + +.mat-expansion-panel-header-description { + justify-content: space-between; + align-items: center; +} + +.selected-client { + background-color: #a0c2e5 !important; + color: white !important; +} + +.loading-spinner { + display: block; + margin: 0 auto; + align-items: center; + justify-content: center; +} + +.client-details { + margin-top: 4px; +} + +.client-name { + font-size: 0.9em; + font-weight: 600; + color: #333; + margin-bottom: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; + display: inline-block; +} + +.client-ip { + display: block; + font-size: 0.9em; + color: #666; +} + + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +@media (max-width: 600px) { + .form-field { + width: 100%; + } + + .dialog-actions { + flex-direction: column; + align-items: stretch; + } + + button { + width: 100%; + margin-left: 0; + margin-bottom: 8px; + } +} diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.html b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.html new file mode 100644 index 0000000..78f4df5 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.html @@ -0,0 +1,105 @@ +

Seleccionar imagen para eliminar de la cache

+ + + + +
+ + + Clientes + + Listado de clientes para arrancar un SO + desktop_windows + + + +
+ +
+ +
+
+
+ + Client Icon + +
+ {{ client.name | slice:0:20 }} + {{ client.ip }} + {{ client.mac }} +
+ + + + + Modelo + + +
+
+
+ +
+
+ + + +
+ + + + + + + + + + + + + +
Seleccionar imagen + + + + + {{ column.header }} + + {{ column.cell(image) }} + + + +
+ {{ image.size }} MB + {{ image.size / 1024 }} GB +
+
+ + +
+ {{ image.operativeSystem?.name }} + {{ image.image?.name}} +
+
+ +
+
+
+ +
+ + +
diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.spec.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.spec.ts new file mode 100644 index 0000000..3d300a7 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.spec.ts @@ -0,0 +1,78 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RemoveCacheImageComponent } from './remove-cache-image.component'; +import {BootSoPartitionComponent} from "../boot-so-partition/boot-so-partition.component"; +import {FormBuilder, FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatInputModule} from "@angular/material/input"; +import {MatCheckboxModule} from "@angular/material/checkbox"; +import {MatButtonModule} from "@angular/material/button"; +import {MatMenuModule} from "@angular/material/menu"; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {MatTableModule} from "@angular/material/table"; +import {MatSelectModule} from "@angular/material/select"; +import {MatIconModule} from "@angular/material/icon"; +import {ToastrModule, ToastrService} from "ngx-toastr"; +import {TranslateModule} from "@ngx-translate/core"; +import {DataService} from "../../data.service"; +import {provideHttpClient} from "@angular/common/http"; +import {provideHttpClientTesting} from "@angular/common/http/testing"; +import {ConfigService} from "@services/config.service"; + +describe('RemoveCacheImageComponent', () => { + let component: RemoveCacheImageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url', + mercureUrl: 'http://mock-mercure-url' + }; + + await TestBed.configureTestingModule({ + declarations: [RemoveCacheImageComponent], + imports: [ + ReactiveFormsModule, + FormsModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + MatCheckboxModule, + MatButtonModule, + MatMenuModule, + BrowserAnimationsModule, + MatTableModule, + MatSelectModule, + MatIconModule, + ToastrModule.forRoot(), + TranslateModule.forRoot() + ], + providers: [ + FormBuilder, + ToastrService, + DataService, + provideHttpClient(), + provideHttpClientTesting(), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: {} + }, + { provide: ConfigService, useValue: mockConfigService } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(RemoveCacheImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.ts new file mode 100644 index 0000000..c650a3b --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.ts @@ -0,0 +1,154 @@ +import {Component, Inject} from '@angular/core'; +import {MatTableDataSource} from "@angular/material/table"; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {ConfigService} from "@services/config.service"; +import {HttpClient} from "@angular/common/http"; +import {ToastrService} from "ngx-toastr"; + +@Component({ + selector: 'app-remove-cache-image', + templateUrl: './remove-cache-image.component.html', + styleUrl: './remove-cache-image.component.css' +}) +export class RemoveCacheImageComponent { + baseUrl: string; + selectedPartition: any = null; + dataSource = new MatTableDataSource(); + clientId: string | null = null; + selectedClients: any[] = []; + selectedModelClient: any = null; + filteredPartitions: any[] = []; + allSelected: boolean = false; + clientData: any[] = []; + loading: boolean = false; + columns = [ + { + columnDef: 'diskNumber', + header: 'Disco', + cell: (partition: any) => partition.diskNumber + }, + { + columnDef: 'partitionNumber', + header: 'Particion', + cell: (partition: any) => partition.partitionNumber + }, + { + columnDef: 'size', + header: 'Tamaño', + cell: (partition: any) => `${partition.size} MB` + }, + { + columnDef: 'partitionCode', + header: 'Tipo de partición', + cell: (partition: any) => partition.partitionCode + }, + { + columnDef: 'filesystem', + header: 'Sistema de ficheros', + cell: (partition: any) => partition.filesystem + }, + { + columnDef: 'operativeSystem', + header: 'SO', + cell: (partition: any) => partition.operativeSystem?.name + } + ]; + + displayedColumns = ['select', ...this.columns.map(column => column.columnDef)]; + + constructor( + @Inject(MAT_DIALOG_DATA) public data: { clients: any }, + private dialogRef: MatDialogRef, + private configService: ConfigService, + private http: HttpClient, + private toastService: ToastrService, + ) { + this.baseUrl = this.configService.apiUrl; + this.clientId = this.data.clients?.length ? this.data.clients[0]['@id'] : null; + + this.data.clients.forEach((client: { selected: boolean; status: string }) => { + if (client.status === 'og-live') { + client.selected = true; + } + }); + + this.selectedClients = this.data.clients.filter( + (client: { status: string }) => client.status === 'og-live' + ); + + this.selectedModelClient = this.data.clients.find( + (client: { status: string }) => client.status === 'og-live' + ) || null; + + if (this.selectedModelClient) { + this.loadPartitions(this.selectedModelClient); + } + } + + ngOnInit() { + + } + + loadPartitions(client: any) { + const url = `${this.baseUrl}${client.uuid}`; + this.http.get(url).subscribe( + (response: any) => { + if (response.partitions) { + this.dataSource.data = response.partitions.filter((partition: any) => { + return partition.partitionNumber !== 0 && partition.image; + }); + } + }, + (error) => { + console.error('Error al cargar los datos del cliente:', error); + } + ); + } + + toggleClientSelection(client: any) { + client.selected = !client.selected; + this.updateSelectedClients(); + } + + updateSelectedClients() { + this.selectedClients = this.data.clients.filter( + (client: { selected: boolean; state: string }) => client.selected && client.state === "og-live" + ); + + if (!this.selectedClients.includes(this.selectedModelClient)) { + this.selectedModelClient = null; + this.filteredPartitions = []; + } + } + + toggleSelectAll() { + this.allSelected = !this.allSelected; + this.data.clients.forEach((client: { selected: boolean; status: string }) => { + if (client.status === "og-live") { + client.selected = this.allSelected; + } + }); + } + + close() { + this.dialogRef.close(); + } + + execute(): void { + this.loading = true; + this.http.post(`${this.baseUrl}/clients/server/remove-cache-image`, { + clients: this.selectedClients.map((client: any) => client.uuid), + partition: this.selectedPartition['@id'] + }).subscribe( + response => { + this.toastService.success('Cliente actualizado correctamente'); + this.dialogRef.close(); + this.loading = false; + }, + error => { + this.toastService.error(error.error['hydra:description']); + this.loading = false; + } + ); + } +}