Some improvements. Added filter search and refactor

oggui/ogboot
Manuel Aranda Rosales 2024-09-02 10:07:35 +02:00
parent 2d6785198f
commit cf3785d863
24 changed files with 359 additions and 311 deletions

View File

@ -10,7 +10,7 @@ import { UsersComponent } from './components/admin/users/users/users.component';
import { RolesComponent } from './components/admin/roles/roles/roles.component'; import { RolesComponent } from './components/admin/roles/roles/roles.component';
import { GroupsComponent } from './components/groups/groups.component'; import { GroupsComponent } from './components/groups/groups.component';
import { ImagesComponent } from './components/ogboot/images/images.component'; import { ImagesComponent } from './components/ogboot/images/images.component';
import { PxeComponent } from './components/ogboot/pxe/pxe/pxe.component'; import { PxeComponent } from './components/ogboot/pxe/pxe.component';
import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component'; import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component';
import {OgbootStatusComponent} from "./components/ogboot/ogboot-status/ogboot-status.component"; import {OgbootStatusComponent} from "./components/ogboot/ogboot-status/ogboot-status.component";
import { OgdhcpComponent } from './components/ogdhcp/ogdhcp.component'; import { OgdhcpComponent } from './components/ogdhcp/ogdhcp.component';

View File

@ -73,9 +73,9 @@ import { ImagesComponent } from './components/ogboot/images/images.component';
import { CreateImageComponent } from './components/ogboot/images/create-image/create-image/create-image.component'; import { CreateImageComponent } from './components/ogboot/images/create-image/create-image/create-image.component';
import { EditImageComponent } from './components/ogboot/images/edit-image/edit-image/edit-image.component'; import { EditImageComponent } from './components/ogboot/images/edit-image/edit-image/edit-image.component';
import { InfoImageComponent } from './components/ogboot/images/info-image/info-image/info-image.component'; import { InfoImageComponent } from './components/ogboot/images/info-image/info-image/info-image.component';
import { PxeComponent } from './components/ogboot/pxe/pxe/pxe.component'; import { PxeComponent } from './components/ogboot/pxe/pxe.component';
import { CreatePxeTemplateComponent } from './components/ogboot/pxe/pxe/create-pxeTemplate/create-pxe-template/create-pxe-template.component'; import { CreatePxeTemplateComponent } from './components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component';
import { EditPxeTemplateComponent } from './components/ogboot/pxe/pxe/edit-pxe-template/edit-pxe-template.component'; import { EditPxeTemplateComponent } from './components/ogboot/pxe/edit-pxe-template/edit-pxe-template.component';
import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component'; import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component';
import {MatExpansionPanel, MatExpansionPanelDescription, MatExpansionPanelTitle} from "@angular/material/expansion"; import {MatExpansionPanel, MatExpansionPanelDescription, MatExpansionPanelTitle} from "@angular/material/expansion";
import { OgbootStatusComponent } from './components/ogboot/ogboot-status/ogboot-status.component'; import { OgbootStatusComponent } from './components/ogboot/ogboot-status/ogboot-status.component';

View File

