Merge pull request 'develop' (#7) from develop into main
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
testing/ogGui-multibranch/pipeline/tag This commit is unstable Details

Reviewed-on: #7
pull/10/head opengnsys_devel-0.0.15
Manuel Aranda Rosales 2024-11-26 12:45:08 +01:00
commit ad0d4c1a40
19 changed files with 73 additions and 646 deletions

View File

@ -113,8 +113,6 @@ import { CreateSoftwareProfileComponent } from './components/software-profile/cr
import { OperativeSystemComponent } from './components/operative-system/operative-system.component';
import { CreateOperativeSystemComponent } from './components/operative-system/create-operative-system/create-operative-system.component';
import { ShowTemplateContentComponent } from './components/ogboot/pxe/show-template-content/show-template-content.component';
import { AddClientsToPxeComponent } from './components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component';
import { ClientsComponent } from './components/ogboot/pxe/clients/clients.component';
import { RepositoriesComponent } from './components/repositories/repositories.component';
import { CreateRepositoryComponent } from './components/repositories/create-repository/create-repository.component';
import { ExecuteCommandComponent } from './components/commands/main-commands/execute-command/execute-command.component';
@ -200,8 +198,6 @@ export function HttpLoaderFactory(http: HttpClient) {
OperativeSystemComponent,
CreateOperativeSystemComponent,
ShowTemplateContentComponent,
AddClientsToPxeComponent,
ClientsComponent,
RepositoriesComponent,
CreateRepositoryComponent,
ExecuteCommandComponent,

View File

@ -42,7 +42,6 @@ table {
align-items: center;
height: 100px;
padding: 10px;
margin-top: 16px;
}
.mat-elevation-z8 {
@ -55,21 +54,15 @@ table {
margin-bottom: 30px;
}
.example-headers-align .mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
.images-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
gap: 10px;
}
.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field {
margin-left: 8px;
}
.button-row {
display: table-cell;
max-width: 600px;
}
.button-row .mat-mdc-button-base {
margin: 8px 8px 8px 0;
mat-spinner {
margin: 0 auto;
align-self: center;
}

View File

@ -1,17 +1,3 @@
<mat-accordion class="example-headers-align">
<mat-expansion-panel hideToggle>
<mat-expansion-panel-header joyrideStep="serverInfoStep" [text]="'serverInfoDescription' | translate">
<mat-panel-title>{{ 'serverInfoTitle' | translate }}</mat-panel-title>
</mat-expansion-panel-header>
<div class="button-row">
<button mat-flat-button color="primary" (click)="syncOgBoot()">{{ 'syncDatabaseButton' | translate }}</button>
</div>
<div class="button-row">
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
</div>
</mat-expansion-panel>
</mat-accordion>
<div class="header-container">
<button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon>
@ -20,6 +6,7 @@
{{ 'adminImagesTitle' | translate }}
</h2>
<div class="images-button-row">
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
<button mat-flat-button color="primary" (click)="addImage()" joyrideStep="addImageStep" [text]="'addImageButtonDescription' | translate">
{{ 'addImageButton' | translate }}
</button>
@ -35,7 +22,7 @@
<mat-icon matSuffix>search</mat-icon>
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="searchDefaultImageStep" [text]="'searchDefaultDescription' | translate">
<mat-label>{{ 'searchDefaultLabel' | translate }}</mat-label>
<mat-select [(ngModel)]="filters['isDefault']" (selectionChange)="search()" [placeholder]="'selectOptionPlaceholder' | translate">
@ -55,7 +42,8 @@
</mat-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" [text]="'tableDescription' | translate">
<mat-spinner *ngIf="loading"></mat-spinner>
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" [text]="'tableDescription' | translate">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef>{{ column.header }}</th>
<td mat-cell *matCellDef="let image">
@ -78,7 +66,7 @@
<ng-container *ngIf="column.columnDef === 'name'">
<span matTooltip="{{ image.name }}">
{{ image.name ? image.name.substring(0, 20) + '...' : '' }}
{{ image.name }}
</span>
</ng-container>
@ -95,8 +83,8 @@
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'actionsColumnHeader' | translate }}</th>
<td mat-cell *matCellDef="let image" joyrideStep="actionsStep" [text]="'actionsDescription' | translate">
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actionsColumnHeader' | translate }}</th>
<td mat-cell *matCellDef="let image" style="text-align: center;" joyrideStep="actionsStep" [text]="'actionsDescription' | translate">
<button mat-icon-button color="info" (click)="showOgLive($event, image)">
<mat-icon>{{ 'viewIcon' | translate }}</mat-icon>
</button>
@ -110,7 +98,7 @@
<mat-icon>menu</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="toggleAction(image, 'install')">{{ 'installOption' | translate }}</button>
<button mat-menu-item [disabled]="image.installed" (click)="toggleAction(image, 'install')">{{ 'installOption' | translate }}</button>
<button mat-menu-item [disabled]="!image.installed" (click)="toggleAction(image, 'uninstall')">
{{ 'uninstallOption' | translate }}
</button>

View File

@ -44,11 +44,6 @@ export class PXEimagesComponent implements OnInit {
header: 'Nombre de imagen',
cell: (user: any) => `${user.name}`
},
{
columnDef: 'downloadUrl',
header: 'Url descarga',
cell: (user: any) => `${user.downloadUrl}`
},
{
columnDef: 'isDefault',
header: 'Imagen por defecto',
@ -56,7 +51,7 @@ export class PXEimagesComponent implements OnInit {
},
{
columnDef: 'installed',
header: 'Imagen instalada en ogBoot',
header: 'Imagen instalada',
cell: (user: any) => `${user.installed}`
},
{
@ -83,8 +78,11 @@ export class PXEimagesComponent implements OnInit {
) {}
ngOnInit(): void {
this.loading = true;
this.search();
this.loadAlert();
this.syncOgBoot()
this.loading = false;
}
addImage(): void {
@ -99,15 +97,12 @@ export class PXEimagesComponent implements OnInit {
}
search(): void {
this.loading = true;
this.dataService.getImages(this.filters).subscribe(
data => {
this.dataSource.data = data;
this.loading = false;
},
error => {
console.error('Error fetching og lives', error);
this.loading = false;
}
);
}
@ -250,7 +245,7 @@ export class PXEimagesComponent implements OnInit {
syncOgBoot(): void {
this.http.post(`${this.apiUrl}/sync`, {})
.subscribe(response => {
this.toastService.success('Sincronización completada');
this.toastService.success('Sincronización con oGBoot exitosa');
this.search()
}, error => {
console.error('Error al sincronizar', error);
@ -261,7 +256,6 @@ export class PXEimagesComponent implements OnInit {
iniciarTour(): void {
this.joyrideService.startTour({
steps: [
'serverInfoStep',
'titleStep',
'addImageStep',
'searchNameStep',
@ -275,5 +269,5 @@ export class PXEimagesComponent implements OnInit {
themeColor: '#3f51b5'
});
}
}

View File

@ -1,17 +0,0 @@
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100px;
}
mat-form-field {
width: 100%;
}
mat-dialog-actions {
display: flex;
justify-content: flex-end;
}

View File

@ -1,39 +0,0 @@
<h2 mat-dialog-title>{{ 'addClientsTitle' | translate: { subnetName: data.subnetName } }}</h2>
<mat-dialog-content>
<mat-form-field appearance="fill" class="search-select">
<input
type="text"
matInput
[formControl]="clientControl"
[matAutocomplete]="clientAuto"
[placeholder]="'selectClientPlaceholder' | translate">
<mat-autocomplete
#clientAuto="matAutocomplete"
[displayWith]="displayFnClient"
(optionSelected)="onOptionClientSelected($event.option.value)">
<mat-option
*ngFor="let client of filteredClients | async"
[value]="client">
{{ client.name }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<div *ngIf="selectedClients.length > 0">
<h3>{{ 'selectedClientsTitle' | translate }}</h3>
<ul>
<li *ngFor="let client of selectedClients">
{{ client.name }}
<button mat-icon-button color="warn" (click)="removeClient(client)">
<mat-icon>{{ 'deleteIcon' | translate }}</mat-icon>
</button>
</li>
</ul>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="close()">{{ 'cancelButton' | translate }}</button>
<button mat-button (click)="save()">{{ 'addButton' | translate }}</button>
</mat-dialog-actions>

View File

@ -1,69 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AddClientsToPxeComponent } from './add-clients-to-pxe.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatOptionModule } from '@angular/material/core';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinner, MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatListModule } from '@angular/material/list';
import { MatTabsModule } from '@angular/material/tabs';
import { TranslateModule } from '@ngx-translate/core';
describe('AddClientsToPxeComponent', () => {
let component: AddClientsToPxeComponent;
let fixture: ComponentFixture<AddClientsToPxeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AddClientsToPxeComponent],
imports: [
HttpClientTestingModule,
ToastrModule.forRoot(),
BrowserAnimationsModule,
MatDividerModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatPaginatorModule,
MatTooltipModule,
FormsModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
MatDialogModule,
MatSelectModule,
MatTabsModule,
MatAutocompleteModule,
MatListModule,
TranslateModule.forRoot()
],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: {} }
]
})
.compileComponents();
fixture = TestBed.createComponent(AddClientsToPxeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,98 +0,0 @@
import {Component, Inject} from '@angular/core';
import {Observable, startWith} from "rxjs";
import {FormControl} from "@angular/forms";
import {HttpClient} from "@angular/common/http";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {ToastrService} from "ngx-toastr";
import {map} from "rxjs/operators";
@Component({
selector: 'app-add-clients-to-pxe',
templateUrl: './add-clients-to-pxe.component.html',
styleUrl: './add-clients-to-pxe.component.css'
})
export class AddClientsToPxeComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
clients: any[] = [];
selectedClients: any[] = [];
loading: boolean = true;
filters: { [key: string]: string } = {};
filteredClients!: Observable<any[]>;
clientControl = new FormControl();
constructor(
private http: HttpClient,
public dialogRef: MatDialogRef<AddClientsToPxeComponent>,
private toastService: ToastrService,
@Inject(MAT_DIALOG_DATA) public data: { subnetUuid: string, subnetName: string }
) {}
ngOnInit(): void {
this.loading = true;
this.loadClients();
this.filteredClients = this.clientControl.valueChanges.pipe(
startWith(''),
map(value => (typeof value === 'string' ? value : value?.name)),
map(name => (name ? this._filterClients(name) : this.clients.slice()))
);
}
loadClients() {
this.http.get<any>( `${this.baseUrl}/clients?&page=1&itemsPerPage=10000&exists[template]=false`).subscribe(
response => {
this.clients = response['hydra:member'];
this.loading = false;
},
error => {
console.error('Error fetching parent units:', error);
this.loading = false;
}
);
}
save() {
const postData = {
clients: this.selectedClients.map(client => client['@id'])
};
this.http.post(`${this.baseUrl}/pxe-templates/${this.data.subnetUuid}/add-clients`, postData).subscribe(
response => {
this.toastService.success('Clientes asignados correctamente');
},
error => {
this.toastService.error(error.error['hydra:description']);
}
);
this.dialogRef.close(this.selectedClients);
}
close() {
this.dialogRef.close();
}
removeClient(client: any) {
const index = this.selectedClients.indexOf(client);
if (index >= 0) {
this.selectedClients.splice(index, 1);
}
}
private _filterClients(name: string): any[] {
const filterValue = name.toLowerCase();
return this.clients.filter(client => client.name.toLowerCase().includes(filterValue));
}
displayFnClient(client: any): string {
return client && client.name ? client.name : '';
}
onOptionClientSelected(client: any) {
if (!this.selectedClients.includes(client)) {
this.selectedClients.push(client);
}
this.clientControl.setValue('');
}
}

View File

@ -1,47 +0,0 @@
mat-dialog-actions {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
button {
margin-left: 10px;
}
.green-icon {
color: green;
}
.red-icon {
color: red;
}
.spacing-container {
margin-top: 20px;
margin-bottom: 16px;
}
.list-item-content {
display: flex;
align-items: flex-start; /* Alinea el contenido al inicio */
justify-content: space-between; /* Espacio entre los textos y los íconos */
width: 100%; /* Asegúrate de que el contenido ocupe todo el ancho */
}
.text-content {
flex-grow: 1;
margin-right: 16px;
margin-left: 10px;
}
.icon-container {
display: flex;
align-items: center;
}
.right-icon {
margin-left: 8px;
cursor: pointer;
}

View File

@ -1,32 +0,0 @@
<h2 mat-dialog-title>{{ 'manageClientsTitle' | translate }}</h2>
<mat-dialog-content>
<mat-list>
<ng-container *ngFor="let client of clients">
<mat-list-item>
<div class="list-item-content">
<mat-icon matListItemIcon [ngClass]="{'red-icon': client.pxeSync === false || !client.pxeSync, 'green-icon': client.pxeSync === true}">
computer
</mat-icon>
<div class="text-content">
<div matListItemTitle>{{ client.name }}</div>
<div matListItemLine>{{ client.mac }}</div>
</div>
<div class="icon-container">
<button mat-icon-button color="info" (click)="showInfo(client)">
<mat-icon>{{ 'viewIcon' | translate }}</mat-icon>
</button>
<button mat-icon-button color="primary" (click)="addClientToTemplate(client)">
<mat-icon>{{ 'syncIcon' | translate }}</mat-icon>
</button>
<button mat-icon-button color="warn" class="right-icon" (click)="deleteClient(client)">
<mat-icon>{{ 'deleteIcon' | translate }}</mat-icon>
</button>
</div>
</div>
</mat-list-item>
</ng-container>
</mat-list>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button type="button" (click)="onCancel()">{{ 'cancelButton' | translate }}</button>
</mat-dialog-actions>

View File

@ -1,70 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ClientsComponent } from './clients.component';
import { MatInputModule } from '@angular/material/input';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatOptionModule } from '@angular/material/core';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinner, MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatListModule } from '@angular/material/list';
import { MatTabsModule } from '@angular/material/tabs';
import { TranslateModule } from '@ngx-translate/core';
describe('ClientsComponent', () => {
let component: ClientsComponent;
let fixture: ComponentFixture<ClientsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ClientsComponent],
imports: [
HttpClientTestingModule,
ToastrModule.forRoot(),
BrowserAnimationsModule,
MatDividerModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatPaginatorModule,
MatTooltipModule,
FormsModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
MatDialogModule,
MatSelectModule,
MatTabsModule,
MatAutocompleteModule,
MatListModule,
TranslateModule.forRoot()
],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: {data: {id: 123}} }
]
})
.compileComponents();
fixture = TestBed.createComponent(ClientsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,107 +0,0 @@
import {Component, Inject} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog";
import {HttpClient} from "@angular/common/http";
import {ToastrService} from "ngx-toastr";
import {DeleteModalComponent} from "../../../../shared/delete_modal/delete-modal/delete-modal.component";
import {Observable} from "rxjs";
import {
ServerInfoDialogComponent
} from "../../../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component";
@Component({
selector: 'app-clients',
templateUrl: './clients.component.html',
styleUrl: './clients.component.css'
})
export class ClientsComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
templateForm!: FormGroup;
clients: any[] = [];
alertMessage: string | null = null;
constructor(
public dialogRef: MatDialogRef<ClientsComponent>,
public dialog: MatDialog,
private http: HttpClient,
private toastService: ToastrService,
@Inject(MAT_DIALOG_DATA) public data: any
) {}
ngOnInit() {
this.getPxeClients()
}
getPxeClients(): void {
this.http.get<any>(`${this.baseUrl}/clients?template.id=${this.data.data.id}`).subscribe({
next: data => {
this.clients = data['hydra:member']
},
error: error => {
console.error('Error al obtener los clientes PXE:', error);
}
});
}
addClientToTemplate(client: any): void {
const postData = {
client: client['@id']
};
this.http.post(`${this.baseUrl}/pxe-templates/${this.data.data.uuid}/sync-client`, postData).subscribe(
response => {
this.toastService.success('Clientes asignados correctamente');
this.getPxeClients()
},
error => {
this.toastService.error(error.error['hydra:description']);
}
);
}
loadAlert(client: any): Observable<any> {
return this.http.post<any>(`${this.baseUrl}/clients/server/${client.uuid}/get-pxe`, {});
}
showInfo(client:any) {
this.loadAlert(client).subscribe(
response => {
this.alertMessage = response.message;
this.dialog.open(ServerInfoDialogComponent, {
width: '600px',
data: {
message: this.alertMessage
}
});
},
error => {
console.error('Error al cargar la información del alert', error);
}
);
}
deleteClient(client: any): void {
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: client.name }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.http.post(`${this.baseUrl}/pxe-templates/${this.data.data.uuid}/delete-client`, { client: client['@id'] }).subscribe({
next: () => {
this.toastService.success('Cliente eliminado exitosamente');
this.dialogRef.close();
},
error: (error) => {
this.toastService.error(error.error['hydra:description']);
}
});
}})
}
onCancel(): void {
this.dialogRef.close(false);
}
}

