refs #1984. Git integration UX changes

pull/22/head
Manuel Aranda Rosales 2025-05-12 16:28:48 +02:00
parent 114483410b
commit e3adf08bd9
12 changed files with 131 additions and 170 deletions

View File

@ -16,9 +16,9 @@
<div class="selector">
<mat-form-field appearance="fill" class="half-width">
<mat-label>Tipo de imagen</mat-label>
<mat-select formControlName="scope" class="full-width" >
<mat-option [value]="'monolitic'">Monolítica</mat-option>
<mat-option disabled [value]="'git'">Git</mat-option>
<mat-select [(ngModel)]="imageType" class="full-width" (selectionChange)="onImageTypeSelected($event.value)">
<mat-option [value]="'monolithic'">Monolítica</mat-option>
<mat-option [value]="'git'">Git</mat-option>
</mat-select>
</mat-form-field>
</div>
@ -31,7 +31,7 @@
<mat-form-field appearance="fill" class="half-width">
<mat-label>Seleccione imagen</mat-label>
<mat-select [(ngModel)]="selectedImage" name="selectedImage" (selectionChange)="resetCanonicalName()" required>
<mat-select [disabled]="!imageType" [(ngModel)]="selectedImage" name="selectedImage" (selectionChange)="resetCanonicalName()" required>
<mat-option *ngFor="let image of images" [value]="image">{{ image?.name }}</mat-option>
</mat-select>
<button *ngIf="selectedImage" mat-icon-button matSuffix aria-label="Clear client search"
@ -61,7 +61,19 @@
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let image">
{{ column.cell(image) }}
<ng-container *ngIf="column.columnDef !== 'size'">
{{ column.cell(image) }}
</ng-container>
<ng-container *ngIf="column.columnDef === 'size'">
<div style="display: flex; flex-direction: column;">
<span> {{ image.size }} MB</span>
<span style="font-size: 0.75rem; color: gray;">{{ image.size / 1024 }} GB</span>
</div>
</ng-container>
</td>
</ng-container>

View File

