refs #1693. Convert Image

pull/18/head
Manuel Aranda Rosales 2025-03-11 17:10:04 +01:00
parent d2b3c8f772
commit 16c367e770
12 changed files with 195 additions and 52 deletions

View File

@ -129,6 +129,7 @@ import { AddClientsToSubnetComponent } from "./components/ogdhcp/add-clients-to-
import { ShowClientsComponent } from './components/ogdhcp/show-clients/show-clients.component'; import { ShowClientsComponent } from './components/ogdhcp/show-clients/show-clients.component';
import { OperationResultDialogComponent } from './components/ogdhcp/operation-result-dialog/operation-result-dialog.component'; import { OperationResultDialogComponent } from './components/ogdhcp/operation-result-dialog/operation-result-dialog.component';
import { ManageClientComponent } from './components/groups/shared/clients/manage-client/manage-client.component'; import { ManageClientComponent } from './components/groups/shared/clients/manage-client/manage-client.component';
import { ConvertImageComponent } from './components/repositories/convert-image/convert-image.component';
export function HttpLoaderFactory(http: HttpClient) { export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './locale/', '.json'); return new TranslateHttpLoader(http, './locale/', '.json');
} }
@ -213,7 +214,8 @@ export function HttpLoaderFactory(http: HttpClient) {
ManageOrganizationalUnitComponent, ManageOrganizationalUnitComponent,
BackupImageComponent, BackupImageComponent,
ShowClientsComponent, ShowClientsComponent,
OperationResultDialogComponent OperationResultDialogComponent,
ConvertImageComponent
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
imports: [BrowserModule, imports: [BrowserModule,

View File

@ -0,0 +1,39 @@
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100px;
}
mat-form-field {
width: 100%;
}
mat-dialog-actions {
display: flex;
justify-content: flex-end;
}
.checkbox-group {
display: flex;
flex-direction: column;
gap: 10px;
}
.selected-list ul {
list-style: none;
padding: 0;
}
.selected-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-bottom: 1px solid #ccc;
}
.selected-item button {
margin-left: 10px;
}

View File

@ -0,0 +1,15 @@
<h2 mat-dialog-title>Convertir imagene virtual hacia {{ data.name }}</h2>
<mat-dialog-content>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Imagen</mat-label>
<input matInput [(ngModel)]="imageName" placeholder="Introduzca el nombre de la imagen a importar."
/>
<mat-hint>El nombre de la imagen tiene que ir con la extensión. </mat-hint>
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions>
<button class="ordinary-button" (click)="close()">Cancelar</button>
<button class="submit-button" (click)="save()">Continuar</button>
</mat-dialog-actions>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ConvertImageComponent } from './convert-image.component';
describe('ConvertImageComponent', () => {
let component: ConvertImageComponent;
let fixture: ComponentFixture<ConvertImageComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ConvertImageComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ConvertImageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,50 @@
import {Component, Inject} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {ToastrService} from "ngx-toastr";
import {Router} from "@angular/router";
@Component({
selector: 'app-convert-image',
templateUrl: './convert-image.component.html',
styleUrl: './convert-image.component.css'
})
export class ConvertImageComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
loading: boolean = true;
imageName: string = '';
constructor(
private http: HttpClient,
public dialogRef: MatDialogRef<ConvertImageComponent>,
private toastService: ToastrService,
private router: Router,
@Inject(MAT_DIALOG_DATA) public data: { repositoryUuid: any, name: string }
) {
}
ngOnInit(): void {
this.loading = true;
}
save() {
console.log(this.data?.repositoryUuid)
this.http.post<any>(`${this.baseUrl}/image-repositories/${this.data?.repositoryUuid}/convert-image`, {
name: this.imageName
}).subscribe({
next: (response) => {
this.toastService.success('Peticion de conversion de imagen enviada correctamente');
this.dialogRef.close();
this.router.navigate(['/commands-logs']);
},
error: error => {
this.toastService.error(error.error['hydra:description']);
}
});
}
close() {
this.dialogRef.close();
}
}

View File

@ -1,25 +1,12 @@
<h2 mat-dialog-title>Importar imagenes a {{data.repository?.name}}</h2> <h2 mat-dialog-title>Importar imagenes a {{ data.name }}</h2>
<mat-dialog-content> <mat-dialog-content>
<mat-form-field appearance="fill" class="full-width"> <mat-form-field appearance="fill" class="full-width">
<mat-label>Seleccione imagenes a importar</mat-label> <mat-label>Imagen</mat-label>
<mat-select [(value)]="selectedClients" multiple> <input matInput [(ngModel)]="imageName" placeholder="Introduzca el nombre de la imagen a importar."
<mat-option *ngFor="let image of images" [value]="image['@id']">{{ image.name }}</mat-option> />
</mat-select> <mat-hint>El nombre de la imagen tiene que ir sin la extensión. </mat-hint>
</mat-form-field> </mat-form-field>
<div *ngIf="selectedClients.length > 0" class="selected-list">
<h3>Imágenes seleccionadas:</h3>
<ul>
<li *ngFor="let imageId of selectedClients" class="selected-item">
<span>{{ getImageName(imageId) }}</span>
<button mat-icon-button color="warn" (click)="removeImage(imageId)">
<mat-icon>delete</mat-icon>
</button>
</li>
</ul>
</div>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions> <mat-dialog-actions>

View File

@ -12,46 +12,26 @@ import {Router} from "@angular/router";
export class ImportImageComponent implements OnInit{ export class ImportImageComponent implements OnInit{
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
loading: boolean = true; loading: boolean = true;
images: any[] = []; imageName: string = '';
selectedClients: any[] = [];
constructor( constructor(
private http: HttpClient, private http: HttpClient,
public dialogRef: MatDialogRef<ImportImageComponent>, public dialogRef: MatDialogRef<ImportImageComponent>,
private toastService: ToastrService, private toastService: ToastrService,
private router: Router, private router: Router,
@Inject(MAT_DIALOG_DATA) public data: { repository: any } @Inject(MAT_DIALOG_DATA) public data: { repositoryUuid: any, name: string }
) { ) {
} }
ngOnInit(): void { ngOnInit(): void {
this.loading = true; this.loading = true;
this.loadImages();
}
loadImages() {
this.http.get<any>(`${this.baseUrl}/images?page=1&itemsPerPage=50`).subscribe(
response => {
this.images = response['hydra:member'];
this.loading = false;
},
error => console.error('Error fetching organizational units:', error)
);
}
getImageName(imageId: string): string {
const image = this.images.find(img => img['@id'] === imageId);
return image ? image.name : 'Desconocido';
}
removeImage(imageId: string) {
this.selectedClients = this.selectedClients.filter(id => id !== imageId);
} }
save() { save() {
this.http.post<any>(`${this.baseUrl}${this.data.repository['@id']}/import-image`, { console.log(this.data?.repositoryUuid)
images: this.selectedClients this.http.post<any>(`${this.baseUrl}/image-repositories/${this.data?.repositoryUuid}/import-image`, {
name: this.imageName
}).subscribe({ }).subscribe({
next: (response) => { next: (response) => {
this.toastService.success('Peticion de importacion de imagen enviada correctamente'); this.toastService.success('Peticion de importacion de imagen enviada correctamente');
@ -59,7 +39,7 @@ export class ImportImageComponent implements OnInit{
this.router.navigate(['/commands-logs']); this.router.navigate(['/commands-logs']);
}, },
error: error => { error: error => {
this.toastService.error('Error al importar imagenes'); this.toastService.error(error.error['hydra:description']);
} }
}); });
} }

View File

@ -1,8 +1,3 @@
.images-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
}
table { table {
width: 100%; width: 100%;
@ -94,3 +89,9 @@ table {
margin-left: 1em; margin-left: 1em;
} }
.images-button-row {
display: flex;
gap: 15px;
padding: 5px;
}

View File

@ -9,6 +9,16 @@
{{ 'imagesTitle' | translate }} en {{ repository?.name }} {{ 'imagesTitle' | translate }} en {{ repository?.name }}
</h2> </h2>
</div> </div>
<div class="images-button-row">
<button class="action-button" (click)="importImage()">
{{ 'importImageButton' | translate }}
</button>
</div>
<div class="images-button-row">
<button class="action-button" (click)="convertImage()">
{{ 'convertImageButton' | translate }}
</button>
</div>
</div> </div>
<div class="search-container"> <div class="search-container">

View File

@ -1,4 +1,4 @@
import {Component, Input, OnInit} from '@angular/core'; import {Component, Input, isDevMode, OnInit} from '@angular/core';
import {MatTableDataSource} from "@angular/material/table"; import {MatTableDataSource} from "@angular/material/table";
import {DatePipe} from "@angular/common"; import {DatePipe} from "@angular/common";
import {MatDialog} from "@angular/material/dialog"; import {MatDialog} from "@angular/material/dialog";
@ -10,6 +10,8 @@ import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/de
import {ExportImageComponent} from "../../images/export-image/export-image.component"; import {ExportImageComponent} from "../../images/export-image/export-image.component";
import {BackupImageComponent} from "../backup-image/backup-image.component"; import {BackupImageComponent} from "../backup-image/backup-image.component";
import {ServerInfoDialogComponent} from "../../ogdhcp/server-info-dialog/server-info-dialog.component"; import {ServerInfoDialogComponent} from "../../ogdhcp/server-info-dialog/server-info-dialog.component";
import {ImportImageComponent} from "../import-image/import-image.component";
import {ConvertImageComponent} from "../convert-image/convert-image.component";
@Component({ @Component({
selector: 'app-repository-images', selector: 'app-repository-images',
@ -162,6 +164,34 @@ export class RepositoryImagesComponent implements OnInit {
); );
} }
importImage(): void {
this.dialog.open(ImportImageComponent, {
width: '600px',
data: {
repositoryUuid: this.repositoryUuid,
name: this.repository.name
}
}).afterClosed().subscribe((result) => {
if (result) {
this.search();
}
});
}
convertImage(): void {
this.dialog.open(ConvertImageComponent, {
width: '600px',
data: {
repositoryUuid: this.repositoryUuid,
name: this.repository.name
}
}).afterClosed().subscribe((result) => {
if (result) {
this.search();
}
});
}
toggleAction(image: any, action:string): void { toggleAction(image: any, action:string): void {
switch (action) { switch (action) {
case 'get-aux': case 'get-aux':
@ -301,4 +331,6 @@ export class RepositoryImagesComponent implements OnInit {
themeColor: '#3f51b5' themeColor: '#3f51b5'
}); });
} }
protected readonly isDevMode = isDevMode;
} }

View File

@ -308,6 +308,8 @@
"noResultsMessage": "No results to display.", "noResultsMessage": "No results to display.",
"imagesTitle": "Manage images", "imagesTitle": "Manage images",
"addImageButton": "Add image", "addImageButton": "Add image",
"importImageButton": "Import image",
"convertImageButton": "Convert virtual image",
"searchNameDescription": "Search images by name to quickly find a specific image.", "searchNameDescription": "Search images by name to quickly find a specific image.",
"searchDefaultDescription": "Filter images to show only default or non-default images.", "searchDefaultDescription": "Filter images to show only default or non-default images.",
"searchDefaultLabel": "Default image", "searchDefaultLabel": "Default image",

View File

@ -239,6 +239,8 @@
"organizationalUnitLabel": "Padre", "organizationalUnitLabel": "Padre",
"ogLiveLabel": "OgLive", "ogLiveLabel": "OgLive",
"imageNameLabel": "Nombre de la imagen", "imageNameLabel": "Nombre de la imagen",
"importImageButton": "Importar imagen",
"convertImageButton": "Convertir imagen virtual",
"repositoryLabel": "Repositorios", "repositoryLabel": "Repositorios",
"serialNumberLabel": "Número de Serie", "serialNumberLabel": "Número de Serie",
"netifaceLabel": "Interfaz de red", "netifaceLabel": "Interfaz de red",