refs #985 Update pxe-boot-files component add logic

develop-jenkins
Alvaro Puente Mella 2024-10-21 15:05:23 +02:00
parent 4871d443a5
commit aaa7ae0f29
4 changed files with 215 additions and 314 deletions

View File

@ -1 +1 @@
NG_APP_BASE_API_URL=http://127.0.0.1:8001
NG_APP_BASE_API_URL=http://127.0.0.1:8001

View File

@ -1,90 +1,70 @@
.header-container {
.global-selectors {
display: flex;
justify-content: space-between;
align-items: center;
height: 100px;
padding: 10px;
margin-top: 16px;
margin-bottom: 15px;
gap: 10px;
}
.title {
font-size: 24px;
.global-selectors mat-form-field {
flex: 1;
margin-right: 10px;
}
.templates-button-row {
.global-selectors button {
height: 40px;
padding: 0 20px;
}
.filters-container {
display: flex;
justify-content: flex-start;
margin-top: 16px;
justify-content: space-between;
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 {
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-header>
<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>
<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">
<button mat-flat-button color="primary" (click)="syncOgCore()"> Sincronizar OgCore</button>
@ -16,67 +11,79 @@
</mat-accordion>
<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>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill" class="search-entity">
<mat-label i18n="@@searchLabel">Plantilla</mat-label>
<mat-select [(ngModel)]="filters['template']" (selectionChange)="search()" placeholder="Seleccionar entidad">
<mat-option [value]="''">Todos</mat-option>
<mat-option *ngFor="let template of templateOptions" [value]="template.id">{{ template.name }}</mat-option>
<div [formGroup]="taskForm" class="filters-container">
<!-- Unidad Organizacional -->
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Unidad Organizacional</mat-label>
<mat-select formControlName="organizationalUnit" (selectionChange)="onOrganizationalUnitChange()">
<mat-option *ngIf="loadingUnits" disabled>Cargando unidades...</mat-option>
<mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
{{ unit.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('organizationalUnit')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<!-- Aula -->
<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-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef>{{column.header}}</th>
<td mat-cell *matCellDef="let element">
<ng-container *ngIf="column.columnDef === 'clients'">
<button mat-icon-button [matMenuTriggerFor]="clientsMenu">
<mat-icon>visibility</mat-icon>
</button>
<mat-menu #clientsMenu="matMenu">
<ng-container *ngFor="let client of column.cell(element)">
<button mat-menu-item>
<mat-list>
<mat-list-item lines="3">
<span matListItemTitle>{{ client.name }}</span>
<span matListItemLine>{{ client.ip }}</span>
<span matListItemLine>{{client.mac}}</span>
</mat-list-item>
</mat-list>
</button>
</ng-container>
</mat-menu>
</ng-container>
<ng-container *ngIf="column.columnDef !== 'clients'">
{{column.cell(element)}}
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions">Acciones</th>
<td mat-cell *matCellDef="let image">
<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>
<mat-divider class="divider"></mat-divider>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<div class="paginator-container">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
<!-- Selectores globales -->
<div class="global-selectors">
<mat-form-field appearance="fill">
<mat-label>Global ogLive</mat-label>
<mat-select [(value)]="globalOgLive">
<mat-option *ngFor="let option of ogLiveOptions" [value]="option">{{ option }}</mat-option>
</mat-select>
</mat-form-field>
<button mat-flat-button color="primary" (click)="applyToAll()">Aplicar a todos</button>
</div>
<!-- Tabla de Clientes -->
<mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Columnas de la tabla -->
<ng-container matColumnDef="id">
<mat-header-cell *matHeaderCellDef> Id </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.id}} </mat-cell>
</ng-container>
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef> Nombre </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.name}} </mat-cell>
</ng-container>
<!-- ogLive -->
<ng-container matColumnDef="ogLive">
<mat-header-cell *matHeaderCellDef> Plantilla </mat-header-cell>
<mat-cell *matCellDef="let element">
<mat-form-field>
<mat-select [(value)]="element.ogLive">
<mat-option *ngFor="let option of ogLiveOptions" [value]="option">{{ option }}</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>
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>

View File

@ -1,198 +1,112 @@
import { Component } from '@angular/core';
import {MatTableDataSource} from "@angular/material/table";
import {MatDialog} from "@angular/material/dialog";
import {HttpClient} from "@angular/common/http";
import { CreatePxeTemplateComponent } from '../pxe/create-pxeTemplate/create-pxe-template.component';
import {PageEvent} from "@angular/material/paginator";
import {ToastrService} from "ngx-toastr";
import {DatePipe} from "@angular/common";
import {DataService} from "./data.service";
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import { PageEvent } from '@angular/material/paginator';
@Component({
selector: 'app-pxe-boot-files',
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;
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: string[] = ['Live Option 1', 'Live Option 2', 'Live Option 3'];
globalOgLive: string | null = null;
length: number = 100;
itemsPerPage: number = 10;
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
currentPage: number = 0;
// Actualizar las columnas a mostrar
displayedColumns: string[] = ['id', 'name', 'ogLive'];
constructor(
public dialog: MatDialog,
private fb: FormBuilder,
private http: HttpClient,
private toastService: ToastrService,
private dataService: DataService
)
{ }
private toastService: ToastrService
) {
this.taskForm = this.fb.group({
organizationalUnit: ['', Validators.required],
selectedChild: ['', Validators.required]
});
}
ngOnInit(): void {
this.fetchOrganizationalUnits();
}
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 => {
this.toastService.error('Error al cargar las unidades organizativas');
this.loadingUnits = false;
}
);
}
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
}));
}
onPageChange(event: PageEvent): void {
this.currentPage = event.pageIndex;
this.itemsPerPage = event.pageSize;
this.search();
this.loadAlert();
this.dataService.getPxeTemplateOptions().subscribe(
data => {
this.templateOptions = data;
},
error => {
console.error('Error fetching template options', error);
}
);
}
search(): void {
this.loading = true;
this.dataService.getPxeBootFiles(this.filters).subscribe(
data => {
this.dataSource.data = data;
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'];
},
error: (error) => {
console.error('Error al cargar las imágenes:', error);
}
});
}
onPageChange(event: PageEvent) {
this.page = event.pageIndex;
this.itemsPerPage = event.pageSize;
this.applyFilter();
}
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
}
// Logic for searching data if it comes from a paginated API would go here
}
syncOgCore(): void {
this.http.post(`${this.apiUrl}/sync`, {})
.subscribe(response => {
this.http.post(`${this.baseUrl}/sync`, {}).subscribe(
() => {
this.toastService.success('Sincronización completada');
this.search()
}, error => {
console.error('Error al sincronizar', error);
},
error => {
this.toastService.error('Error al sincronizar');
});
}
);
}
}