Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop

develop-jenkins
Manuel Aranda Rosales 2024-10-22 07:29:58 +02:00
commit 8eef8cd8ec
5 changed files with 247 additions and 303 deletions

1
.gitignore vendored 100644
View File

@ -0,0 +1 @@
ogWebconsole/.env

View File

@ -1 +1 @@
NG_APP_BASE_API_URL=http://127.0.0.1:8001 NG_APP_BASE_API_URL=https://localhost:8443

View File

@ -1,90 +1,70 @@
.header-container { .global-selectors {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 100px; margin-bottom: 15px;
padding: 10px; gap: 10px;
margin-top: 16px;
} }
.title { .global-selectors mat-form-field {
font-size: 24px; flex: 1;
margin-right: 10px;
} }
.templates-button-row { .global-selectors button {
height: 40px;
padding: 0 20px;
}
.filters-container {
display: flex; display: flex;
justify-content: flex-start; justify-content: space-between;
margin-top: 16px; align-items: flex-end;
gap: 10px;
margin-bottom: 15px;
}
.filters-container mat-form-field {
flex: 1;
}
.mat-elevation-z8 {
margin-top: 15px;
}
.mat-table {
width: 100%;
}
mat-header-cell, mat-cell {
text-align: center;
font-size: 14px;
padding: 8px;
}
mat-form-field {
width: 100%;
}
.mat-paginator {
margin-top: 20px;
display: flex;
justify-content: center;
}
mat-select, mat-input {
font-size: 14px;
}
.mat-form-field-appearance-fill .mat-form-field-flex {
padding: 8px 0;
}
button.mat-flat-button {
padding: 8px 16px;
font-size: 14px;
} }
.divider { .divider {
margin: 20px 0; margin: 20px 0;
} }
.lists-container {
padding: 16px;
}
.templatesLists-container {
flex: 1;
}
.card.unidad-card {
height: 100%;
box-sizing: border-box;
}
.template-container {
display: flex;
align-items: center;
margin-bottom: 16px;
border-bottom: 1px solid rgba(122, 122, 122, 0.555);
}
.template-container h4 {
margin: 0;
flex: 1;
}
.mat-icon-button {
margin-left: 16px;
align-self: center;
}
.template-name{
cursor: pointer;
}
table {
width: 100%;
margin-top: 50px;
}
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 5px;
box-sizing: border-box;
}
.search-string {
flex: 2;
padding: 5px;
}
.search-entity {
flex: 1;
padding: 5px;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
}
.paginator-container {
display: flex;
justify-content: end;
margin-bottom: 30px;
}

View File

