From 9797b14038e6a792e1a03bfb13188168cfa121a7 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 13 Jul 2026 10:42:33 +0200 Subject: [PATCH] ref 3007. Software profile in versioned image --- .../create-image/create-image.component.html | 9 --- .../create-image/create-image.component.ts | 17 ------ .../show-monolitic-images.component.css | 33 +++++++++++ .../show-monolitic-images.component.html | 19 +++++- .../show-monolitic-images.component.ts | 59 +++++++++++++++++-- 5 files changed, 106 insertions(+), 31 deletions(-) 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 26df815..f43bdc0 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 @@ -32,15 +32,6 @@ - - {{ 'softwareProfile' | translate }} - - - {{ profile.description }} - - - - {{ 'labelRemotePc' | translate }} 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 99ff396..ae5600c 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 @@ -18,7 +18,6 @@ export class CreateImageComponent implements OnInit { baseUrl: string; imageForm: FormGroup; imageId: string | null = null; - softwareProfiles: any[] = []; repositories: any[] = []; loading: boolean = false; isEditMode: boolean = false; @@ -41,7 +40,6 @@ export class CreateImageComponent implements OnInit { comments: [''], remotePc: [false], isGlobal: [false], - softwareProfile: [''], imageRepositories: [[], Validators.required], }); } @@ -54,7 +52,6 @@ export class CreateImageComponent implements OnInit { } else { this.loading = false; } - this.fetchSoftwareProfiles(); this.fetchRepositories(); } @@ -67,7 +64,6 @@ export class CreateImageComponent implements OnInit { comments: response.comments, remotePc: response.remotePc, isGlobal: response.isGlobal, - softwareProfile: response.softwareProfile ? response.softwareProfile['@id'] : null, imageRepositories: response.imageRepositories ? response.imageRepositories.map((r: any) => r.imageRepository['@id']) : [], }); this.imageId = response['@id']; @@ -85,18 +81,6 @@ export class CreateImageComponent implements OnInit { this.showWarning = this.imageForm.get('isGlobal')?.value; } - fetchSoftwareProfiles() { - const url = `${this.baseUrl}/software-profiles`; - this.http.get(url).subscribe({ - next: (response: any) => { - this.softwareProfiles = response['hydra:member']; - }, - error: (error) => { - this.toastService.error(this.translate.instant('errorGettingProfiles')); - } - }); - } - fetchRepositories() { const url = `${this.baseUrl}/image-repositories`; this.http.get(url).subscribe({ @@ -117,7 +101,6 @@ export class CreateImageComponent implements OnInit { remotePc: this.imageForm.value.remotePc, isGlobal: this.imageForm.value.isGlobal, imageRepositories: this.imageForm.value.imageRepositories, - ...(this.imageForm.value.softwareProfile ? { softwareProfile: this.imageForm.value.softwareProfile } : {}), }; if (this.imageId) { diff --git a/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.css b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.css index 05c97d3..2f59c42 100644 --- a/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.css +++ b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.css @@ -108,3 +108,36 @@ table { margin: 0 auto; align-self: center; } + +.software-profile-select { + width: 150px; + font-size: 0.8em; +} + +.software-profile-select ::ng-deep .mat-mdc-text-field-wrapper { + padding: 0 8px; +} + +.software-profile-select ::ng-deep .mat-mdc-form-field-infix { + min-height: unset; + padding-top: 8px; + padding-bottom: 8px; +} + +.software-profile-caret { + cursor: pointer; + color: rgba(0, 0, 0, 0.54); + font-size: 20px; + width: 20px; + height: 20px; +} + +::ng-deep .software-profile-panel { + max-width: 320px; +} + +::ng-deep .software-profile-panel .mat-mdc-option { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.html b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.html index df7b830..4e973a5 100644 --- a/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.html +++ b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.html @@ -83,8 +83,25 @@ {{ getStatusLabel(image[column.columnDef]) }} + + + + + arrow_drop_down + + {{ 'noSoftwareProfileAssigned' | translate }} + + {{ profile.description }} + + + + + + *ngIf="column.columnDef !== 'remotePc' && column.columnDef !== 'status' && column.columnDef !== 'isGlobal' && column.columnDef !== 'name' && column.columnDef !== 'datasize' && column.columnDef !== 'softwareProfile'"> {{ column.cell(image) }} diff --git a/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.ts b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.ts index fa2ba97..8f89564 100644 --- a/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.ts +++ b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.ts @@ -37,6 +37,7 @@ export class ShowMonoliticImagesComponent implements OnInit { filters: { [key: string]: string } = {}; alertMessage: string | null = null; repository: any = {}; + softwareProfiles: any[] = []; datePipe: DatePipe = new DatePipe('es-ES'); columns = [ { @@ -70,9 +71,9 @@ export class ShowMonoliticImagesComponent implements OnInit { cell: (image: any) => this.formatSize(image.datasize) }, { - columnDef: 'description', - header: 'labelDescription', - cell: (image: any) => image.description + columnDef: 'softwareProfile', + header: 'softwareProfile', + cell: (image: any) => image.softwareProfile ? image.softwareProfile['@id'] : null }, { columnDef: 'createdAt', @@ -100,13 +101,63 @@ export class ShowMonoliticImagesComponent implements OnInit { if (this.data) { this.loadData(); } + this.loadSoftwareProfiles(); + } + + loadSoftwareProfiles(): void { + this.http.get(`${this.baseUrl}/software-profiles?page=1&itemsPerPage=10000`).subscribe({ + next: (response) => { + this.softwareProfiles = response['hydra:member']; + }, + error: (error) => { + console.error(this.translate.instant('errorGettingProfiles'), error); + } + }); + } + + filterProfiles(value: any): any[] { + const query = typeof value === 'string' ? value : (value?.description || ''); + const normalized = query.toLowerCase(); + return this.softwareProfiles.filter(profile => (profile.description || '').toLowerCase().includes(normalized)); + } + + displayProfile(value: any): string { + return value?.description || ''; + } + + onProfileSelected(image: any, selectedProfile: any | null): void { + const previousProfile = image.softwareProfile; + + // Optimistic update: reflect the choice immediately so the blur/reset + // handler (which reads image.softwareProfile) doesn't race the PATCH + // response and briefly flash back to the old value. + image.softwareProfile = selectedProfile; + image.profileValue = selectedProfile; + + this.http.patch(`${this.baseUrl}${image['@id']}`, { softwareProfile: selectedProfile ? selectedProfile['@id'] : null }).subscribe({ + next: () => { + this.toastService.success(this.translate.instant('successfullyEditedSoftware')); + }, + error: (error) => { + image.softwareProfile = previousProfile; + image.profileValue = previousProfile; + this.toastService.error(error.error['hydra:description']); + } + }); + } + + resetProfileSearchIfInvalid(image: any): void { + image.profileValue = image.softwareProfile || null; } loadData(): void { this.loading = true; this.http.get(`${this.apiUrl}?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}&repository.id=${this.data.repositoryId}`, { params: this.filters }).subscribe( data => { - this.dataSource.data = data['hydra:member']; + this.dataSource.data = (data['hydra:member'] || []).map((item: any) => ({ + ...item, + profileValue: item.softwareProfile || null + })); this.length = data['hydra:totalItems']; this.loading = false; },