@ -91,7 +91,6 @@ export class GroupsComponent implements OnInit {
); );
} }
onSelectUnidad(unidad: UnidadOrganizativa): void { onSelectUnidad(unidad: UnidadOrganizativa): void {
this.selectedUnidad = unidad; this.selectedUnidad = unidad;
this.selectedDetail = unidad; this.selectedDetail = unidad;

View File

@ -0,0 +1,34 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DataService {
private apiUrl = 'http://127.0.0.1:8080/og-lives?page=1&itemsPerPage=1000';
constructor(private http: HttpClient) {}
getImages(search: string = ''): Observable<any[]> {
let url = `${this.apiUrl}`;
if (search) {
url += `&name=${encodeURIComponent(search)}`;
}
return this.http.get<any>(url).pipe(
map(response => {
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
return response['hydra:member'];
} else {
throw new Error('Unexpected response format');
}
}),
catchError(error => {
console.error('Error fetching images', error);
return throwError(error);
})
);
}
}

View File

@ -1,11 +1,3 @@
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
height: 100px;
padding: 10px;
}
.title { .title {
font-size: 24px; font-size: 24px;
} }
@ -16,11 +8,6 @@
margin-top: 16px; margin-top: 16px;
} }
button {
margin-left: 10px;
margin-bottom: 20px;
}
.divider { .divider {
margin: 20px 0; margin: 20px 0;
} }
@ -50,33 +37,26 @@ button {
flex: 1; flex: 1;
} }
.mat-icon-button { .image-name{
margin-left: 16px;
align-self: center;
}
.mat-menu {
min-width: 160px;
}
.image-name{
cursor: pointer; cursor: pointer;
} }
table { table {
width: 100%; width: 100%;
margin-top: 50px; margin-top: 50px;
} }
.search-container mat-form-field {
width: 100%;
}
.header-container { .header-container {
margin-top: 16px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} height: 100px;
padding: 10px;
.header-container h1 { margin-top: 16px;
margin: 0;
} }
.mat-elevation-z8 { .mat-elevation-z8 {

View File

@ -22,27 +22,32 @@
</div> </div>
</div> </div>
<mat-divider class="divider"></mat-divider> <mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill">
<mat-label i18n="@@searchLabel">Buscar nombre de imagen</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="searchTerm" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
</mat-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef"> <ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th> <th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let image" [ngClass]="{'clickable': column.columnDef === 'name'}" <td mat-cell *matCellDef="let image" [ngClass]="{'clickable': column.columnDef === 'name'}"
(click)="column.columnDef === 'name' && showInfo(image)"> (click)="column.columnDef === 'name' && showInfo(image)">
<!-- Condición para mostrar íconos para isDefault e installed -->
<ng-container *ngIf="column.columnDef === 'isDefault' || column.columnDef === 'installed'"> <ng-container *ngIf="column.columnDef === 'isDefault' || column.columnDef === 'installed'">
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'"> <mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }} {{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
</mat-icon> </mat-icon>
</ng-container> </ng-container>
<!-- Mostrar el downloadUrl truncado con tooltip -->
<ng-container *ngIf="column.columnDef === 'downloadUrl'"> <ng-container *ngIf="column.columnDef === 'downloadUrl'">
<span matTooltip="{{ image.downloadUrl }}"> <span matTooltip="{{ image.downloadUrl }}">
{{ image.downloadUrl ? image.downloadUrl.substring(0, 20) + '...' : '' }} {{ image.downloadUrl ? image.downloadUrl.substring(0, 20) + '...' : '' }}
</span> </span>
</ng-container> </ng-container>
<!-- Mostrar otros campos normalmente -->
<ng-container *ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'"> <ng-container *ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'">
{{ column.cell(image) }} {{ column.cell(image) }}
</ng-container> </ng-container>
@ -50,7 +55,6 @@
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions">Acciones</th> <th mat-header-cell *matHeaderCellDef i18n="@@columnActions">Acciones</th>
<td mat-cell *matCellDef="let image"> <td mat-cell *matCellDef="let image">

View File

@ -9,6 +9,7 @@ import {PageEvent} from "@angular/material/paginator";
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";
import { DatePipe } from "@angular/common"; import { DatePipe } from "@angular/common";
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component'; import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
import {DataService} from "./data.service";
@Component({ @Component({
selector: 'app-images', selector: 'app-images',
@ -23,6 +24,8 @@ export class ImagesComponent implements OnInit {
page: number = 1; page: number = 1;
pageSizeOptions: number[] = [5, 10, 20, 40, 100]; pageSizeOptions: number[] = [5, 10, 20, 40, 100];
selectedElements: string[] = []; selectedElements: string[] = [];
loading:boolean = false;
searchTerm: string = '';
alertMessage: string | null = null; alertMessage: string | null = null;
readonly panelOpenState = signal(false); readonly panelOpenState = signal(false);
datePipe: DatePipe = new DatePipe('es-ES'); datePipe: DatePipe = new DatePipe('es-ES');
@ -65,26 +68,15 @@ export class ImagesComponent implements OnInit {
constructor( constructor(
public dialog: MatDialog, public dialog: MatDialog,
private http: HttpClient, private http: HttpClient,
private dataService: DataService,
private toastService: ToastrService private toastService: ToastrService
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
this.loadImages(); this.search();
this.loadAlert(); this.loadAlert();
} }
loadImages(): void {
this.http.get<any>(`${this.apiUrl}?page=1&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);
}
});
}
addImage(): void { addImage(): void {
const dialogRef = this.dialog.open(CreateImageComponent, { const dialogRef = this.dialog.open(CreateImageComponent, {
width: '400px' width: '400px'
@ -92,10 +84,24 @@ export class ImagesComponent implements OnInit {
dialogRef.afterClosed().subscribe(result => { dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed'); console.log('The dialog was closed');
this.loadImages(); // Opcional: recargar imágenes después de añadir una nueva this.search();
}); });
} }
search(): void {
this.loading = true;
this.dataService.getImages(this.searchTerm).subscribe(
data => {
this.dataSource.data = data;
this.loading = false;
},
error => {
console.error('Error fetching og lives', error);
this.loading = false;
}
);
}
showInfo(image: any): void { showInfo(image: any): void {
const dialogRef = this.dialog.open(InfoImageComponent, { const dialogRef = this.dialog.open(InfoImageComponent, {
width: '700px', width: '700px',
@ -110,7 +116,7 @@ export class ImagesComponent implements OnInit {
next: () => { next: () => {
console.log('Imagen cambiada'); console.log('Imagen cambiada');
this.toastService.success('Petición de cambio de imagen enviada'); this.toastService.success('Petición de cambio de imagen enviada');
this.loadImages(); this.search();
}, },
error: (error) => { error: (error) => {
console.error('Error al cambiar la imagen:', error); console.error('Error al cambiar la imagen:', error);
@ -123,7 +129,7 @@ export class ImagesComponent implements OnInit {
next: () => { next: () => {
console.log('Imagen cambiada'); console.log('Imagen cambiada');
this.toastService.success('Petición de instalación enviada'); this.toastService.success('Petición de instalación enviada');
this.loadImages(); this.search();
}, },
error: (error) => { error: (error) => {
console.error('Error al instalar la imagen:', error); console.error('Error al instalar la imagen:', error);
@ -137,7 +143,7 @@ export class ImagesComponent implements OnInit {
console.log('Imagen cambiada'); console.log('Imagen cambiada');
this.toastService.success('Petición de desinstalación enviada'); this.toastService.success('Petición de desinstalación enviada');
/* this.deleteImage(image); */ /* this.deleteImage(image); */
this.loadImages(); this.search();
}, },
error: (error) => { error: (error) => {
console.error('Error al desinstalar la imagen:', error); console.error('Error al desinstalar la imagen:', error);
@ -151,29 +157,6 @@ export class ImagesComponent implements OnInit {
} }
} }
deleteImage(image: any): void {
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: image.name }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.http.delete(`${this.apiUrl}/${image.uuid}`).subscribe({
next: () => {
console.log('Imagen eliminada');
this.toastService.success('Image deleted successfully');
this.loadImages();
},
error: (error) => {
console.error('Error al eliminar la imagen:', error);
}
});
} else {
console.log('Eliminación de imagen cancelada');
}
}); }
editImage(image: any): void { editImage(image: any): void {
const dialogRef = this.dialog.open(EditImageComponent, { const dialogRef = this.dialog.open(EditImageComponent, {
width: '700px', width: '700px',
@ -182,7 +165,7 @@ export class ImagesComponent implements OnInit {
dialogRef.afterClosed().subscribe(result => { dialogRef.afterClosed().subscribe(result => {
if (result) { if (result) {
this.loadImages(); this.search();
} }
}); });
} }
@ -227,7 +210,7 @@ export class ImagesComponent implements OnInit {
this.http.post(`${this.apiUrl}/sync`, {}) this.http.post(`${this.apiUrl}/sync`, {})
.subscribe(response => { .subscribe(response => {
this.toastService.success('Sincronización completada'); this.toastService.success('Sincronización completada');
this.loadImages() this.search()
}, error => { }, error => {
console.error('Error al sincronizar', error); console.error('Error al sincronizar', error);
this.toastService.error('Error al sincronizar'); this.toastService.error('Error al sincronizar');

View File

@ -2,7 +2,9 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 100px;
padding: 10px; padding: 10px;
margin-top: 16px;
} }
.title { .title {
@ -15,11 +17,6 @@
margin-top: 16px; margin-top: 16px;
} }
button {
margin-left: 10px;
margin-bottom: 20px;
}
.divider { .divider {
margin: 20px 0; margin: 20px 0;
} }
@ -54,26 +51,13 @@ button {
align-self: center; align-self: center;
} }
.mat-menu {
min-width: 160px;
}
.template-name{ .template-name{
cursor: pointer; cursor: pointer;
} }
table { table {
width: 100%; width: 100%;
} margin-top: 50px;
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-container h1 {
margin: 0;
} }
.mat-elevation-z8 { .mat-elevation-z8 {

View File

@ -1,27 +1,60 @@
<mat-accordion class="example-headers-align">
<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>
</div>
</mat-expansion-panel>
</mat-accordion>
<div class="header-container"> <div class="header-container">
<h2 class="title" i18n="@@adminPXETitle">Administrar ficheros de arranque PXE</h2> <h2 class="title" i18n="@@adminPXETitle">Administrar ficheros de arranque PXE</h2>
</div> </div>
<mat-divider class="divider"></mat-divider> <mat-divider class="divider"></mat-divider>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef"> <ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th> <th mat-header-cell *matHeaderCellDef>{{column.header}}</th>
<td mat-cell *matCellDef="let image" [ngClass]="{'clickable': column.columnDef === 'name'}" <td mat-cell *matCellDef="let element">
(click)="column.columnDef === 'name' && showPxeInfo(image)"> <ng-container *ngIf="column.columnDef === 'clients'">
{{ column.cell(image) }} <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> </td>
<td mat-cell *matCellDef="let user" > {{ column.cell(user) }} </td>
</ng-container> </ng-container>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions">Acciones</th> <th mat-header-cell *matHeaderCellDef i18n="@@columnActions">Acciones</th>
<td mat-cell *matCellDef="let image"> <td mat-cell *matCellDef="let image">
<button mat-button color="primary" (click)="editPxeTemplate(image)" i18n="@@editImage">Editar</button> <button mat-icon-button [matMenuTriggerFor]="menu">
<button mat-button color="warn" (click)="deletePxeTemplate(image)" i18n="@@buttonDeleteUser">Eliminar</button> <mat-icon>menu</mat-icon>
<button mat-button [matMenuTriggerFor]="menu">
Acciones (ogBoot)
</button> </button>
<mat-menu #menu="matMenu"> <mat-menu #menu="matMenu">
<button mat-menu-item (click)="toggleAction(image, 'create')">Crear</button>
<button mat-menu-item (click)="toggleAction(image, 'delete')">Eliminar</button> <button mat-menu-item (click)="toggleAction(image, 'delete')">Eliminar</button>
</mat-menu> </mat-menu>
</td> </td>

View File

@ -2,9 +2,11 @@ import { Component } from '@angular/core';
import {MatTableDataSource} from "@angular/material/table"; import {MatTableDataSource} from "@angular/material/table";
import {MatDialog} from "@angular/material/dialog"; import {MatDialog} from "@angular/material/dialog";
import {HttpClient} from "@angular/common/http"; import {HttpClient} from "@angular/common/http";
import { CreatePxeTemplateComponent } from '../pxe/pxe/create-pxeTemplate/create-pxe-template/create-pxe-template.component'; import { CreatePxeTemplateComponent } from '../pxe/create-pxeTemplate/create-pxe-template.component';
import { EditPxeTemplateComponent } from '../pxe/pxe/edit-pxe-template/edit-pxe-template.component'; import { EditPxeTemplateComponent } from '../pxe/edit-pxe-template/edit-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";
@Component({ @Component({
selector: 'app-pxe-boot-files', selector: 'app-pxe-boot-files',
@ -16,9 +18,11 @@ export class PxeBootFilesComponent {
currentPage: number = 1; currentPage: number = 1;
dataSource = new MatTableDataSource<any>(); dataSource = new MatTableDataSource<any>();
length: number = 0; length: number = 0;
alertMessage: string | null = null;
itemsPerPage: number = 10; itemsPerPage: number = 10;
page: number = 1; page: number = 1;
pageSizeOptions: number[] = [5, 10, 20, 40, 100]; pageSizeOptions: number[] = [5, 10, 20, 40, 100];
datePipe: DatePipe = new DatePipe('es-ES');
selectedElements: string[] = []; selectedElements: string[] = [];
columns = [ columns = [
{ {
@ -27,27 +31,38 @@ export class PxeBootFilesComponent {
cell: (user: any) => `${user.id}` cell: (user: any) => `${user.id}`
}, },
{ {
columnDef: 'name', columnDef: 'templateName',
header: 'Nombre de la plantilla', header: 'Nombre de la plantilla',
cell: (user: any) => `${user.name}` cell: (user: any) => `${user.template.name}`
},
{
columnDef: 'clients',
header: 'Clientes',
cell: (user: any) => user.clients
}, },
{ {
columnDef: 'createdAt', columnDef: 'createdAt',
header: 'Fecha de creación', header: 'Fecha de creación',
cell: (user: any) => `${user.createdAt}` cell: (user: any) => `${this.datePipe.transform(user.createdAt, 'dd/MM/yyyy hh:mm:ss')}`
} }
]; ];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
private apiUrl = 'http://127.0.0.1:8080/pxe-boot-files'; private apiUrl = 'http://127.0.0.1:8080/pxe-boot-files';
constructor(public dialog: MatDialog, private http: HttpClient) { } constructor(
public dialog: MatDialog,
private http: HttpClient,
private toastService: ToastrService
)
{ }
ngOnInit(): void { ngOnInit(): void {
this.loadPxeTemplates(); this.loadPxeBootFiles();
this.loadAlert();
} }
loadPxeTemplates(): void { loadPxeBootFiles(): void {
this.http.get<any>(`${this.apiUrl}?page=1&itemsPerPage=${this.itemsPerPage}`).subscribe({ this.http.get<any>(`${this.apiUrl}?page=1&itemsPerPage=${this.itemsPerPage}`).subscribe({
next: (response) => { next: (response) => {
this.dataSource.data = response['hydra:member']; this.dataSource.data = response['hydra:member'];
@ -65,7 +80,7 @@ export class PxeBootFilesComponent {
}); });
dialogRef.afterClosed().subscribe(() => { dialogRef.afterClosed().subscribe(() => {
this.loadPxeTemplates(); this.loadPxeBootFiles();
}); });
} }
@ -79,7 +94,7 @@ export class PxeBootFilesComponent {
this.http.post(`${this.apiUrl}/server/${image.uuid}/post`, {}).subscribe({ this.http.post(`${this.apiUrl}/server/${image.uuid}/post`, {}).subscribe({
next: () => { next: () => {
console.log('Plantilla cambiada'); console.log('Plantilla cambiada');
this.loadPxeTemplates(); this.loadPxeBootFiles();
}, },
error: (error) => { error: (error) => {
console.error('Error al cambiar la imagen:', error); console.error('Error al cambiar la imagen:', error);
@ -90,7 +105,7 @@ export class PxeBootFilesComponent {
this.http.post(`${this.apiUrl}/server/${image.uuid}/delete`, {}).subscribe({ this.http.post(`${this.apiUrl}/server/${image.uuid}/delete`, {}).subscribe({
next: () => { next: () => {
console.log('Plantilla cambiada'); console.log('Plantilla cambiada');
this.loadPxeTemplates(); this.loadPxeBootFiles();
}, },
error: (error) => { error: (error) => {
console.error('Error al cambiar la imagen:', error); console.error('Error al cambiar la imagen:', error);
@ -113,7 +128,7 @@ export class PxeBootFilesComponent {
}); });
dialogRef.afterClosed().subscribe(() => { dialogRef.afterClosed().subscribe(() => {
this.loadPxeTemplates(); this.loadPxeBootFiles();
}); });
} }
@ -135,4 +150,33 @@ export class PxeBootFilesComponent {
this.applyFilter(); 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
}
}
syncOgCore(): void {
this.http.post(`${this.apiUrl}/sync`, {})
.subscribe(response => {
this.toastService.success('Sincronización completada');
this.loadPxeBootFiles()
}, error => {
console.error('Error al sincronizar', error);
this.toastService.error('Error al sincronizar');
});
}
} }

View File

@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DataService {
private apiUrl = 'http://127.0.0.1:8080/pxe-templates?page=1&itemsPerPage=1000';
constructor(private http: HttpClient) {}
getPxeTemplates(search: string = ''): Observable<any[]> {
let url = `${this.apiUrl}`;
if (search) {
url += `&name=${encodeURIComponent(search)}`;
}
return this.http.get<any>(url).pipe(
map(response => {
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
return response['hydra:member'];
} else {
throw new Error('Unexpected response format');
}
}),
catchError(error => {
console.error('Error fetching pxe templates', error);
return throwError(error);
})
);
}
}

View File

@ -0,0 +1,52 @@
form {
max-width: 600px;
margin: 20px auto;
padding: 20px;
}
mat-form-field {
width: 100%;
margin-bottom: 20px;
}
pre {
background-color: #eceff1;
padding: 15px;
border-radius: 4px;
white-space: pre-wrap;
word-wrap: break-word;
font-size: 0.9rem;
color: #333;
}
mat-dialog-actions {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
button {
margin-left: 10px;
}
button[type="submit"] {
background-color: #3f51b5;
color: #fff;
}
button[type="submit"]:disabled {
background-color: #c5cae9;
}
h2 {
margin-bottom: 20px;
font-size: 1.5rem;
color: #000000;
text-align: center;
}
h3 {
margin-top: 30px;
font-size: 1.2rem;
color: #000000;
}

View File

@ -4,23 +4,13 @@
align-items: center; align-items: center;
height: 100px; height: 100px;
padding: 10px; padding: 10px;
margin-top: 16px;
} }
.title { .title {
font-size: 24px; font-size: 24px;
} }
.templates-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
}
button {
margin-left: 10px;
margin-bottom: 20px;
}
.divider { .divider {
margin: 20px 0; margin: 20px 0;
} }
@ -55,10 +45,6 @@ button {
align-self: center; align-self: center;
} }
.mat-menu {
min-width: 160px;
}
.template-name{ .template-name{
cursor: pointer; cursor: pointer;
} }
@ -68,6 +54,10 @@ table {
margin-top: 50px; margin-top: 50px;
} }
.search-container mat-form-field {
width: 100%;
}
.header-container { .header-container {
margin-top: 16px; margin-top: 16px;
display: flex; display: flex;
@ -75,10 +65,6 @@ table {
align-items: center; align-items: center;
} }
.header-container h1 {
margin: 0;
}
.mat-elevation-z8 { .mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2); box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
} }

View File

@ -8,6 +8,9 @@
</mat-expansion-panel-header> </mat-expansion-panel-header>
<p *ngIf="alertMessage">Plantillas creadsa en servidor ogBoot: {{ alertMessage }}</p> <p *ngIf="alertMessage">Plantillas creadsa en servidor ogBoot: {{ alertMessage }}</p>
<p *ngIf="alertMessage">Plantillas creadsa en servidor ogCore (base de datos): {{ length }}</p> <p *ngIf="alertMessage">Plantillas creadsa en servidor ogCore (base de datos): {{ length }}</p>
<div class="example-button-row">
<button mat-flat-button color="primary" (click)="syncOgCore()"> Sincronizar OgCore</button>
</div>
</mat-expansion-panel> </mat-expansion-panel>
</mat-accordion> </mat-accordion>
@ -18,6 +21,14 @@
</div> </div>
</div> </div>
<mat-divider class="divider"></mat-divider> <mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill">
<mat-label i18n="@@searchLabel">Buscar nombre de plantilla</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="searchTerm" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar </mat-hint>
</mat-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef"> <ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th> <th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>

View File

@ -1,13 +1,14 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { CreatePxeTemplateComponent } from './create-pxeTemplate/create-pxe-template/create-pxe-template.component'; import { CreatePxeTemplateComponent } from './create-pxeTemplate/create-pxe-template.component';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { EditPxeTemplateComponent } from './edit-pxe-template/edit-pxe-template.component'; import { EditPxeTemplateComponent } from './edit-pxe-template/edit-pxe-template.component';
import {MatTableDataSource} from "@angular/material/table"; import {MatTableDataSource} from "@angular/material/table";
import {PageEvent} from "@angular/material/paginator"; import {PageEvent} from "@angular/material/paginator";
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";
import {DatePipe} from "@angular/common"; import {DatePipe} from "@angular/common";
import { DeleteModalComponent } from '../../../../shared/delete_modal/delete-modal/delete-modal.component'; import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
import {DataService} from "./data.service";
@Component({ @Component({
selector: 'app-pxe', selector: 'app-pxe',
@ -23,6 +24,8 @@ export class PxeComponent {
page: number = 1; page: number = 1;
pageSizeOptions: number[] = [5, 10, 20, 40, 100]; pageSizeOptions: number[] = [5, 10, 20, 40, 100];
selectedElements: string[] = []; selectedElements: string[] = [];
loading:boolean = false;
searchTerm: string = ''
alertMessage: string | null = null; alertMessage: string | null = null;
datePipe: DatePipe = new DatePipe('es-ES'); datePipe: DatePipe = new DatePipe('es-ES');
selectedItem: any = null; selectedItem: any = null;
@ -57,24 +60,26 @@ export class PxeComponent {
public dialog: MatDialog, public dialog: MatDialog,
private http: HttpClient, private http: HttpClient,
private toastService: ToastrService, private toastService: ToastrService,
private dataService: DataService
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.loadPxeTemplates(); this.search()
this.loadAlert() this.loadAlert();
} }
loadPxeTemplates(): void { search(): void {
this.http.get<any>(`${this.apiUrl}?page=1&itemsPerPage=${this.itemsPerPage}`).subscribe({ this.loading = true;
next: (response) => { this.dataService.getPxeTemplates(this.searchTerm).subscribe(
this.dataSource.data = response['hydra:member']; data => {
this.length = response['hydra:totalItems']; this.dataSource.data = data;
console.log('Plantillas PXE cargadas:', this.pxeTemplates); this.loading = false;
}, },
error: error => { error => {
console.error('Error al cargar plantillas PXE:', error); console.error('Error fetching pxe templates', error);
this.loading = false;
} }
}); );
} }
addPxeTemplate() { addPxeTemplate() {
@ -83,7 +88,7 @@ export class PxeComponent {
}); });
dialogRef.afterClosed().subscribe(() => { dialogRef.afterClosed().subscribe(() => {
this.loadPxeTemplates(); this.search();
}); });
} }
@ -98,7 +103,7 @@ export class PxeComponent {
case 'create': case 'create':
this.http.post(`${this.apiUrl}/server/${image.uuid}/post`, {}).subscribe({ this.http.post(`${this.apiUrl}/server/${image.uuid}/post`, {}).subscribe({
next: (response) => { next: (response) => {
this.loadPxeTemplates(); this.search();
// @ts-ignore // @ts-ignore
this.toastService.success(response.message); this.toastService.success(response.message);
}, },
@ -111,7 +116,7 @@ export class PxeComponent {
this.http.post(`${this.apiUrl}/server/${image.uuid}/delete`, {}).subscribe({ this.http.post(`${this.apiUrl}/server/${image.uuid}/delete`, {}).subscribe({
next: () => { next: () => {
console.log('Plantilla cambiada'); console.log('Plantilla cambiada');
this.loadPxeTemplates(); this.search();
}, },
error: (error) => { error: (error) => {
console.error('Error al cambiar la imagen:', error); console.error('Error al cambiar la imagen:', error);
@ -124,40 +129,14 @@ export class PxeComponent {
} }
} }
deletePxeTemplate(template: any) {
// Lógica para eliminar una plantilla
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px'
});
console.log('Eliminando pxe:', template.uuid);
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.http.delete(`http://127.0.0.1:8080/pxe-templates/${template.uuid}`).subscribe({
next: () => {
console.log('pxe eliminado');
this.toastService.success('PXE deleted successfully');
this.loadPxeTemplates();
},
error: (error) => {
console.error('Error al eliminar la pxe:', error);
}
});
} else {
console.log('Eliminación de pxe cancelada');
}
});
}
editPxeTemplate(template: any) { editPxeTemplate(template: any) {
const dialogRef = this.dialog.open(EditPxeTemplateComponent, { const dialogRef = this.dialog.open(EditPxeTemplateComponent, {
data: template data: template,
width: '600px'
}); });
dialogRef.afterClosed().subscribe(() => { dialogRef.afterClosed().subscribe(() => {
this.loadPxeTemplates(); this.search();
}); });
} }
@ -197,4 +176,14 @@ export class PxeComponent {
} }
} }
syncOgCore(): void {
this.http.post(`${this.apiUrl}/sync`, {})
.subscribe(response => {
this.toastService.success('Sincronización completada');
this.search()
}, error => {
console.error('Error al sincronizar', error);
this.toastService.error('Error al sincronizar');
});
}
} }

