PXE templates

oggui/ogboot
Alvaro Puente Mella 2024-08-14 16:52:42 +02:00
parent e71561966a
commit 3d23eb86ab
22 changed files with 703 additions and 10 deletions

View File

@ -10,6 +10,7 @@ import { UsersComponent } from './components/pages/admin/users/users/users.compo
import { RolesComponent } from './components/pages/admin/roles/roles/roles.component';
import { GroupsComponent } from './components/groups/groups.component';
import { ImagesComponent } from './components/images/images/images.component';
import { PxeComponent } from './components/pxe/pxe/pxe.component';
const routes: Routes = [
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
{
@ -22,6 +23,7 @@ const routes: Routes = [
{ path: 'user-groups', component: RolesComponent },
{ path: 'groups', component: GroupsComponent },
{ path: 'images', component: ImagesComponent },
{ path: 'pxe', component: PxeComponent },
],
},
{

View File

@ -74,6 +74,10 @@ import { AcctionsModalComponent } from './components/groups/acctions-modal/accti
import { ImagesComponent } from './components/images/images/images.component';
import { CreateImageComponent } from './components/images/images/create-image/create-image/create-image.component';
import { EditImageComponent } from './components/images/images/edit-image/edit-image/edit-image.component';
import { InfoImageComponent } from './components/images/images/info-image/info-image/info-image.component';
import { PxeComponent } from './components/pxe/pxe/pxe.component';
import { CreatePxeTemplateComponent } from './components/pxe/pxe/create-pxeTemplate/create-pxe-template/create-pxe-template.component';
import { EditPxeTemplateComponent } from './components/pxe/pxe/edit-pxe-template/edit-pxe-template.component';
@NgModule({
declarations: [
@ -110,7 +114,11 @@ import { EditImageComponent } from './components/images/images/edit-image/edit-i
AcctionsModalComponent,
ImagesComponent,
CreateImageComponent,
EditImageComponent
EditImageComponent,
InfoImageComponent,
PxeComponent,
CreatePxeTemplateComponent,
EditPxeTemplateComponent
],
bootstrap: [AppComponent],
imports: [BrowserModule,

View File

@ -64,3 +64,7 @@ button {
margin-right: 20px;
transform: scale(1.5);
}
.image-name{
cursor: pointer;
}

View File

@ -9,13 +9,13 @@
<div class="lists-container">
<div class="imagesLists-container">
<mat-card class="card unidad-card">
<mat-card-title>Imágenes</mat-card-title>
<mat-card-title>Imágenes disponibles</mat-card-title>
<mat-card-content>
<div *ngIf="images.length === 0">No hay imágenes disponibles.</div>
<div *ngFor="let image of images">
<div class="image-container" (click)="showInfo(image)">
<div class="image-container">
<mat-icon class="cd-icon">album</mat-icon>
<h4 matLine>{{ image.name }}</h4>
<h4 class="image-name" (click)="showInfo(image)">{{ image.name }}</h4>
<button mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>more_vert</mat-icon>
</button>

View File

@ -3,6 +3,7 @@ import { MatDialog } from '@angular/material/dialog';
import { HttpClient } from '@angular/common/http';
import { CreateImageComponent } from './create-image/create-image/create-image.component';
import { EditImageComponent } from './edit-image/edit-image/edit-image.component';
import { InfoImageComponent } from './info-image/info-image/info-image.component';
@Component({
selector: 'app-images',
@ -47,8 +48,10 @@ export class ImagesComponent implements OnInit {
}
showInfo(image: any): void {
// Implementar lógica para mostrar información
console.log('Mostrar info:', image);
const dialogRef = this.dialog.open(InfoImageComponent, {
width: '300px',
data: image
});
}
toggleStatus(image: any): void {
@ -60,7 +63,7 @@ export class ImagesComponent implements OnInit {
this.http.delete(`${this.apiUrl}/${uuid}`).subscribe({
next: () => {
console.log('Imagen eliminada');
this.loadImages(); // Recargar imágenes después de eliminar
this.loadImages();
},
error: (error) => {
console.error('Error al eliminar la imagen:', error);
@ -71,12 +74,11 @@ export class ImagesComponent implements OnInit {
editImage(image: any): void {
const dialogRef = this.dialog.open(EditImageComponent, {
width: '300px',
data: image // Pasa los datos de la imagen a editar
data: image
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
// Opcional: Actualiza la lista de imágenes o realiza otras acciones necesarias
this.loadImages();
}
});

View File

@ -0,0 +1,10 @@
<h2 mat-dialog-title>Imagen ogLive</h2>
<mat-dialog-content>
<h3>Nombre</h3>
<p>{{name}}</p>
<h3>URL</h3>
<p>{{downloadUrl}}</p>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()">Cancel</button>
</mat-dialog-actions>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InfoImageComponent } from './info-image.component';
describe('InfoImageComponent', () => {
let component: InfoImageComponent;
let fixture: ComponentFixture<InfoImageComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [InfoImageComponent]
})
.compileComponents();
fixture = TestBed.createComponent(InfoImageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,28 @@
import { Component, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ToastrService } from 'ngx-toastr';
@Component({
selector: 'app-info-image',
templateUrl: './info-image.component.html',
styleUrls: ['./info-image.component.css']
})
export class InfoImageComponent {
name: string;
downloadUrl: string;
constructor(
private toastService: ToastrService,
private http: HttpClient,
public dialogRef: MatDialogRef<InfoImageComponent>,
@Inject(MAT_DIALOG_DATA) public data: { name: string, downloadUrl: string, uuid: string }
) {
this.name = data.name;
this.downloadUrl = data.downloadUrl;
}
onNoClick(): void {
this.dialogRef.close();
}
}

View File

@ -37,7 +37,7 @@
<span i18n="@@gallery">ogLive</span>
</span>
</mat-list-item>
<mat-list-item>
<mat-list-item routerLink="/pxe">
<span class="entry">
<mat-icon class="icon">chevron_right</mat-icon>
<span i18n="@@upload">PXE</span>

View File

@ -0,0 +1,86 @@
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;
}

View File

@ -0,0 +1,51 @@
<h2>Crear Plantilla PXE</h2>
<mat-horizontal-stepper [linear]="true" #stepper>
<mat-step [stepControl]="firstFormGroup">
<form [formGroup]="firstFormGroup">
<ng-template matStepLabel>Nombre de la plantilla</ng-template>
<mat-form-field>
<mat-label>Nombre de la Plantilla</mat-label>
<input matInput formControlName="name" placeholder="Nombre de la plantilla" required />
</mat-form-field>
<div>
<button mat-button matStepperNext>Continuar</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup">
<ng-template matStepLabel>Configurar parámetros</ng-template>
<mat-form-field appearance="fill">
<mat-label>Tiempo de espera (en segundos)</mat-label>
<input matInput formControlName="timeout" type="number" required />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Estilo de tiempo de espera</mat-label>
<mat-select formControlName="timeoutStyle" required>
<mat-option value="hidden">Oculto</mat-option>
<mat-option value="visible">Visible</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Directorio ISO</mat-label>
<input matInput formControlName="isoDir" required />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Argumentos del Kernel</mat-label>
<input matInput formControlName="kernelArgs" required />
</mat-form-field>
<div>
<button mat-button matStepperPrevious>Atrás</button>
<button mat-button matStepperNext>Siguiente</button>
</div>
</form>
</mat-step>
<mat-step>
<ng-template matStepLabel>Vista previa y creación</ng-template>
<pre>{{ previewContent }}</pre>
<div class="button-group">
<button mat-flat-button color="primary" (click)="onCreate()" >Crear</button>
<button mat-button matStepperPrevious>Atrás</button>
</div>
</mat-step>
</mat-horizontal-stepper>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CreatePxeTemplateComponent } from './create-pxe-template.component';
describe('CreatePxeTemplateComponent', () => {
let component: CreatePxeTemplateComponent;
let fixture: ComponentFixture<CreatePxeTemplateComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CreatePxeTemplateComponent]
})
.compileComponents();
fixture = TestBed.createComponent(CreatePxeTemplateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,84 @@
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-create-pxe-template',
templateUrl: './create-pxe-template.component.html',
styleUrl: './create-pxe-template.component.css'
})
export class CreatePxeTemplateComponent {
firstFormGroup!: FormGroup; // Use `!` to assert that this will be initialized
secondFormGroup!: FormGroup; // Use `!` to assert that this will be initialized
previewContent: string = '';
constructor(
public dialogRef: MatDialogRef<CreatePxeTemplateComponent>,
private http: HttpClient,
private fb: FormBuilder
) {}
ngOnInit() {
this.firstFormGroup = this.fb.group({
name: ['', Validators.required]
});
this.secondFormGroup = this.fb.group({
timeout: [0, Validators.required],
timeoutStyle: ['hidden', Validators.required],
isoDir: ['OGLIVE', Validators.required],
kernelArgs: ['INFOHOST', Validators.required]
});
this.secondFormGroup.valueChanges.subscribe(() => {
this.updatePreview();
});
}
updatePreview() {
const formValues = { ...this.firstFormGroup.value, ...this.secondFormGroup.value };
this.previewContent = `#!ipxe
set timeout ${formValues.timeout}
set timeout-style ${formValues.timeoutStyle}
set ISODIR ${formValues.isoDir}
set default 0
set kernelargs ${formValues.kernelArgs}
#Menú de entrada para seleccionar OgLive
:try_iso
kernel http://SERVERIP/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs} || goto fallback
initrd http://SERVERIP/tftpboot/\${ISODIR}/oginitrd.img
boot
:fallback
echo "OgLive default"
set ISODIR ogLive
kernel http://SERVERIP/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs}
initrd http://SERVERIP/tftpboot/\${ISODIR}/oginitrd.img
boot`;
}
onCreate(): void {
const formValues = { ...this.firstFormGroup.value, ...this.secondFormGroup.value };
const payload = {
name: formValues.name,
templateContent: "this.previewContent"
};
console.log(payload)
this.http.post<any>('http://127.0.0.1:8080/pxe-templates', payload).subscribe({
next: data => {
console.log('Plantilla PXE creada:', data);
this.dialogRef.close(true);
},
error: error => {
console.error('Error al crear la plantilla PXE:', error);
this.dialogRef.close(false);
}
});
}
onCancel(): void {
this.dialogRef.close(false);
}
}

View File

@ -0,0 +1,51 @@
<h2>Editar Plantilla PXE</h2>
<mat-horizontal-stepper [linear]="true" #stepper>
<mat-step [stepControl]="firstFormGroup">
<form [formGroup]="firstFormGroup">
<ng-template matStepLabel>Nombre de la plantilla</ng-template>
<mat-form-field>
<mat-label>Nombre de la Plantilla</mat-label>
<input matInput formControlName="name" placeholder="Nombre de la plantilla" required />
</mat-form-field>
<div>
<button mat-button matStepperNext>Continuar</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup">
<ng-template matStepLabel>Configurar parámetros</ng-template>
<mat-form-field appearance="fill">
<mat-label>Tiempo de espera (en segundos)</mat-label>
<input matInput formControlName="timeout" type="number" required />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Estilo de tiempo de espera</mat-label>
<mat-select formControlName="timeoutStyle" required>
<mat-option value="hidden">Oculto</mat-option>
<mat-option value="visible">Visible</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Directorio ISO</mat-label>
<input matInput formControlName="isoDir" required />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Argumentos del Kernel</mat-label>
<input matInput formControlName="kernelArgs" required />
</mat-form-field>
<div>
<button mat-button matStepperPrevious>Atrás</button>
<button mat-button matStepperNext>Siguiente</button>
</div>
</form>
</mat-step>
<mat-step>
<ng-template matStepLabel>Vista previa y edición</ng-template>
<pre>{{ previewContent }}</pre>
<div class="button-group">
<button mat-flat-button color="primary" (click)="onEdit()" >Guardar Cambios</button>
<button mat-button matStepperPrevious>Atrás</button>
</div>
</mat-step>
</mat-horizontal-stepper>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditPxeTemplateComponent } from './edit-pxe-template.component';
describe('EditPxeTemplateComponent', () => {
let component: EditPxeTemplateComponent;
let fixture: ComponentFixture<EditPxeTemplateComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [EditPxeTemplateComponent]
})
.compileComponents();
fixture = TestBed.createComponent(EditPxeTemplateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,112 @@
import { HttpClient } from '@angular/common/http';
import { Component, Inject } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
@Component({
selector: 'app-edit-pxe-template',
templateUrl: './edit-pxe-template.component.html',
styleUrl: './edit-pxe-template.component.css'
})
export class EditPxeTemplateComponent {
firstFormGroup!: FormGroup;
secondFormGroup!: FormGroup;
previewContent: string = '';
constructor(
private fb: FormBuilder,
private http: HttpClient,
public dialogRef: MatDialogRef<EditPxeTemplateComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) { }
ngOnInit(): void {
this.firstFormGroup = this.fb.group({
name: [this.data.name, Validators.required],
});
const parsedContent = this.parseTemplateContent(this.data.templateContent);
this.secondFormGroup = this.fb.group({
timeout: [parsedContent.timeout, Validators.required],
timeoutStyle: [parsedContent.timeoutStyle, Validators.required],
isoDir: [parsedContent.isoDir, Validators.required],
kernelArgs: [parsedContent.kernelArgs, Validators.required],
});
this.updatePreviewContent();
this.secondFormGroup.valueChanges.subscribe(() => {
this.updatePreviewContent();
});
}
parseTemplateContent(templateContent: string): any {
const lines = templateContent.split('\n');
let timeout = '';
let timeoutStyle = '';
let isoDir = '';
let kernelArgs = '';
lines.forEach(line => {
if (line.startsWith('set timeout ')) {
timeout = line.replace('set timeout ', '').trim();
} else if (line.startsWith('set timeout-style ')) {
timeoutStyle = line.replace('set timeout-style ', '').trim();
} else if (line.startsWith('set ISODIR ')) {
isoDir = line.replace('set ISODIR ', '').trim();
} else if (line.startsWith('set kernelargs ')) {
kernelArgs = line.replace('set kernelargs ', '').trim();
}
});
return { timeout, timeoutStyle, isoDir, kernelArgs };
}
updatePreviewContent(): void {
const { timeout, timeoutStyle, isoDir, kernelArgs } = this.secondFormGroup.value;
this.previewContent = `
#!ipxe
set timeout ${timeout}
set timeout-style ${timeoutStyle}
set ISODIR ${isoDir}
set kernelargs ${kernelArgs}
# Menú de entrada para seleccionar OgLive
:try_iso
kernel http://SERVERIP/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs} || goto fallback
initrd http://SERVERIP/tftpboot/\${ISODIR}/oginitrd.img
boot
:fallback
echo "OgLive default"
set ISODIR ogLive
kernel http://SERVERIP/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs}
initrd http://SERVERIP/tftpboot/\${ISODIR}/oginitrd.img
boot
`;
}
onEdit(): void {
if (this.firstFormGroup.valid && this.secondFormGroup.valid) {
const updatedTemplate = {
name: this.firstFormGroup.value.name,
templateContent: this.previewContent.trim(),
};
this.http.patch(`http://127.0.0.1:8080/pxe-templates/${this.data.uuid}`, updatedTemplate)
.subscribe({
next: () => {
console.log('Plantilla actualizada correctamente');
this.dialogRef.close(true);
},
error: (error) => {
console.error('Error al actualizar la plantilla:', error);
}
});
}
}
onCancel(): void {
this.dialogRef.close();
}
}

View File

@ -0,0 +1,70 @@
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
height: 100px;
padding: 10px;
}
.title {
font-size: 24px;
}
.templates-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
}
button {
margin-left: 10px;
margin-bottom: 20px;
}
.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;
}
.mat-menu {
min-width: 160px;
}
.cd-icon {
color: #888888;
margin-right: 20px;
transform: scale(1.5);
}
.template-name{
cursor: pointer;
}

View File

@ -0,0 +1,31 @@
<div class="header-container">
<h2 class="title" i18n="@@adminPXETitle">Administrar plantillas PXE</h2>
<div class="pxe-button-row">
<button mat-flat-button color="primary" (click)="addPxeTemplate()">Añadir plantilla PXE</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="lists-container">
<div class="pxeLists-container">
<mat-card class="card pxe-card">
<mat-card-title>Plantillas PXE disponibles</mat-card-title>
<mat-card-content>
<div *ngIf="pxeTemplates.length === 0">No hay plantillas PXE disponibles.</div>
<div *ngFor="let template of pxeTemplates">
<div class="template-container">
<mat-icon class="cd-icon">insert_drive_file</mat-icon>
<h4 class="template-name" (click)="showPxeInfo(template)">{{ template.name }}</h4>
<button mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="editPxeTemplate(template)">Editar</button>
<button mat-menu-item (click)="deletePxeTemplate(template.uuid)">Eliminar</button>
</mat-menu>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
</div>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PxeComponent } from './pxe.component';
describe('PxeComponent', () => {
let component: PxeComponent;
let fixture: ComponentFixture<PxeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [PxeComponent]
})
.compileComponents();
fixture = TestBed.createComponent(PxeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,62 @@
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { CreatePxeTemplateComponent } from './create-pxeTemplate/create-pxe-template/create-pxe-template.component';
import { MatDialog } from '@angular/material/dialog';
import { EditPxeTemplateComponent } from './edit-pxe-template/edit-pxe-template.component';
@Component({
selector: 'app-pxe',
templateUrl: './pxe.component.html',
styleUrl: './pxe.component.css'
})
export class PxeComponent {
pxeTemplates: any[] = []; // Inicializa el array de plantillas
itemsPerPage: number = 30;
currentPage: number = 1;
constructor(public dialog: MatDialog, private http: HttpClient) { }
ngOnInit(): void {
this.loadPxeTemplates();
}
loadPxeTemplates(): void {
const url = `http://127.0.0.1:8080/pxe-templates?page=${this.currentPage}&itemsPerPage=${this.itemsPerPage}`;
this.http.get<any>(url).subscribe({
next: data => {
this.pxeTemplates = data['hydra:member'];
console.log('Plantillas PXE cargadas:', this.pxeTemplates);
},
error: error => {
console.error('Error al cargar plantillas PXE:', error);
}
});
}
addPxeTemplate() {
const dialogRef = this.dialog.open(CreatePxeTemplateComponent, {
});
dialogRef.afterClosed().subscribe(() => {
this.loadPxeTemplates();
});
}
showPxeInfo(template: any) {
// Lógica para mostrar información de una plantilla
}
deletePxeTemplate(uuid: string) {
// Lógica para eliminar una plantilla
}
editPxeTemplate(template: any) {
const dialogRef = this.dialog.open(EditPxeTemplateComponent, {
data: template
});
dialogRef.afterClosed().subscribe(() => {
this.loadPxeTemplates();
});
}
}