View File

@ -34,7 +34,7 @@ set ISODIR ogLive
kernel http://__SERVERIP__/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs}
initrd http://__SERVERIP__/tftpboot/\${ISODIR}/oginitrd.img
boot`,
disco: `#!ipxe
iseq \${platform} efi && goto uefi_boot || goto bios_boot
@ -104,8 +104,7 @@ exit`
this.dialogRef.close(true);
},
error: error => {
this.toastService.error('Error al crear la plantilla PXE');
this.dialogRef.close(false);
this.toastService.error(error.error['hydra:description']);
}
});
}

View File

@ -34,6 +34,7 @@ table {
display: flex;
justify-content: space-between;
align-items: center;
height: 100px;
padding: 10px;
}
@ -41,27 +42,21 @@ table {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
}
.template-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
gap: 10px;
}
mat-spinner {
margin: 0 auto;
align-self: center;
}
.paginator-container {
display: flex;
justify-content: end;
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;
}

View File

@ -1,23 +1,10 @@
<mat-accordion class="example-headers-align">
<mat-expansion-panel hideToggle>
<mat-expansion-panel-header joyrideStep="serverInfoStep" text="{{ 'serverInfoDescription' | translate }}">
<mat-panel-title>{{ 'serverInfoTitle' | translate }}</mat-panel-title>
</mat-expansion-panel-header>
<div class="example-button-row">
<button mat-flat-button color="primary" (click)="syncTemplates()">{{ 'syncDatabaseButton' | translate }}</button>
</div>
<div class="example-button-row">
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
</div>
</mat-expansion-panel>
</mat-accordion>
<div class="header-container">
<button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon>
</button>
<h2 class="title" joyrideStep="titleStep" text="{{ 'adminPxeDescription' | translate }}">{{ 'adminPxeTitle' | translate }}</h2>
<div class="pxe-button-row">
<div class="template-button-row">
<button mat-flat-button color="accent" (click)="openInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
<button mat-flat-button color="primary" (click)="addPxeTemplate()" joyrideStep="addTemplateStep" text="{{ 'addTemplateButtonDescription' | translate }}">{{ 'addTemplateButton' | translate }}</button>
</div>
</div>
@ -41,7 +28,8 @@
</mat-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableDescription' | translate }}">
<mat-spinner *ngIf="loading"></mat-spinner>
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableDescription' | translate }}">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef>{{ column.header }}</th>
<td mat-cell *matCellDef="let image">
@ -57,26 +45,17 @@
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'actionsColumn' | translate }}</th>
<td mat-cell *matCellDef="let template">
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actionsColumn' | translate }}</th>
<td mat-cell *matCellDef="let template" style="text-align: center;">
<button mat-icon-button color="info" (click)="showTemplate($event, template)">
<mat-icon>visibility</mat-icon>
</button>
<button mat-icon-button color="info" [disabled]="template.clientsLength === 0" (click)="editClients($event, template)">
<mat-icon>computer</mat-icon>
</button>
<button mat-icon-button color="primary" (click)="editPxeTemplate(template)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>menu</mat-icon>
<button mat-icon-button color="warn" (click)="toggleAction(template, 'delete')">
<mat-icon>{{ 'deleteIcon' | translate }}</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="toggleAction(template, 'create')">{{ 'createServerButton' | translate }}</button>
<button mat-menu-item (click)="addClientsToPxe(template)">{{ 'addClientButton' | translate }}</button>
<button mat-menu-item (click)="toggleAction(template, 'sync')">{{ 'syncDatabaseButton' | translate }}</button>
<button mat-menu-item (click)="toggleAction(template, 'delete')">{{ 'deleteButton' | translate }}</button>
</mat-menu>
</td>
</ng-container>

View File

@ -6,21 +6,12 @@ import { MatTableDataSource } from '@angular/material/table';
import { PageEvent } from '@angular/material/paginator';
import { ToastrService } from 'ngx-toastr';
import { DatePipe } from '@angular/common';
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
import { DataService } from './data.service';
import {
ShowOrganizationalUnitComponent
} from "../../groups/shared/organizational-units/show-organizational-unit/show-organizational-unit.component";
import {ShowTemplateContentComponent} from "./show-template-content/show-template-content.component";
import {ServerInfoDialogComponent} from "../../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component";
import {
AddClientsToSubnetComponent
} from "../../ogdhcp/og-dhcp-subnets/add-clients-to-subnet/add-clients-to-subnet.component";
import {Subnet} from "../../ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component";
import {AddClientsToPxeComponent} from "./add-clients-to-pxe/add-clients-to-pxe.component";
import {Observable} from "rxjs";
import {ClientsComponent} from "./clients/clients.component";
import { JoyrideService } from 'ngx-joyride';
import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/delete-modal.component";
@Component({
selector: 'app-pxe',
@ -78,19 +69,20 @@ export class PxeComponent {
) { }
ngOnInit(): void {
this.loading = true;
this.search();
this.loadAlert()
this.syncTemplates()
this.loading = false;
}
search(): void {
this.loading = true;
this.dataService.getPxeTemplates(this.filters).subscribe(
data => {
this.dataSource.data = data;
this.loading = false;
},
error => {
console.error('Error fetching pxe templates', error);
this.loading = false;
}
);
}
@ -105,6 +97,7 @@ export class PxeComponent {
});
}
editPxeTemplate(template: any) {
const dialogRef = this.dialog.open(CreatePxeTemplateComponent, {
data: template, // Pasa los datos del template para edición
@ -118,40 +111,26 @@ export class PxeComponent {
toggleAction(image: any, action: string): void {
switch (action) {
case 'create':
this.http.post(`${this.apiUrl}/server/${image.uuid}/post`, {}).subscribe({
next: (response) => {
this.search();
// @ts-ignore
this.toastService.success(response.success);
},
error: (error) => {
this.toastService.error(error.error.error);
}
});
break;
case 'sync':
this.http.get(`${this.apiUrl}/server/${image.uuid}/get`, {}).subscribe({
next: (response) => {
this.search();
// @ts-ignore
this.toastService.success(response.success);
},
error: (error) => {
this.toastService.error(error.error.error);
}
});
break;
case 'delete':
this.http.post(`${this.apiUrl}/server/${image.uuid}/delete`, {}).subscribe({
next: () => {
this.toastService.success('Plantilla eliminada correctamente');
this.search();
},
error: (error) => {
this.toastService.error(error.error.error);
}
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: image.name }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.http.post(`${this.apiUrl}/server/${image.uuid}/delete`, {}).subscribe({
next: () => {
this.toastService.success('Plantilla eliminada exitosamente');
this.search();
},
error: (error) => {
console.error('Error al eliminar la subred', error);
this.toastService.error(error.error['hydra:description']);
}
});
}
})
break;
default:
console.error('Acción no soportada:', action);
@ -164,11 +143,6 @@ export class PxeComponent {
const dialogRef = this.dialog.open(ShowTemplateContentComponent, { data: { data }, width: '700px'});
}
editClients(event: MouseEvent, data: any): void {
event.stopPropagation();
const dialogRef = this.dialog.open(ClientsComponent, { data: { data }, width: '700px'});
}
syncTemplates() {
this.http.post(`${this.apiUrl}/sync`, {})
.subscribe(response => {
@ -180,17 +154,6 @@ export class PxeComponent {
});
}
addClientsToPxe(template: Subnet) {
const dialogRef = this.dialog.open(AddClientsToPxeComponent, {
width: '600px',
data: { subnetUuid: template.uuid, subnetName: template.name }
});
dialogRef.afterClosed().subscribe(result => {
this.search();
});
}
applyFilter() {
this.http.get<any>(`${this.apiUrl}?page=${this.page}&itemsPerPage=${this.itemsPerPage}`).subscribe({
next: (response) => {
@ -207,7 +170,7 @@ export class PxeComponent {
return this.http.get<any>(`${this.apiUrl}/server/get-collection`);
}
openSubnetInfoDialog() {
openInfoDialog() {
this.loadAlert().subscribe(
response => {
this.alertMessage = response.message;
@ -248,5 +211,5 @@ export class PxeComponent {
themeColor: '#3f51b5'
});
}
}

View File

@ -38,7 +38,6 @@
</mat-form-field>
</div>
<mat-divider class="divider"></mat-divider>
<mat-spinner *ngIf="loading"></mat-spinner>
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="Visualiza y administra las subredes listadas según los filtros aplicados.">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">

View File

@ -6,7 +6,7 @@
"loginError": "Login error: {{error}}",
"labelUsers": "Users",
"labelRoles": "Roles",
"adminImagesTitle": "Manage clients",
"adminImagesTitle": "Manage images",
"addUser": "Add users",
"searchLabel": "Search image name",
"searchPlaceholder": "Search",

View File

@ -6,7 +6,7 @@
"loginError": "Login error: {{error}}",
"labelUsers": "Usuarios",
"labelRoles": "Roles",
"adminImagesTitle": "Administrar clientes",
"adminImagesTitle": "Administrar OgLives",
"addUser": "Añadir usuarios",
"searchLabel": "Buscar nombre de imagen",
"searchPlaceholder": "Búsqueda",