refs #1575. Mercure and notifications
testing/ogGui-multibranch/pipeline/head This commit looks good Details

deb-pkg
Manuel Aranda Rosales 2025-02-21 11:22:01 +01:00
parent 09f83f6af7
commit 2d9ccd01b4
8 changed files with 147 additions and 24 deletions

View File

@ -130,6 +130,7 @@ 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';
import { ManageOrganizationalUnitComponent } from './components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component';
import { BackupImageComponent } from './components/repositories/backup-image/backup-image.component';
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './locale/', '.json');
}
@ -216,6 +217,7 @@ export function HttpLoaderFactory(http: HttpClient) {
RepositoryImagesComponent,
InputDialogComponent,
ManageOrganizationalUnitComponent,
BackupImageComponent,
],
bootstrap: [AppComponent],
imports: [BrowserModule,

View File

@ -67,9 +67,9 @@
<ng-container *ngSwitchCase="'status'">
<ng-container *ngIf="trace.status === 'in-progress' && trace.progress; else statusChip">
<div class="progress-container">
<mat-progress-bar class="example-margin" [mode]="mode" [value]="progress" [bufferValue]="bufferValue">
<mat-progress-bar class="example-margin" [mode]="mode" [value]="trace.progress" [bufferValue]="bufferValue">
</mat-progress-bar>
<span>{{progress}}%</span>
<span>{{trace.progress}}%</span>
</div>
</ng-container>
<ng-template #statusChip>

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import {ChangeDetectorRef, Component, OnInit} from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, forkJoin } from 'rxjs';
import { FormControl } from '@angular/forms';
@ -27,7 +27,7 @@ export class TaskLogsComponent implements OnInit {
pageSizeOptions: number[] = [10, 20, 30, 50];
datePipe: DatePipe = new DatePipe('es-ES');
mode: ProgressBarMode = 'buffer';
progress = 65;
progress = 0;
bufferValue = 0;
columns = [
@ -87,13 +87,14 @@ export class TaskLogsComponent implements OnInit {
constructor(private http: HttpClient,
private joyrideService: JoyrideService,
private dialog: MatDialog
private dialog: MatDialog,
private cdr: ChangeDetectorRef
) { }
ngOnInit(): void {
this.loadTraces();
this.loadCommands();
this.loadClients();
//this.loadClients();
this.filteredCommands = this.commandControl.valueChanges.pipe(
startWith(''),
map(value => (typeof value === 'string' ? value : value?.name)),
@ -104,7 +105,38 @@ export class TaskLogsComponent implements OnInit {
map(value => (typeof value === 'string' ? value : value?.name)),
map(name => (name ? this._filterClients(name) : this.clients.slice()))
);
const eventSource = new EventSource('http://localhost:3000/.well-known/mercure?topic='
+ encodeURIComponent(`traces`));
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data && data['@id']) {
this.updateTracesStatus(data['@id'], data.status, data.progress);
}
}
}
private updateTracesStatus(clientUuid: string, newStatus: string, progress: Number): void {
const traceIndex = this.traces.findIndex(trace => trace['@id'] === clientUuid);
if (traceIndex !== -1) {
const updatedTraces = [...this.traces];
updatedTraces[traceIndex] = {
...updatedTraces[traceIndex],
status: newStatus,
progress: progress
};
this.traces = updatedTraces;
this.cdr.detectChanges();
console.log(`Estado actualizado para la traza ${clientUuid}: ${newStatus}`);
} else {
console.warn(`Traza con UUID ${clientUuid} no encontrado en la lista.`);
}
}
private _filterClients(name: string): any[] {
const filterValue = name.toLowerCase();

View File

@ -126,6 +126,35 @@ export class GroupsComponent implements OnInit, OnDestroy {
};
this.arrayClients = this.selectedClients.data;
const eventSource = new EventSource('http://localhost:3000/.well-known/mercure?topic='
+ encodeURIComponent(`clients`));
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data && data['@id']) {
this.updateClientStatus(data['@id'], data.status);
}
}
}
private updateClientStatus(clientUuid: string, newStatus: string): void {
const clientIndex = this.selectedClients.data.findIndex(client => client['@id'] === clientUuid);
if (clientIndex !== -1) {
const updatedClients = [...this.selectedClients.data];
updatedClients[clientIndex] = {
...updatedClients[clientIndex],
status: newStatus
};
this.selectedClients.data = updatedClients;
console.log(`Estado actualizado para el cliente ${clientUuid}: ${newStatus}`);
} else {
console.warn(`Cliente con UUID ${clientUuid} no encontrado en la lista.`);
}
}