@ -2,12 +2,7 @@
<mat-expansion-panel hideToggle> <mat-expansion-panel hideToggle>
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> Sincronización ogBoot </mat-panel-title> <mat-panel-title> Sincronización ogBoot </mat-panel-title>
<mat-panel-description>
<mat-icon [style.color]="getIcon().color">{{ getIcon().name }}</mat-icon>
</mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<p *ngIf="alertMessage">Oglives creados en servidor ogBoot: {{ alertMessage }}</p>
<p *ngIf="alertMessage">Oglives creados en servidor ogCore (base de datos): {{ length }}</p>
<div class="example-button-row"> <div class="example-button-row">
<button mat-flat-button color="primary" (click)="syncOgCore()"> Sincronizar OgCore</button> <button mat-flat-button color="primary" (click)="syncOgCore()"> Sincronizar OgCore</button>
@ -16,67 +11,78 @@
</mat-accordion> </mat-accordion>
<div class="header-container"> <div class="header-container">
<h2 class="title" i18n="@@adminPXETitle">Administrar ficheros de arranque PXE</h2> <h2 class="title">Administrar ficheros de arranque PXE</h2>
</div> </div>
<mat-divider class="divider"></mat-divider> <div [formGroup]="taskForm" class="filters-container">
<div class="search-container"> <mat-form-field appearance="fill" class="full-width">
<mat-form-field appearance="fill" class="search-entity"> <mat-label>Selecciona Unidad Organizacional</mat-label>
<mat-label i18n="@@searchLabel">Plantilla</mat-label> <mat-select formControlName="organizationalUnit" (selectionChange)="onOrganizationalUnitChange()">
<mat-select [(ngModel)]="filters['template']" (selectionChange)="search()" placeholder="Seleccionar entidad"> <mat-option *ngIf="loadingUnits" disabled>Cargando unidades...</mat-option>
<mat-option [value]="''">Todos</mat-option> <mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
<mat-option *ngFor="let template of templateOptions" [value]="template.id">{{ template.name }}</mat-option> {{ unit.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('organizationalUnit')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona aula</mat-label>
<mat-select formControlName="selectedChild" (selectionChange)="onChildChange()">
<mat-option *ngIf="selectedUnitChildren.length === 0" disabled>No hay aulas disponibles</mat-option>
<mat-option *ngFor="let child of selectedUnitChildren" [value]="child['@id']">
{{ child.name }}
</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef"> <mat-divider class="divider"></mat-divider>
<th mat-header-cell *matHeaderCellDef>{{column.header}}</th>
<td mat-cell *matCellDef="let element"> <div class="global-selectors">
<ng-container *ngIf="column.columnDef === 'clients'"> <mat-form-field appearance="fill">
<button mat-icon-button [matMenuTriggerFor]="clientsMenu"> <mat-label>Global ogLive</mat-label>
<mat-icon>visibility</mat-icon> <mat-select [(value)]="globalOgLive">
</button> <mat-option *ngFor="let option of ogLiveOptions" [value]="option['@id']">{{ option.name }}</mat-option>
<mat-menu #clientsMenu="matMenu"> </mat-select>
<ng-container *ngFor="let client of column.cell(element)"> </mat-form-field>
<button mat-menu-item>
<mat-list> <button mat-flat-button color="primary" (click)="applyToAll()">Aplicar a todos</button>
<mat-list-item lines="3"> </div>
<span matListItemTitle>{{ client.name }}</span>
<span matListItemLine>{{ client.ip }}</span> <mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<span matListItemLine>{{client.mac}}</span> <ng-container matColumnDef="id">
</mat-list-item> <mat-header-cell *matHeaderCellDef> Id </mat-header-cell>
</mat-list> <mat-cell *matCellDef="let element"> {{element.id}} </mat-cell>
</button>
</ng-container>
</mat-menu>
</ng-container>
<ng-container *ngIf="column.columnDef !== 'clients'">
{{column.cell(element)}}
</ng-container>
</td>
</ng-container> </ng-container>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions">Acciones</th> <mat-header-cell *matHeaderCellDef> Nombre </mat-header-cell>
<td mat-cell *matCellDef="let image"> <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell>
<button mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>menu</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="toggleAction(image, 'delete')">Eliminar</button>
</mat-menu>
</td>
</ng-container> </ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <ng-container matColumnDef="ogLive">
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> <mat-header-cell *matHeaderCellDef> Plantilla </mat-header-cell>
</table> <mat-cell *matCellDef="let element">
<div class="paginator-container"> <mat-form-field>
<mat-select [(value)]="element.ogLive">
<mat-option *ngFor="let option of ogLiveOptions" [value]="option['@id']">{{ option.name }}</mat-option>
</mat-select>
</mat-form-field>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
<div class="save-button"
>
<button mat-flat-button color="primary" (click)="saveOgLiveTemplates()">Guardar</button>
</div>
<mat-paginator [length]="length" <mat-paginator [length]="length"
[pageSize]="itemsPerPage" [pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions" [pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)"> (page)="onPageChange($event)">
</mat-paginator> </mat-paginator>
</div>

View File

@ -1,198 +1,155 @@
import { Component } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import {MatTableDataSource} from "@angular/material/table"; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {MatDialog} from "@angular/material/dialog"; import { HttpClient } from '@angular/common/http';
import {HttpClient} from "@angular/common/http"; import { ToastrService } from 'ngx-toastr';
import { CreatePxeTemplateComponent } from '../pxe/create-pxeTemplate/create-pxe-template.component'; import { PageEvent } from '@angular/material/paginator';
import {PageEvent} from "@angular/material/paginator";
import {ToastrService} from "ngx-toastr";
import {DatePipe} from "@angular/common";
import {DataService} from "./data.service";
@Component({ @Component({
selector: 'app-pxe-boot-files', selector: 'app-pxe-boot-files',
templateUrl: './pxe-boot-files.component.html', templateUrl: './pxe-boot-files.component.html',
styleUrl: './pxe-boot-files.component.css' styleUrls: ['./pxe-boot-files.component.css']
}) })
export class PxeBootFilesComponent { export class PxeBootFilesComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
pxeTemplates: any[] = [];
currentPage: number = 1;
dataSource = new MatTableDataSource<any>();
length: number = 0;
alertMessage: string | null = null;
itemsPerPage: number = 10;
loading:boolean = false;
filters: { [key: string]: string } = {};
page: number = 1;
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
datePipe: DatePipe = new DatePipe('es-ES');
selectedElements: string[] = [];
templateOptions: any[] = [];
columns = [
{
columnDef: 'id',
header: 'ID',
cell: (user: any) => `${user.id}`
},
{
columnDef: 'templateName',
header: 'Nombre de la plantilla',
cell: (user: any) => `${user.template.name}`
},
{
columnDef: 'clients',
header: 'Clientes',
cell: (user: any) => user.clients
},
{
columnDef: 'createdAt',
header: 'Fecha de creación',
cell: (user: any) => `${this.datePipe.transform(user.createdAt, 'dd/MM/yyyy hh:mm:ss')}`
}
];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
private apiUrl = `${this.baseUrl}/pxe-boot-files`; availableOrganizationalUnits: any[] = [];
selectedUnitChildren: any[] = [];
dataSource: any[] = [];
taskForm: FormGroup;
loadingUnits: boolean = false;
ogLiveOptions: any[] = [];
globalOgLive: string | null = null;
length: number = 0;
itemsPerPage: number = 10;
page: number = 0;
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
filters: any = {};
displayedColumns: string[] = ['id', 'name', 'ogLive'];
constructor( constructor(
public dialog: MatDialog, private fb: FormBuilder,
private http: HttpClient, private http: HttpClient,
private toastService: ToastrService, private toastService: ToastrService
private dataService: DataService ) {
) this.taskForm = this.fb.group({
{ } organizationalUnit: ['', Validators.required],
selectedChild: ['', Validators.required]
});
}
ngOnInit(): void { ngOnInit(): void {
this.search(); this.fetchOrganizationalUnits();
this.loadAlert(); this.fetchPxeTemplates();
this.dataService.getPxeTemplateOptions().subscribe( }
data => {
this.templateOptions = data; fetchOrganizationalUnits(): void {
this.loadingUnits = true;
this.http.get<any>(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=30`)
.subscribe(
response => {
this.availableOrganizationalUnits = response['hydra:member'].filter((item: any) => item.type === 'organizational-unit');
this.loadingUnits = false;
}, },
error => { error => {
console.error('Error fetching template options', error); this.toastService.error('Error al cargar las unidades organizativas');
this.loadingUnits = false;
} }
); );
} }
search(): void { fetchPxeTemplates(): void {
this.loading = true; this.http.get<any>(`${this.baseUrl}/pxe-templates?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters })
this.dataService.getPxeBootFiles(this.filters).subscribe( .subscribe(
data => { response => {
this.dataSource.data = data; this.ogLiveOptions = response['hydra:member'];
this.loading = false;
},
error => {
console.error('Error fetching pxe boot files', error);
this.loading = false;
}
);
}
toggleAction(image: any, action:string): void {
switch (action) {
case 'create':
this.http.post(`${this.apiUrl}/server/${image.uuid}/post`, {}).subscribe({
next: () => {
console.log('Plantilla cambiada');
this.search();
},
error: (error) => {
console.error('Error al cambiar la imagen:', error);
}
});
break;
case 'delete':
this.http.post(`${this.apiUrl}/server/${image.uuid}/delete`, {}).subscribe({
next: () => {
console.log('Plantilla cambiada');
this.search();
},
error: (error) => {
console.error('Error al cambiar la imagen:', error);
}
});
break;
default:
console.error('Acción no soportada:', action);
break;
}
}
deletePxeTemplate(uuid: string) {
// Lógica para eliminar una plantilla
}
editPxeTemplate(template: any) {
const dialogRef = this.dialog.open(CreatePxeTemplateComponent, {
data: template
});
dialogRef.afterClosed().subscribe(() => {
this.search();
});
}
/* editPxeBootFile(bootFile: any) {
const dialogRef = this.dialog.open(CreatePxeBootFileComponent, {
data: {
clients: bootFile.clients,
bootFile: bootFile
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.loadPxeBootFiles(); // Refrescar la lista después de la edición
}
});
} */
applyFilter() {
this.http.get<any>(`${this.apiUrl}?page=${this.page}&itemsPerPage=${this.itemsPerPage}`).subscribe({
next: (response) => {
this.dataSource.data = response['hydra:member'];
this.length = response['hydra:totalItems']; this.length = response['hydra:totalItems'];
}, },
error: (error) => { error => {
console.error('Error al cargar las imágenes:', error); this.toastService.error('Error al cargar las plantillas');
} }
);
}
onOrganizationalUnitChange(): void {
const selectedUnitId = this.taskForm.get('organizationalUnit')?.value;
const selectedUnit = this.availableOrganizationalUnits.find(unit => unit['@id'] === selectedUnitId);
if (selectedUnit && selectedUnit.children) {
this.selectedUnitChildren = selectedUnit.children.filter((child: any) => child.type === 'classroom');
} else {
this.selectedUnitChildren = [];
}
}
onChildChange(): void {
const selectedChildId = this.taskForm.get('selectedChild')?.value;
const selectedChild = this.selectedUnitChildren.find(child => child['@id'] === selectedChildId);
if (selectedChild && selectedChild.clients) {
this.dataSource = selectedChild.clients;
} else {
this.dataSource = [];
}
}
applyToAll(): void {
this.dataSource = this.dataSource.map(client => ({
...client,
ogLive: this.globalOgLive || client.ogLive
}));
}
saveOgLiveTemplates(): void {
const groupedByTemplate: { [key: string]: string[] } = {};
this.dataSource.forEach(client => {
if (client.ogLive) {
if (!groupedByTemplate[client.ogLive]) {
groupedByTemplate[client.ogLive] = [];
}
groupedByTemplate[client.ogLive].push(client['@id']);
}
});
Object.keys(groupedByTemplate).forEach(templateId => {
const clients = groupedByTemplate[templateId];
const url = `${this.baseUrl}${templateId}/add-clients`;
const payload = {
clients: clients
};
console.log('Payload:', url);
this.http.post(url, payload).subscribe({
next: () => {
this.toastService.success(`Clientes guardados correctamente para la plantilla ${templateId}`);
},
error: () => {
this.toastService.error(`Error al guardar clientes para la plantilla ${templateId}`);
}
});
}); });
} }
onPageChange(event: PageEvent) { onPageChange(event: PageEvent): void {
this.page = event.pageIndex; this.page = event.pageIndex;
this.itemsPerPage = event.pageSize; this.itemsPerPage = event.pageSize;
this.applyFilter(); this.fetchPxeTemplates();
}
loadAlert() {
this.http.get(`${this.apiUrl}/server/get-collection`)
.subscribe(response => {
// @ts-ignore
this.alertMessage = response.templates.length
}, error => {
console.error('Error al cargar la información del alert', error);
});
}
getIcon(): { name: string, color: string } {
if (Number(this.alertMessage) === this.length) {
return { name: 'check_circle', color: 'green' }; // Icono de check verde
} else {
return { name: 'cancel', color: 'red' }; // Icono de cruz roja
}
} }
syncOgCore(): void { syncOgCore(): void {
this.http.post(`${this.apiUrl}/sync`, {}) this.http.post(`${this.baseUrl}/sync`, {}).subscribe(
.subscribe(response => { () => {
this.toastService.success('Sincronización completada'); this.toastService.success('Sincronización completada');
this.search() },
}, error => { error => {
console.error('Error al sincronizar', error);
this.toastService.error('Error al sincronizar'); this.toastService.error('Error al sincronizar');
});
} }
);
}
} }