View File

@ -1,86 +0,0 @@
mat-form-field {
width: 100%;
margin-bottom: 16px;
padding: 5px;
}
.button-group {
display: flex;
justify-content: space-between;
}
button {
width: 48%;
}
:host {
display: block;
padding: 20px;
background-color: #f5f5f5;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.mat-form-field {
width: 100%;
margin-bottom: 16px;
}
.mat-step-label {
font-weight: bold;
font-size: 1.1em;
}
button {
margin: 8px 0;
}
.mat-stepper-header {
background-color: #fff;
padding: 16px;
border-bottom: 1px solid #e0e0e0;
border-radius: 8px 8px 0 0;
}
.mat-stepper-horizontal-line {
border-color: #3f51b5;
}
.mat-stepper-horizontal {
background-color: #fff;
padding: 0;
border-radius: 8px;
overflow: hidden;
}
.mat-step-header {
background-color: #e8eaf6;
color: #3f51b5;
}
.mat-step-header .mat-step-icon {
background-color: #3f51b5;
color: #fff;
}
.mat-stepper-content {
padding: 16px;
background-color: #fff;
border-radius: 0 0 8px 8px;
}
pre {
background-color: #e8eaf6;
padding: 16px;
border-radius: 4px;
font-size: 12px;
overflow-x: auto;
}
.mat-raised-button {
margin-right: 8px;
}
.mat-button {
margin-right: 8px;
}