View File

@ -38,12 +38,10 @@
</mat-icon>
</ng-container>
<ng-container *ngIf="column.columnDef === 'imageRepositories'">
<button class="action-button" [matMenuTriggerFor]="menu">Ver repositorios</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let repository of image.imageRepositories">
{{ repository.imageRepository.name }}
</button>
</mat-menu>
<mat-chip-set>
<mat-chip *ngFor="let repository of image.imageRepositories"> {{ repository.imageRepository.name }} </mat-chip>
</mat-chip-set>
</ng-container>
<ng-container *ngIf="column.columnDef === 'isGlobal'">
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">

View File

@ -1,6 +1,19 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BackupImageComponent } from './backup-image.component';
import {ImportImageComponent} from "../import-image/import-image.component";
import {FormBuilder, 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 {MatButtonModule} from "@angular/material/button";
import {MatSelectModule} from "@angular/material/select";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {ToastrModule, ToastrService} from "ngx-toastr";
import {TranslateModule} from "@ngx-translate/core";
import {provideHttpClient} from "@angular/common/http";
import {provideHttpClientTesting} from "@angular/common/http/testing";
import {LoadingComponent} from "../../../shared/loading/loading.component";
describe('BackupImageComponent', () => {
let component: BackupImageComponent;
@ -8,7 +21,31 @@ describe('BackupImageComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [BackupImageComponent]
declarations: [BackupImageComponent, LoadingComponent],
imports: [
ReactiveFormsModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule,
MatSelectModule,
BrowserAnimationsModule,
ToastrModule.forRoot(),
TranslateModule.forRoot()
],
providers: [
FormBuilder,
ToastrService,
provideHttpClient(),
provideHttpClientTesting(),
{
provide: MatDialogRef,
useValue: {}
},
{
provide: MAT_DIALOG_DATA,
useValue: {}
}]
})
.compileComponents();

View File

@ -70,9 +70,10 @@
</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 imagen</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'" (click)="toggleAction(image, 'recover')">Recuperar imagen de la papelera</button>
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'" (click)="toggleAction(image, 'transfer')">Transferir imagen</button>
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'" (click)="toggleAction(image, 'backup')">Realizar backup </button>
</mat-menu>
</td>
</ng-container>

View File

@ -10,6 +10,7 @@ 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";
import {BackupImageComponent} from "../backup-image/backup-image.component";
@Component({
selector: 'app-repository-images',
@ -209,6 +210,11 @@ export class RepositoryImagesComponent implements OnInit {
break;
case 'delete-permanent':
this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: image.name },
}).afterClosed().subscribe((result) => {
if (result) {
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');
@ -218,6 +224,8 @@ export class RepositoryImagesComponent implements OnInit {
this.toastService.error(error.error['hydra:description']);
}
});
}
});
break;
case 'recover':
this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/recover`, {}).subscribe({
@ -246,6 +254,22 @@ export class RepositoryImagesComponent implements OnInit {
}
});
break;
case 'backup':
this.http.get(`${this.baseUrl}${image.image['@id']}`).subscribe({
next: (response) => {
this.dialog.open(BackupImageComponent, {
width: '600px',
data: {
image: response,
imageImageRepository: image
}
});
},
error: (error) => {
this.toastService.error(error.error['hydra:description']);
}
});
break;
default:
console.error('Acción no soportada:', action);
break;