@ -25,32 +25,38 @@ export class CreateClientImageComponent implements OnInit{
client: any = null;
loading: boolean = false;
selectedImage: any = null;
imageType : string = 'monolithic';
dataSource = new MatTableDataSource<any>();
columns = [
{
columnDef: 'diskNumber',
header: 'Disco',
cell: (partition: any) => `${partition.diskNumber}`
cell: (partition: any) => partition.diskNumber
},
{
columnDef: 'partitionNumber',
header: 'Particion',
cell: (partition: any) => `${partition.partitionNumber}`
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}`
cell: (partition: any) => partition.filesystem
},
{
columnDef: 'operativeSystem',
header: 'SO',
cell: (partition: any) => `${partition.operativeSystem?.name}`
cell: (partition: any) => partition.operativeSystem?.name
}
];
@ -92,8 +98,13 @@ export class CreateClientImageComponent implements OnInit{
);
}
onImageTypeSelected(event: any) {
this.imageType = event;
this.loadImages();
}
loadImages() {
const url = `${this.baseUrl}/images?created=false&page=1&itemsPerPage=1000`;
const url = `${this.baseUrl}/images?created=false&type=${this.imageType}&page=1&itemsPerPage=100`;
this.http.get(url).subscribe(
(response: any) => {
this.images = response['hydra:member'];
@ -126,6 +137,7 @@ export class CreateClientImageComponent implements OnInit{
name: this.name,
partition: this.selectedPartition['@id'],
source: 'assistant',
type: this.imageType,
selectedImage: this.selectedImage?.['@id']
};

View File

@ -78,10 +78,20 @@
<mat-divider style="margin-top: 20px;"></mat-divider>
<div class="select-container">
<div class="deploy-container">
<mat-form-field appearance="fill" class="half-width">
<mat-label>Tipo de imagen</mat-label>
<mat-select [(ngModel)]="imageType" (selectionChange)="onImageTypeSelected($event.value)">
<mat-option [value]="'monolithic'">Monolítica</mat-option>
<mat-option [value]="'git'">Git</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="deploy-container">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Seleccione imagen</mat-label>
<mat-select [(ngModel)]="selectedImage" name="selectedImage">
<mat-select [(ngModel)]="selectedImage" name="selectedImage" [disabled] = "!imageType" >
<mat-option *ngFor="let image of images" [value]="image">
<div class="unit-name"> {{ image.name }}</div>
<div style="font-size: smaller; color: gray;">{{ image.description }}</div>

View File

@ -52,6 +52,7 @@ export class DeployImageComponent implements OnInit{
selectedModelClient: any = null;
filteredPartitions: any[] = [];
selectedRepository: any = null;
imageType: string = 'monolithic';
allMethods = [
{ name: 'Multicast', value: 'udpcast' },
@ -135,6 +136,11 @@ export class DeployImageComponent implements OnInit{
});
}
onImageTypeSelected(event: any) {
this.imageType = event;
this.loadImages();
}
get runScriptTitle(): string {
const ctx = this.runScriptContext;
if (!ctx) {
@ -229,7 +235,13 @@ export class DeployImageComponent implements OnInit{
return;
}
const url = `${this.baseUrl}/image-image-repositories?status=success&repository.id=${repositoryId}&page=1&itemsPerPage=1000`;
let url = ''
if (this.imageType === 'monolithic') {
url = `${this.baseUrl}/image-image-repositories?status=success&repository.id=${repositoryId}&page=1&itemsPerPage=1000`;
} else {
url = `${this.baseUrl}/git-image-repositories?status=success&repository.id=${repositoryId}&page=1&itemsPerPage=1000`;
}
this.http.get(url).subscribe(
(response: any) => {

View File

@ -69,4 +69,4 @@
<button class="ordinary-button" (click)="close()">{{ 'cancelButton' | translate }}</button>
<button class="submit-button" (click)="saveImage()" [disabled]="loading">{{ 'saveButton' | translate }}</button>
</mat-dialog-actions>
</div>
</div>

View File

@ -79,25 +79,6 @@ table {
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;
}
.example-button-row {
display: table-cell;
max-width: 600px;
}
.example-button-row .mat-mdc-button-base {
margin: 8px 8px 8px 0;
}
.header-container-title {
flex-grow: 1;
text-align: left;

View File

@ -1,31 +1,33 @@
<app-loading [isLoading]="loading"></app-loading>
<div class="header-container">
<button mat-icon-button color="primary" (click)="iniciarTour()">
<button mat-icon-button color="primary" (click)="initTour()">
<mat-icon>help</mat-icon>
</button>
<div class="header-container-title">
<h2 joyrideStep="repositoryTitleStep" text="{{ 'groupsTitleStepText' | translate }}">
<h2 joyrideStep="repositoryTitleStep" text="{{ 'repositoryTitleStepText' | translate }}">
{{ 'repositoryTitle' | translate }}
</h2>
</div>
<div class="images-button-row">
<button class="action-button" (click)="addImage()" joyrideStep="addStep"
text="Utiliza este botón para añadir un nuevo repositorio.">Añadir repositorio</button>
<button
class="action-button" (click)="addImage()"
joyrideStep="addStep"
text="{{ 'addRepositoryStepText' | translate }}">{{ 'addRepository' | translate }}</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
<mat-form-field appearance="fill" class="search-string">
<mat-label>Buscar nombre de repositorio</mat-label>
<mat-label>{{ 'search' | translate }} {{ 'repository' | translate }}</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()"
i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill" class="search-string">
<mat-label>Buscar IP de repositorio</mat-label>
<mat-label>{{ 'search' | translate }} IP de {{ 'repository' | translate }}</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['ip']" (keyup.enter)="search()"
i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
@ -33,7 +35,7 @@
</mat-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableDateStep" text="{{ 'tableDateRepositoryText' | translate }}">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let repository">
@ -42,15 +44,30 @@
</ng-container>
<ng-container *ngIf="column.columnDef === 'images'">
<button class="action-button" (click)="openShowMonoliticImagesDialog(repository)">Gestionar Imágenes</button>
<button class="action-button" style="margin-left: 0.5vw;" [disabled]="!isGitModuleInstalled" (click)="openShowGitImagesDialog(repository)">Gestionar Imágenes Git</button>
<button
class="action-button"
joyrideStep="monolithicImageStep"
text="{{ 'monolithicImageStepText' | translate }}"
(click)="openShowMonoliticImagesDialog(repository)">
{{ 'monolithicImage' | translate }}
</button>
<button
class="action-button"
joyrideStep="gitImageStep"
text="{{ 'gitImageStepText' | translate }}"
style="margin-left: 0.5vw;"
[disabled]="!isGitModuleInstalled"
(click)="openShowGitImagesDialog(repository)">
{{ 'gitImage' | translate }}
</button>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let repository" style="text-align: center;">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">{{ 'actions' | translate }}</th>
<td mat-cell *matCellDef="let repository" style="text-align: center;" joyrideStep="actionsStep"
[text]="'actionsDescription' | translate">
<button mat-icon-button color="primary" (click)="editRepository($event, repository)" i18n="@@editImage">
<mat-icon>edit</mat-icon></button>
<button mat-icon-button color="warn" (click)="deleteRepository($event, repository)">
@ -62,7 +79,7 @@
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<div class="paginator-container">
<div class="paginator-container" joyrideStep="paginationStep" text="{{ 'paginationDescription' | translate }}">
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="[5, 10, 20, 40, 100]"
(page)="onPageChange($event)">
</mat-paginator>

View File

@ -23,7 +23,7 @@ export class RepositoriesComponent implements OnInit {
private apiUrl: string;
dataSource = new MatTableDataSource<any>();
length: number = 0;
itemsPerPage: number = 10;
itemsPerPage: number = 20;
page: number = 0;
loading: boolean = false;
filters: { [key: string]: string } = {};
@ -37,7 +37,7 @@ export class RepositoriesComponent implements OnInit {
{
columnDef: 'name',
header: 'Nombre de repositorio',
cell: (repository: any) => `${repository.name}`
cell: (repository: any) => repository.name
},
{
columnDef: 'user',
@ -47,12 +47,12 @@ export class RepositoriesComponent implements OnInit {
{
columnDef: 'ip',
header: 'Ip',
cell: (repository: any) => `${repository.ip}`
cell: (repository: any) => repository.ip
},
{
columnDef: 'images',
header: 'Imágenes',
cell: (repository: any) => `${repository.images}`
header: 'Gestionar imágenes',
cell: (repository: any) => repository.images
},
{
columnDef: 'createdAt',
@ -60,7 +60,7 @@ export class RepositoriesComponent implements OnInit {
cell: (repository: any) => `${this.datePipe.transform(repository.createdAt, 'dd/MM/yyyy hh:mm:ss')}`
}
];
isGitModuleInstalled: boolean = false;
isGitModuleInstalled: boolean = true;
displayedColumns: string[] = ['id', 'name', 'ip', 'user', 'images', 'createdAt', 'actions'];
constructor(
@ -166,11 +166,17 @@ export class RepositoriesComponent implements OnInit {
this.search();
}
iniciarTour(): void {
initTour(): void {
this.joyrideService.startTour({
steps: [
'repositoryTitleStep',
'addStep',
'searchStep',
'tableDateStep',
'monolithicImageStep',
'gitImageStep',
'actionsStep',
'paginationStep'
],
showPrevButton: true,
themeColor: '#3f51b5'

View File

@ -14,9 +14,6 @@
<button class="action-button" (click)="importImage()">
{{ 'importImageButton' | translate }}
</button>
<button class="action-button" (click)="convertImage()">
{{ 'convertImageButton' | translate }}
</button>
</div>
</div>
@ -48,10 +45,10 @@
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let image">
<ng-container *ngIf="column.columnDef === 'remotePc' || column.columnDef === 'isGlobal'">
<mat-icon [color]="image.image[column.columnDef] ? 'primary' : 'warn'">
{{ image.image[column.columnDef] ? 'check_circle' : 'cancel' }}
</mat-icon>
<ng-container *ngIf="column.columnDef === 'isGlobal'">
<mat-chip>
{{ image.isGlobal ? 'Sí' : 'No' }}
</mat-chip>
</ng-container>
<ng-container *ngIf="column.columnDef === 'status'">
<mat-chip [ngClass]="{
@ -74,8 +71,6 @@
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let image" style="text-align: center;">
<button mat-icon-button color="info" (click)="showImageInfo($event, image)"><mat-icon
i18n="@@deleteElementTooltip">visibility</mat-icon></button>
<button mat-icon-button color="primary" (click)="toggleAction(image, 'edit')">
<mat-icon i18n="@@deleteElementTooltip">edit</mat-icon>
</button>
@ -86,7 +81,6 @@
<mat-icon>menu</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="toggleAction(image, 'get-aux')">Obtener ficheros auxiliares</button>
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'"
(click)="toggleAction(image, 'delete-permanent')">Eliminar permanentemente</button>
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'trash'"
@ -97,10 +91,6 @@
(click)="toggleAction(image, 'transfer-global')">Transferir imagen globalmente </button>
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'"
(click)="toggleAction(image, 'backup')">Realizar backup </button>
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'"
(click)="toggleAction(image, 'status')">Checkear estado imagen </button>
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'"
(click)="toggleAction(image, 'convert-image-to-virtual')">Convertir imagen en virtual </button>
</mat-menu>
</td>
</ng-container>
@ -117,4 +107,4 @@
</mat-dialog-content>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">Cerrar</button>
</mat-dialog-actions>
</mat-dialog-actions>

View File

@ -22,7 +22,7 @@ import {EditImageComponent} from "../edit-image/edit-image.component";
templateUrl: './show-git-images.component.html',
styleUrl: './show-git-images.component.css'
})
export class ShowGitImagesComponent {
export class ShowGitImagesComponent implements OnInit{
baseUrl: string;
private apiUrl: string;
dataSource = new MatTableDataSource<any>();
@ -43,27 +43,27 @@ baseUrl: string;
{
columnDef: 'name',
header: 'Nombre de imagen',
cell: (image: any) => `${image.image.name}`
cell: (image: any) => image.name
},
{
columnDef: 'version',
header: 'Version',
cell: (image: any) => `${image.version ? image.version : '0'}`
columnDef: 'tag',
header: 'Tag',
cell: (image: any) => image.tag
},
{
columnDef: 'isGlobal',
header: 'Imagen global',
cell: (image: any) => `${image.image?.isGlobal}`
cell: (image: any) => image.image?.isGlobal
},
{
columnDef: 'status',
header: 'Estado',
cell: (image: any) => `${image.status}`
cell: (image: any) => image.status
},
{
columnDef: 'description',
header: 'Descripción',
cell: (image: any) => `${image.description ? image.description : 'Sin descripción'}`
cell: (image: any) => image.description
},
{
columnDef: 'createdAt',
@ -84,11 +84,10 @@ baseUrl: string;
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/image-image-repositories`;
this.apiUrl = `${this.baseUrl}/git-image-repositories`;
}
ngOnInit(): void {
console.error()
if (this.data) {
this.loadData();
}
@ -140,30 +139,7 @@ baseUrl: string;
return this.http.get<any>(`${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;
}
);
}
importImage(): void {
console.log(this.data)
this.dialog.open(ImportImageComponent, {
width: '600px',
data: {
@ -177,33 +153,8 @@ baseUrl: string;
});
}
convertImage(): void {
this.dialog.open(ConvertImageComponent, {
width: '600px',
data: {
repositoryUuid: this.data.repositoryUuid,
name: this.data.repositoryName
}
}).afterClosed().subscribe((result) => {
if (result) {
this.loadData();
}
});
}
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: (message) => {
this.toastService.success('Petición de creación de archivos auxiliares enviada');
this.loadData()
},
error: (error) => {
this.toastService.error(error.error['hydra:description']);
}
});
break;
case 'delete-trash':
if (!image.imageFullsum) {
const dialogRef = this.dialog.open(DeleteModalComponent, {
@ -278,17 +229,6 @@ baseUrl: string;
}
});
break;
case 'status':
this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/status`, {}).subscribe({
next: (response: any) => {
this.toastService.info(response?.output);
this.loadData()
},
error: (error) => {
this.toastService.error(error.error['hydra:description']);
}
});
break;
case 'transfer':
this.http.get(`${this.baseUrl}${image.image['@id']}`).subscribe({
next: (response) => {
@ -335,23 +275,6 @@ baseUrl: string;
}
});
break;
case 'convert-image-to-virtual':
this.http.get(`${this.baseUrl}${image.image['@id']}`).subscribe({
next: (response) => {
this.dialog.open(ConvertImageToVirtualComponent, {
width: '600px',
data: {
image: response,
imageImageRepository: image
}
});
this.router.navigate(['/commands-logs']);
},
error: (error) => {
this.toastService.error(error.error['hydra:description']);
}
});
break;
default:
console.error('Acción no soportada:', action);
break;
@ -376,11 +299,11 @@ baseUrl: string;
}
loadAlert(): Observable<any> {
return this.http.post<any>(`${this.baseUrl}/image-repositories/server/${this.data.repositoryUuid}/get-collection`, {});
return this.http.post<any>(`${this.baseUrl}/image-repositories/server/git/${this.data.repositoryUuid}/get-collection`, {});
}
syncRepository() {
this.http.post(`${this.baseUrl}/image-repositories/server/${this.data.repositoryUuid}/sync`, {})
this.http.post(`${this.baseUrl}/image-repositories/server/git/${this.data.repositoryUuid}/sync`, {})
.subscribe(response => {
this.toastService.success('Sincronización completada');
this.loadData()
@ -393,12 +316,12 @@ baseUrl: string;
openImageInfoDialog() {
this.loadAlert().subscribe(
response => {
this.alertMessage = response.output;
this.alertMessage = response.repositories;
this.dialog.open(ServerInfoDialogComponent, {
width: '800px',
data: {
message: this.alertMessage
repositories: this.alertMessage
}
});
},

View File

@ -6,7 +6,7 @@
<button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon>
</button>
<h2>Gestionar imágenes monolíticas en {{data.repositoryName}}</h2>
<h2> {{ 'monolithicImage' | translate }} {{data.repositoryName}}</h2>
</div>
<div class="images-button-row">
<button class="action-button" (click)="openImageInfoDialog()">Ver Información</button>
@ -48,11 +48,12 @@
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let image">
<ng-container *ngIf="column.columnDef === 'remotePc' || column.columnDef === 'isGlobal'">
<mat-icon [color]="image.image[column.columnDef] ? 'primary' : 'warn'">
{{ image.image[column.columnDef] ? 'check_circle' : 'cancel' }}
</mat-icon>
<ng-container *ngIf="column.columnDef === 'isGlobal'">
<mat-chip>
{{ image.isGlobal ? 'Sí' : 'No' }}
</mat-chip>
</ng-container>
<ng-container *ngIf="column.columnDef === 'status'">
<mat-chip [ngClass]="{
'chip-failed': image.status === 'failed',

View File

@ -44,7 +44,7 @@ export class ShowMonoliticImagesComponent implements OnInit {
{
columnDef: 'name',
header: 'Nombre de imagen',
cell: (image: any) => `${image.name}`
cell: (image: any) => image.name
},
{
columnDef: 'version',
@ -54,17 +54,17 @@ export class ShowMonoliticImagesComponent implements OnInit {
{
columnDef: 'isGlobal',
header: 'Imagen global',
cell: (image: any) => `${image.image?.isGlobal}`
cell: (image: any) => image.image?.isGlobal
},
{
columnDef: 'status',
header: 'Estado',
cell: (image: any) => `${image.status}`
cell: (image: any) => image.status
},
{
columnDef: 'description',
header: 'Descripción',
cell: (image: any) => `${image.description ? image.description : 'Sin descripción'}`
cell: (image: any) => image.description
},
{
columnDef: 'createdAt',
@ -164,7 +164,6 @@ export class ShowMonoliticImagesComponent implements OnInit {
}
importImage(): void {
console.log(this.data)
this.dialog.open(ImportImageComponent, {
width: '600px',
data: {
@ -410,9 +409,7 @@ export class ShowMonoliticImagesComponent implements OnInit {
this.dialog.open(ServerInfoDialogComponent, {
width: '800px',
data: {
message: this.alertMessage
}
data: this.alertMessage
});
},
error => {