Compare commits
No commits in common. "9ba8c1771d1d2a57ba9a9b6f51821320c63f0bde" and "f35ba106bab5a7c561a01f5ffe1ae79b75bdbdac" have entirely different histories.
9ba8c1771d
...
f35ba106ba
|
@ -35,7 +35,7 @@ export class CommandsTaskComponent implements OnInit {
|
|||
{ columnDef: 'name', header: 'Nombre de tarea', cell: (task: any) => task.name },
|
||||
{ columnDef: 'organizationalUnit', header: 'Ámbito', cell: (task: any) => task.organizationalUnit.name },
|
||||
{ columnDef: 'management', header: 'Gestiones', cell: (task: any) => task.schedules },
|
||||
{ columnDef: 'nextExecution', header: 'Próxima ejecución', cell: (task: any) => this.datePipe.transform(task.nextExecution, 'dd/MM/yyyy HH:mm:ss', 'UTC') },
|
||||
{ columnDef: 'nextExecution', header: 'Próxima ejecución', cell: (task: any) => this.datePipe.transform(task.nextExecution, 'dd/MM/yyyy HH:mm:ss') },
|
||||
{ columnDef: 'createdBy', header: 'Creado por', cell: (task: any) => task.createdBy },
|
||||
];
|
||||
|
||||
|
|
|
@ -114,26 +114,20 @@ export class CreateTaskScheduleComponent implements OnInit{
|
|||
return date.toISOString().substring(11, 16);
|
||||
}
|
||||
|
||||
convertDateToLocalISO(date: Date): string {
|
||||
const adjustedDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
return adjustedDate.toISOString();
|
||||
}
|
||||
|
||||
|
||||
onSubmit() {
|
||||
const formData = this.form.value;
|
||||
|
||||
const payload: any = {
|
||||
commandTask: this.data.task['@id'],
|
||||
executionDate: formData.recurrenceType === 'none' ? this.convertDateToLocalISO(formData.executionDate) : null,
|
||||
executionDate: formData.recurrenceType === 'none' ? formData.executionDate : null,
|
||||
executionTime: formData.executionTime,
|
||||
recurrenceType: formData.recurrenceType,
|
||||
recurrenceDetails: {
|
||||
...formData.recurrenceDetails,
|
||||
initDate: formData.recurrenceDetails?.initDate || null,
|
||||
endDate: formData.recurrenceDetails?.endDate || null,
|
||||
daysOfWeek: formData.recurrenceDetails?.daysOfWeek || [],
|
||||
months: formData.recurrenceDetails?.months || []
|
||||
initDate: formData.recurrenceDetails.initDate || null,
|
||||
endDate: formData.recurrenceDetails.endDate || null,
|
||||
daysOfWeek: formData.recurrenceDetails.daysOfWeek || [],
|
||||
months: formData.recurrenceDetails.months || []
|
||||
},
|
||||
enabled: formData.enabled
|
||||
}
|
||||
|
|
|
@ -120,7 +120,6 @@ export class CreateTaskScriptComponent implements OnInit {
|
|||
commandTask: this.data.task['@id'],
|
||||
content: this.commandType === 'existing' ? this.scriptContent : this.newScript,
|
||||
order: this.executionOrder,
|
||||
type: 'run-script',
|
||||
}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Tarea creada con éxito');
|
||||
|
|
|
@ -28,7 +28,7 @@ export class ShowTaskScheduleComponent implements OnInit{
|
|||
columns = [
|
||||
{ columnDef: 'id', header: 'ID', cell: (schedule: any) => schedule.id },
|
||||
{ columnDef: 'recurrenceType', header: 'Recurrencia', cell: (schedule: any) => schedule.recurrenceType },
|
||||
{ columnDef: 'time', header: 'Hora de ejecución', cell: (schedule: any) => this.datePipe.transform(schedule.executionTime, 'HH:mm', 'UTC') },
|
||||
{ columnDef: 'time', header: 'Hora de ejecución', cell: (schedule: any) => this.datePipe.transform(schedule.executionTime, 'HH:mm') },
|
||||
{ columnDef: 'daysOfWeek', header: 'Dias de la semana', cell: (schedule: any) => schedule.recurrenceDetails.daysOfWeek },
|
||||
{ columnDef: 'months', header: 'Meses', cell: (schedule: any) => schedule.recurrenceDetails.months },
|
||||
{ columnDef: 'enabled', header: 'Activo', cell: (schedule: any) => schedule.enabled }
|
||||
|
|
|
@ -27,7 +27,7 @@ import {TranslateModule} from "@ngx-translate/core";
|
|||
import {JoyrideModule} from "ngx-joyride";
|
||||
import {ConfigService} from "@services/config.service";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {InputDialogComponent} from "../../../../task-logs/input-dialog/input-dialog.component";
|
||||
import {InputDialogComponent} from "../../task-logs/input-dialog/input-dialog.component";
|
||||
import {provideHttpClient} from "@angular/common/http";
|
||||
|
||||
describe('ViewParametersModalComponent', () => {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<h1 mat-dialog-title>{{ 'inputDetails' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<pre>{{ data.input | json }}</pre>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button class="ordinary-button" (click)="close()">{{ 'closeButton' | translate }}</button>
|
||||
</div>
|
|
@ -0,0 +1,47 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InputDialogComponent } from './input-dialog.component';
|
||||
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
|
||||
import {FormBuilder} from "@angular/forms";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {provideHttpClient} from "@angular/common/http";
|
||||
import {provideHttpClientTesting} from "@angular/common/http/testing";
|
||||
import {TranslateModule} from "@ngx-translate/core";
|
||||
|
||||
describe('InputDialogComponent', () => {
|
||||
let component: InputDialogComponent;
|
||||
let fixture: ComponentFixture<InputDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [InputDialogComponent],
|
||||
imports: [
|
||||
MatDialogModule,
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
FormBuilder,
|
||||
ToastrService,
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
{
|
||||
provide: MatDialogRef,
|
||||
useValue: {}
|
||||
},
|
||||
{
|
||||
provide: MAT_DIALOG_DATA,
|
||||
useValue: {}
|
||||
}
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(InputDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-input-dialog',
|
||||
templateUrl: './input-dialog.component.html',
|
||||
styleUrl: './input-dialog.component.css'
|
||||
})
|
||||
export class InputDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<InputDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { input: any }
|
||||
) {}
|
||||
|
||||
close(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.calendar-button-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.lists-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.imagesLists-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card.unidad-card {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-string {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.search-boolean {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.search-select {
|
||||
flex: 2;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.chip-failed {
|
||||
background-color: #e87979 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chip-success {
|
||||
background-color: #46c446 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chip-pending {
|
||||
background-color: #bebdbd !important;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.chip-in-progress {
|
||||
background-color: #f5a623 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-progress-flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
button.cancel-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
color: red;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cancel-button mat-icon {
|
||||
color: red;
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCommandsTitle' |
|
||||
translate }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="images-button-row">
|
||||
<button class="action-button" (click)="resetFilters()" joyrideStep="resetFiltersStep"
|
||||
text="{{ 'resetFiltersStepText' | translate }}">
|
||||
{{ 'resetFilters' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-select" joyrideStep="clientSelectStep"
|
||||
text="{{ 'clientSelectStepText' | translate }}">
|
||||
<input type="text" matInput [formControl]="clientControl" [matAutocomplete]="clientAuto"
|
||||
placeholder="{{ 'filterClientPlaceholder' | 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>
|
||||
|
||||
<mat-form-field appearance="fill" class="search-select" joyrideStep="commandSelectStep"
|
||||
text="{{ 'commandSelectStepText' | translate }}">
|
||||
<input type="text" matInput [formControl]="commandControl" [matAutocomplete]="commandAuto"
|
||||
placeholder="{{ 'filterCommandPlaceholder' | translate }}">
|
||||
<mat-autocomplete #commandAuto="matAutocomplete" [displayWith]="displayFnCommand"
|
||||
(optionSelected)="onOptionCommandSelected($event.option.value)">
|
||||
<mat-option *ngFor="let command of filteredCommands | async" [value]="command">
|
||||
{{ command.name }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="search-boolean">
|
||||
<mat-label i18n="@@searchLabel">Estado</mat-label>
|
||||
<mat-select [(ngModel)]="filters['status']" (selectionChange)="loadTraces()" placeholder="Seleccionar opción">
|
||||
<mat-option [value]="undefined">Todos</mat-option>
|
||||
<mat-option [value]="'failed'">Fallido</mat-option>
|
||||
<mat-option [value]="'pending'">Pendiente de ejecutar</mat-option>
|
||||
<mat-option [value]="'in-progress'">Ejecutando</mat-option>
|
||||
<mat-option [value]="'success'">Completado con éxito</mat-option>
|
||||
<mat-option [value]="'cancelled'">Cancelado</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div *ngIf="!loading">
|
||||
<table mat-table [dataSource]="traces" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
text="{{ 'tableStepText' | translate }}">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let trace">
|
||||
|
||||
<ng-container [ngSwitch]="column.columnDef">
|
||||
<ng-container *ngSwitchCase="'status'">
|
||||
<ng-container *ngIf="trace.status === 'in-progress' && trace.progress; else statusChip">
|
||||
<div class="progress-container">
|
||||
<mat-progress-bar class="example-margin" [mode]="mode" [value]="trace.progress" [bufferValue]="bufferValue">
|
||||
</mat-progress-bar>
|
||||
<span>{{trace.progress}}%</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #statusChip>
|
||||
<div class="status-progress-flex">
|
||||
<mat-chip [ngClass]="{
|
||||
'chip-failed': trace.status === 'failed',
|
||||
'chip-success': trace.status === 'success',
|
||||
'chip-pending': trace.status === 'pending',
|
||||
'chip-in-progress': trace.status === 'in-progress',
|
||||
'chip-cancelled': trace.status === 'cancelled'
|
||||
}">
|
||||
{{
|
||||
trace.status === 'failed' ? 'Fallido' :
|
||||
trace.status === 'in-progress' ? 'En ejecución' :
|
||||
trace.status === 'success' ? 'Finalizado con éxito' :
|
||||
trace.status === 'pending' ? 'Pendiente de ejecutar' :
|
||||
trace.status === 'cancelled' ? 'Cancelado' :
|
||||
trace.status
|
||||
}}
|
||||
</mat-chip>
|
||||
<button *ngIf="trace.status === 'in-progress' && trace.command === 'deploy-image'"
|
||||
mat-icon-button
|
||||
(click)="cancelTrace(trace)"
|
||||
class="cancel-button"
|
||||
matTooltip="Cancelar transmisión de imagen">
|
||||
<mat-icon>cancel</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'input'">
|
||||
<button mat-icon-button (click)="openInputModal(trace.input)">
|
||||
<mat-icon>info</mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchDefault>
|
||||
{{ column.cell(trace) }}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="paginator-container" joyrideStep="paginationStep" text="{{ 'paginationStepText' | translate }}">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
|
@ -0,0 +1,309 @@
|
|||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable, forkJoin } from 'rxjs';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
import { MatDialog } from "@angular/material/dialog";
|
||||
import { InputDialogComponent } from "./input-dialog/input-dialog.component";
|
||||
import { ProgressBarMode } from '@angular/material/progress-bar';
|
||||
import { DeleteModalComponent } from "../../../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { ConfigService } from '@services/config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-task-logs',
|
||||
templateUrl: './task-logs.component.html',
|
||||
styleUrls: ['./task-logs.component.css']
|
||||
})
|
||||
export class TaskLogsComponent implements OnInit {
|
||||
baseUrl: string;
|
||||
mercureUrl: string;
|
||||
traces: any[] = [];
|
||||
groupedTraces: any[] = [];
|
||||
commands: any[] = [];
|
||||
clients: any[] = [];
|
||||
length: number = 0;
|
||||
itemsPerPage: number = 20;
|
||||
page: number = 0;
|
||||
loading: boolean = true;
|
||||
pageSizeOptions: number[] = [10, 20, 30, 50];
|
||||
datePipe: DatePipe = new DatePipe('es-ES');
|
||||
mode: ProgressBarMode = 'buffer';
|
||||
progress = 0;
|
||||
bufferValue = 0;
|
||||
|
||||
columns = [
|
||||
{
|
||||
columnDef: 'id',
|
||||
header: 'ID',
|
||||
cell: (trace: any) => `${trace.id}`,
|
||||
},
|
||||
{
|
||||
columnDef: 'command',
|
||||
header: 'Comando',
|
||||
cell: (trace: any) => `${trace.command}`
|
||||
},
|
||||
{
|
||||
columnDef: 'client',
|
||||
header: 'Client',
|
||||
cell: (trace: any) => `${trace.client?.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'status',
|
||||
header: 'Estado',
|
||||
cell: (trace: any) => `${trace.status}`
|
||||
},
|
||||
{
|
||||
columnDef: 'jobId',
|
||||
header: 'Hilo de trabajo',
|
||||
cell: (trace: any) => `${trace.jobId}`
|
||||
},
|
||||
{
|
||||
columnDef: 'input',
|
||||
header: 'Input',
|
||||
cell: (trace: any) => `${trace.input}`
|
||||
},
|
||||
{
|
||||
columnDef: 'output',
|
||||
header: 'Logs',
|
||||
cell: (trace: any) => `${trace.output}`
|
||||
},
|
||||
{
|
||||
columnDef: 'executedAt',
|
||||
header: 'Programación de ejecución',
|
||||
cell: (trace: any) => `${this.datePipe.transform(trace.executedAt, 'dd/MM/yyyy hh:mm:ss')}`,
|
||||
},
|
||||
{
|
||||
columnDef: 'finishedAt',
|
||||
header: 'Finalización',
|
||||
cell: (trace: any) => `${this.datePipe.transform(trace.finishedAt, 'dd/MM/yyyy hh:mm:ss')}`,
|
||||
},
|
||||
];
|
||||
displayedColumns = [...this.columns.map(column => column.columnDef)];
|
||||
|
||||
filters: { [key: string]: string } = {};
|
||||
filteredClients!: Observable<any[]>;
|
||||
clientControl = new FormControl();
|
||||
filteredCommands!: Observable<any[]>;
|
||||
commandControl = new FormControl();
|
||||
|
||||
constructor(private http: HttpClient,
|
||||
private joyrideService: JoyrideService,
|
||||
private dialog: MatDialog,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private configService: ConfigService,
|
||||
private toastService: ToastrService
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
this.mercureUrl = this.configService.mercureUrl;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadTraces();
|
||||
this.loadCommands();
|
||||
//this.loadClients();
|
||||
this.filteredCommands = this.commandControl.valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => (typeof value === 'string' ? value : value?.name)),
|
||||
map(name => (name ? this._filterCommands(name) : this.commands.slice()))
|
||||
);
|
||||
this.filteredClients = this.clientControl.valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => (typeof value === 'string' ? value : value?.name)),
|
||||
map(name => (name ? this._filterClients(name) : this.clients.slice()))
|
||||
);
|
||||
|
||||
const eventSource = new EventSource(`${this.mercureUrl}?topic=`
|
||||
+ encodeURIComponent(`traces`));
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data && data['@id']) {
|
||||
this.updateTracesStatus(data['@id'], data.status, data.progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateTracesStatus(clientUuid: string, newStatus: string, progress: Number): void {
|
||||
const traceIndex = this.traces.findIndex(trace => trace['@id'] === clientUuid);
|
||||
if (traceIndex !== -1) {
|
||||
const updatedTraces = [...this.traces];
|
||||
|
||||
updatedTraces[traceIndex] = {
|
||||
...updatedTraces[traceIndex],
|
||||
status: newStatus,
|
||||
progress: progress
|
||||
};
|
||||
|
||||
this.traces = updatedTraces;
|
||||
this.cdr.detectChanges();
|
||||
|
||||
console.log(`Estado actualizado para la traza ${clientUuid}: ${newStatus}`);
|
||||
} else {
|
||||
console.warn(`Traza con UUID ${clientUuid} no encontrado en la lista.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private _filterClients(name: string): any[] {
|
||||
const filterValue = name.toLowerCase();
|
||||
return this.clients.filter(client => client.name.toLowerCase().includes(filterValue));
|
||||
}
|
||||
|
||||
private _filterCommands(name: string): any[] {
|
||||
const filterValue = name.toLowerCase();
|
||||
return this.commands.filter(command => command.name.toLowerCase().includes(filterValue));
|
||||
}
|
||||
|
||||
displayFnClient(client: any): string {
|
||||
return client && client.name ? client.name : '';
|
||||
}
|
||||
|
||||
displayFnCommand(command: any): string {
|
||||
return command && command.name ? command.name : '';
|
||||
}
|
||||
|
||||
onOptionCommandSelected(selectedCommand: any): void {
|
||||
this.filters['command.id'] = selectedCommand.id;
|
||||
this.loadTraces();
|
||||
}
|
||||
|
||||
onOptionClientSelected(selectedClient: any): void {
|
||||
this.filters['client.id'] = selectedClient.id;
|
||||
this.loadTraces();
|
||||
}
|
||||
|
||||
openInputModal(inputData: any): void {
|
||||
this.dialog.open(InputDialogComponent, {
|
||||
width: '700px',
|
||||
data: { input: inputData }
|
||||
});
|
||||
}
|
||||
|
||||
cancelTrace(trace: any): void {
|
||||
this.dialog.open(DeleteModalComponent, {
|
||||
width: '300px',
|
||||
data: { name: trace.jobId },
|
||||
}).afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
this.http.post(`${this.baseUrl}/traces/server/${trace.uuid}/cancel`, {}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Transmision de imagen cancelada');
|
||||
this.loadTraces();
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
console.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadTraces(): void {
|
||||
this.loading = true;
|
||||
const url = `${this.baseUrl}/traces?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`;
|
||||
const params = { ...this.filters };
|
||||
if (params['status'] === undefined) {
|
||||
delete params['status'];
|
||||
}
|
||||
this.http.get<any>(url, { params }).subscribe(
|
||||
(data) => {
|
||||
this.traces = data['hydra:member'];
|
||||
this.length = data['hydra:totalItems'];
|
||||
this.groupedTraces = this.groupByCommandId(this.traces);
|
||||
this.loading = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching traces', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadCommands() {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.baseUrl}/commands?&page=1&itemsPerPage=10000`).subscribe(
|
||||
response => {
|
||||
this.commands = response['hydra:member'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching commands:', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadClients() {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.baseUrl}/clients?&page=1&itemsPerPage=10000`).subscribe(
|
||||
response => {
|
||||
const clientIds = response['hydra:member'].map((client: any) => client['@id']);
|
||||
const clientDetailsRequests: Observable<any>[] = clientIds.map((id: string) => this.http.get<any>(`${this.baseUrl}${id}`));
|
||||
forkJoin(clientDetailsRequests).subscribe(
|
||||
(clients: any[]) => {
|
||||
this.clients = clients;
|
||||
this.loading = false;
|
||||
},
|
||||
(error: any) => {
|
||||
console.error('Error fetching client details:', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
},
|
||||
(error: any) => {
|
||||
console.error('Error fetching clients:', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
resetFilters() {
|
||||
this.loading = true;
|
||||
this.filters = {};
|
||||
this.loadTraces();
|
||||
}
|
||||
|
||||
groupByCommandId(traces: any[]): any[] {
|
||||
const grouped: { [key: string]: any[] } = {};
|
||||
|
||||
traces.forEach(trace => {
|
||||
const commandId = trace.command.id;
|
||||
if (!grouped[commandId]) {
|
||||
grouped[commandId] = [];
|
||||
}
|
||||
grouped[commandId].push(trace);
|
||||
});
|
||||
|
||||
return Object.keys(grouped).map(key => ({
|
||||
commandId: key,
|
||||
traces: grouped[key]
|
||||
}));
|
||||
}
|
||||
|
||||
onPageChange(event: any): void {
|
||||
this.page = event.pageIndex;
|
||||
this.itemsPerPage = event.pageSize;
|
||||
this.length = event.length;
|
||||
this.loadTraces();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'resetFiltersStep',
|
||||
'clientSelectStep',
|
||||
'commandSelectStep',
|
||||
'tableStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -51,3 +51,21 @@
|
|||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
|
@ -37,7 +37,7 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
<mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
text="{{ 'tableDescription' | translate }}">
|
||||
<ng-container matColumnDef="id">
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'idColumnHeader' | translate }}</mat-header-cell>
|
||||
|
@ -50,12 +50,12 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="ip">
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'ipLabel' | translate }}</mat-header-cell>
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'nameColumnHeader' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let element"> {{ element.ip }} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="mac">
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'macLabel' | translate }}</mat-header-cell>
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'nameColumnHeader' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let element"> {{ element.mac }} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
|
@ -77,4 +77,4 @@
|
|||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
</table>
|
||||
</mat-table>
|
|
@ -4,7 +4,6 @@ import { HttpClient } from '@angular/common/http';
|
|||
import { ToastrService } from 'ngx-toastr';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
import {MatTableDataSource} from "@angular/material/table";
|
||||
|
||||
@Component({
|
||||
selector: 'app-pxe-boot-files',
|
||||
|
@ -16,7 +15,7 @@ export class PxeBootFilesComponent implements OnInit {
|
|||
|
||||
availableOrganizationalUnits: any[] = [];
|
||||
selectedUnitChildren: any[] = [];
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
dataSource: any[] = [];
|
||||
taskForm: FormGroup;
|
||||
units: any[] = [];
|
||||
ogLiveOptions: any[] = [];
|
||||
|
@ -73,14 +72,14 @@ export class PxeBootFilesComponent implements OnInit {
|
|||
loadChildUnits(event: any) {
|
||||
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${event.value.id}`).subscribe(
|
||||
response => {
|
||||
this.dataSource.data = response['hydra:member'];
|
||||
this.dataSource = response['hydra:member'];
|
||||
},
|
||||
error => console.error('Error fetching child units:', error)
|
||||
);
|
||||
}
|
||||
|
||||
applyToAll(): void {
|
||||
this.dataSource.data = this.dataSource.data.map(client => ({
|
||||
this.dataSource = this.dataSource.map(client => ({
|
||||
...client,
|
||||
ogLive: this.globalOgLive || client.ogLive
|
||||
}));
|
||||
|
@ -89,7 +88,7 @@ export class PxeBootFilesComponent implements OnInit {
|
|||
saveOgLiveTemplates(): void {
|
||||
const groupedByTemplate: { [key: string]: string[] } = {};
|
||||
|
||||
this.dataSource.data.forEach(client => {
|
||||
this.dataSource.forEach(client => {
|
||||
if (client.ogLive) {
|
||||
if (!groupedByTemplate[client.ogLive]) {
|
||||
groupedByTemplate[client.ogLive] = [];
|
||||
|
@ -108,10 +107,10 @@ export class PxeBootFilesComponent implements OnInit {
|
|||
|
||||
this.http.post(url, payload).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success(`Clientes guardados correctamente para la plantilla`);
|
||||
this.toastService.success(`Clientes guardados correctamente para la plantilla ${templateId}`);
|
||||
},
|
||||
error: () => {
|
||||
this.toastService.error(`Error al guardar clientes para la plantilla`);
|
||||
this.toastService.error(`Error al guardar clientes para la plantilla ${templateId}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</h2>
|
||||
</div>
|
||||
<div class="images-button-row">
|
||||
<button class="action-button" joyrideStep="viewInfoStep" [text]="'viewInfoStepText' | translate" (click)="openSubnetInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
|
||||
<button class="action-button" (click)="openSubnetInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
|
||||
<button class="action-button" (click)="addImage()" joyrideStep="addImageStep"
|
||||
[text]="'addOgLiveButtonDescription' | translate">
|
||||
{{ 'addOgLiveButton' | translate }}
|
||||
|
@ -57,14 +57,14 @@
|
|||
<th mat-header-cell *matHeaderCellDef>{{ column.header }}</th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef === 'isDefault'">
|
||||
<mat-chip>
|
||||
<ng-container *ngIf="image.isDefault">
|
||||
{{ 'yesOption' | translate }}
|
||||
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||
<ng-container *ngIf="image[column.columnDef]; else cancelIcon">
|
||||
{{ 'checkCircle' | translate }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!image.isDefault">
|
||||
{{ 'noOption' | translate }}
|
||||
</ng-container>
|
||||
</mat-chip>
|
||||
<ng-template #cancelIcon>
|
||||
{{ 'cancelIcon' | translate }}
|
||||
</ng-template>
|
||||
</mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="column.columnDef === 'downloadUrl'">
|
||||
|
@ -99,6 +99,9 @@
|
|||
<button mat-icon-button color="info" (click)="showOgLive($event, image)">
|
||||
<mat-icon>{{ 'viewIcon' | translate }}</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button disabled color="primary" (click)="editImage(image)">
|
||||
<mat-icon>{{ 'editIcon' | translate }}</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="deleteImage(image)">
|
||||
<mat-icon>{{ 'deleteIcon' | translate }}</mat-icon>
|
||||
</button>
|
||||
|
|
|
@ -25,8 +25,8 @@ export class PXEimagesComponent implements OnInit {
|
|||
images: { downloadUrl: string; name: string; uuid: string }[] = [];
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
length: number = 0;
|
||||
itemsPerPage: number = 20;
|
||||
page: number = 0;
|
||||
itemsPerPage: number = 10;
|
||||
page: number = 1;
|
||||
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
|
||||
selectedElements: string[] = [];
|
||||
loading: boolean = false;
|
||||
|
@ -94,15 +94,12 @@ export class PXEimagesComponent implements OnInit {
|
|||
}
|
||||
|
||||
search(): void {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
|
||||
this.dataService.getImages(this.filters).subscribe(
|
||||
data => {
|
||||
this.dataSource.data = data['hydra:member'];
|
||||
this.length = data['hydra:totalItems'];
|
||||
this.loading = false;
|
||||
this.dataSource.data = data;
|
||||
},
|
||||
error => {
|
||||
this.loading = false;
|
||||
console.error('Error fetching og lives', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -163,6 +160,19 @@ export class PXEimagesComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
editImage(image: any): void {
|
||||
const dialogRef = this.dialog.open(CreatePXEImageComponent, {
|
||||
width: '700px',
|
||||
data: image
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.search();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteImage(image: any): void {
|
||||
const dialogRef = this.dialog.open(DeleteModalComponent, {
|
||||
width: '400px',
|
||||
|
@ -193,11 +203,22 @@ export class PXEimagesComponent implements OnInit {
|
|||
const dialogRef = this.dialog.open(InfoImageComponent, { data: { data }, width: '700px' });
|
||||
}
|
||||
|
||||
onPageChange(event: any): void {
|
||||
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.length = event.length;
|
||||
this.search();
|
||||
this.applyFilter();
|
||||
}
|
||||
|
||||
loadAlert(): Observable<any> {
|
||||
|
@ -227,7 +248,6 @@ export class PXEimagesComponent implements OnInit {
|
|||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'viewInfoStep',
|
||||
'addImageStep',
|
||||
'searchNameStep',
|
||||
'searchDefaultImageStep',
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #eceff1;
|
||||
|
@ -9,17 +12,41 @@ pre {
|
|||
color: #333;
|
||||
}
|
||||
|
||||
|
||||
.dialog-content {
|
||||
mat-dialog-actions {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 40px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.pxe-form {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
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;
|
||||
}
|
||||
|
||||
.spacing-container {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.list-item-content {
|
||||
|
@ -29,11 +56,6 @@ pre {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
flex-grow: 1;
|
||||
margin-right: 16px;
|
||||
|
@ -50,11 +72,13 @@ pre {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-container {
|
||||
.actions-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1em;
|
||||
padding: 1.5em;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.action-buttons {
|
|
@ -0,0 +1,43 @@
|
|||
<h2 mat-dialog-title>{{ isEditMode ? ('editTemplateTitle' | translate) : ('addTemplateTitle' | translate) }}</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<div class="spacing-container">
|
||||
<form [formGroup]="templateForm" (ngSubmit)="onSave()">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>{{ 'templateNameLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" [placeholder]="'templateNamePlaceholder' | translate">
|
||||
<mat-error *ngIf="templateForm.get('name')?.hasError('required')">
|
||||
{{ 'templateNameError' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>{{ 'templateContentLabel' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="templateContent" rows="20"
|
||||
[placeholder]="'templateContentPlaceholder' | translate"></textarea>
|
||||
<mat-error *ngIf="templateForm.get('templateContent')?.hasError('required')">
|
||||
{{ 'templateContentError' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<div class="actions-container">
|
||||
<button class="action-button" [matMenuTriggerFor]="templateMenu">
|
||||
{{ 'loadTemplateModelButton' | translate }}
|
||||
</button>
|
||||
<mat-menu #templateMenu="matMenu">
|
||||
<button mat-menu-item (click)="loadTemplateModel('ogLive')">{{ 'ogLiveModel' | translate }}</button>
|
||||
<button mat-menu-item (click)="loadTemplateModel('disco')">{{ 'diskModel' | translate }}</button>
|
||||
</mat-menu>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button class="ordinary-button" (click)="onCancel()">{{ 'cancelButton' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSave()" [disabled]="!templateForm.valid">
|
||||
{{ isEditMode ? ('updateButton' | translate) : ('createButton' | translate) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-actions>
|
|
@ -20,35 +20,35 @@ export class CreatePxeTemplateComponent implements OnInit {
|
|||
|
||||
templateModels = {
|
||||
ogLive: `#!ipxe
|
||||
set timeout 0
|
||||
set timeout-style hidden
|
||||
set ISODIR __OGLIVE__
|
||||
set default 0
|
||||
set kernelargs __INFOHOST__
|
||||
:try_iso
|
||||
kernel http://__SERVERIP__/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs} || goto fallback
|
||||
initrd http://__SERVERIP__/tftpboot/\${ISODIR}/oginitrd.img
|
||||
boot
|
||||
set timeout 0
|
||||
set timeout-style hidden
|
||||
set ISODIR __OGLIVE__
|
||||
set default 0
|
||||
set kernelargs __INFOHOST__
|
||||
:try_iso
|
||||
kernel http://__SERVERIP__/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs} || goto fallback
|
||||
initrd http://__SERVERIP__/tftpboot/\${ISODIR}/oginitrd.img
|
||||
boot
|
||||
|
||||
:fallback
|
||||
set ISODIR ogLive
|
||||
kernel http://__SERVERIP__/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs}
|
||||
initrd http://__SERVERIP__/tftpboot/\${ISODIR}/oginitrd.img
|
||||
boot`,
|
||||
:fallback
|
||||
set ISODIR ogLive
|
||||
kernel http://__SERVERIP__/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs}
|
||||
initrd http://__SERVERIP__/tftpboot/\${ISODIR}/oginitrd.img
|
||||
boot`,
|
||||
|
||||
disco: `#!ipxe
|
||||
disco: `#!ipxe
|
||||
|
||||
iseq \${platform} efi && goto uefi_boot || goto bios_boot
|
||||
iseq \${platform} efi && goto uefi_boot || goto bios_boot
|
||||
|
||||
:bios_boot
|
||||
echo "Running in BIOS mode - Booting first disk"
|
||||
chain http://__SERVERIP__/tftpboot/grub.exe --config-file="title FirstHardDisk;chainloader (hd0)+1;rootnoverify (hd0);boot" || echo "Failed to boot in BIOS mode"
|
||||
exit
|
||||
:bios_boot
|
||||
echo "Running in BIOS mode - Booting first disk"
|
||||
chain http://__SERVERIP__/tftpboot/grub.exe --config-file="title FirstHardDisk;chainloader (hd0)+1;rootnoverify (hd0);boot" || echo "Failed to boot in BIOS mode"
|
||||
exit
|
||||
|
||||
:uefi_boot
|
||||
echo "Running in UEFI mode - Booting first disk"
|
||||
sanboot --no-describe --drive 0 --filename \\EFI\\grub\\Boot\\grubx64.efi || echo "Failed to boot in UEFI mode"
|
||||
exit`
|
||||
:uefi_boot
|
||||
echo "Running in UEFI mode - Booting first disk"
|
||||
sanboot --no-describe --drive 0 --filename \\EFI\\grub\\Boot\\grubx64.efi || echo "Failed to boot in UEFI mode"
|
||||
exit`
|
||||
};
|
||||
|
||||
constructor(
|
||||
|
@ -72,8 +72,7 @@ export class CreatePxeTemplateComponent implements OnInit {
|
|||
|
||||
this.templateForm = this.fb.group({
|
||||
name: [this.data?.name || '', Validators.required],
|
||||
templateContent: [this.data?.templateContent || '', Validators.required],
|
||||
isDefault: [this.data?.isDefault || false]
|
||||
templateContent: [this.data?.templateContent || '', Validators.required]
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -100,8 +99,7 @@ export class CreatePxeTemplateComponent implements OnInit {
|
|||
const formValues = this.templateForm.value;
|
||||
const payload = {
|
||||
name: formValues.name,
|
||||
templateContent: formValues.templateContent,
|
||||
isDefault: formValues.isDefault,
|
||||
templateContent: formValues.templateContent
|
||||
};
|
||||
|
||||
this.http.post<any>(`${this.baseUrl}/pxe-templates`, payload).subscribe({
|
||||
|
@ -119,8 +117,7 @@ export class CreatePxeTemplateComponent implements OnInit {
|
|||
const formValues = this.templateForm.value;
|
||||
const payload = {
|
||||
name: formValues.name,
|
||||
templateContent: formValues.templateContent,
|
||||
isDefault: formValues.isDefault,
|
||||
templateContent: formValues.templateContent
|
||||
};
|
||||
|
||||
this.http.patch<any>(`${this.baseUrl}/pxe-templates/${this.data.uuid}`, payload).subscribe({
|
||||
|
@ -141,6 +138,42 @@ export class CreatePxeTemplateComponent implements OnInit {
|
|||
this.toastService.info(`Plantilla ${type} cargada.`);
|
||||
}
|
||||
|
||||
addClientToTemplate(client: any): void {
|
||||
const postData = {
|
||||
client: client['@id']
|
||||
};
|
||||
|
||||
this.http.post(`${this.baseUrl}/pxe-templates/${this.data.uuid}/sync-client`, postData).subscribe(
|
||||
() => {
|
||||
this.toastService.success('Clientes asignados correctamente');
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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.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);
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<h2 mat-dialog-title>{{ isEditMode ? ('editTemplateTitle' | translate) : ('addTemplateTitle' | translate) }}</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<form [formGroup]="templateForm" (ngSubmit)="onSave()" class="pxe-form">
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>{{ 'templateNameLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" [placeholder]="'templateNamePlaceholder' | translate">
|
||||
<mat-error *ngIf="templateForm.get('name')?.hasError('required')">
|
||||
{{ 'templateNameError' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>{{ 'templateContent' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="templateContent" rows="20"
|
||||
[placeholder]="'templateContentPlaceholder' | translate"></textarea>
|
||||
<mat-error *ngIf="templateForm.get('templateContent')?.hasError('required')">
|
||||
{{ 'templateContentError' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-checkbox formControlName="isDefault">
|
||||
{{ 'isDefaultLabel' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
|
||||
<div mat-dialog-actions class="action-container">
|
||||
<button class="action-button" [matMenuTriggerFor]="templateMenu">
|
||||
{{ 'loadTemplateModelButton' | translate }}
|
||||
</button>
|
||||
<mat-menu #templateMenu="matMenu">
|
||||
<button mat-menu-item (click)="loadTemplateModel('ogLive')">{{ 'ogLiveModel' | translate }}</button>
|
||||
<button mat-menu-item (click)="loadTemplateModel('disco')">{{ 'diskModel' | translate }}</button>
|
||||
</mat-menu>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button class="ordinary-button" (click)="onCancel()">{{ 'cancelButton' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSave()" [disabled]="!templateForm.valid">
|
||||
{{ isEditMode ? ('saveButton' | translate) : ('saveButton' | translate) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +1,5 @@
|
|||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="initTour()">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<div class="header-container-title">
|
||||
|
@ -7,7 +7,7 @@
|
|||
translate }}</h2>
|
||||
</div>
|
||||
<div class="template-button-row">
|
||||
<button class="action-button" joyrideStep="viewInfoStep" [text]="'viewInfoStepText' | translate" (click)="openInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
|
||||
<button class="action-button" (click)="openInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
|
||||
<button class="action-button" (click)="addPxeTemplate()" joyrideStep="addTemplateStep"
|
||||
text="{{ 'addTemplateButtonDescription' | translate }}">{{ 'addTemplateButton' | translate }}</button>
|
||||
</div>
|
||||
|
@ -22,10 +22,10 @@
|
|||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="searchIsDefaultStep"
|
||||
text="{{ 'searchIsDefaultText' | translate }}">
|
||||
<mat-label>{{ 'isDefaultLabel' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="filters['isDefault']" (selectionChange)="search()"
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="searchSyncStep"
|
||||
text="{{ 'searchSyncDescription' | translate }}">
|
||||
<mat-label>{{ 'createdInOgbootLabel' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="filters['synchronized']" (selectionChange)="search()"
|
||||
placeholder="{{ 'selectOptionPlaceholder' | translate }}">
|
||||
<mat-option [value]="''">{{ 'allOption' | translate }}</mat-option>
|
||||
<mat-option [value]="true">{{ 'yesOption' | translate }}</mat-option>
|
||||
|
@ -36,31 +36,16 @@
|
|||
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
text="{{ 'tableDatePxeTemplateText' | translate }}">
|
||||
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">
|
||||
<ng-container *ngIf="column.columnDef === 'synchronized'">
|
||||
<mat-chip>
|
||||
<ng-container *ngIf="image.synchronized">
|
||||
{{ 'yesOption' | translate }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!image.synchronized">
|
||||
{{ 'noOption' | translate }}
|
||||
</ng-container>
|
||||
</mat-chip>
|
||||
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'isDefault'">
|
||||
<mat-chip>
|
||||
<ng-container *ngIf="image.isDefault">
|
||||
{{ 'yesOption' | translate }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!image.isDefault">
|
||||
{{ 'noOption' | translate }}
|
||||
</ng-container>
|
||||
</mat-chip>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef !== 'synchronized' && column.columnDef !== 'isDefault'">
|
||||
<ng-container *ngIf="column.columnDef !== 'synchronized'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
|
@ -75,7 +60,7 @@
|
|||
<button mat-icon-button color="primary" (click)="editPxeTemplate(template)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" [disabled]="template.isDefault" (click)="toggleAction(template, 'delete')">
|
||||
<button mat-icon-button color="warn" (click)="toggleAction(template, 'delete')">
|
||||
<mat-icon>{{ 'deleteIcon' | translate }}</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import { CreatePxeTemplateComponent } from './manage-pxeTemplate/create-pxe-template.component';
|
||||
import { CreatePxeTemplateComponent } from './create-pxeTemplate/create-pxe-template.component';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { PageEvent } from '@angular/material/paginator';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { DataService } from './data.service';
|
||||
|
@ -25,8 +26,8 @@ export class PxeComponent implements OnInit{
|
|||
currentPage: number = 1;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
length: number = 0;
|
||||
itemsPerPage: number = 20;
|
||||
page: number = 0;
|
||||
itemsPerPage: number = 10;
|
||||
page: number = 1;
|
||||
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
|
||||
selectedElements: string[] = [];
|
||||
loading: boolean = false;
|
||||
|
@ -44,22 +45,17 @@ export class PxeComponent implements OnInit{
|
|||
{
|
||||
columnDef: 'name',
|
||||
header: 'Nombre de la plantilla',
|
||||
cell: (user: any) => user.name
|
||||
cell: (user: any) => `${user.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'synchronized',
|
||||
header: 'Sincronizado',
|
||||
cell: (user: any) => user.synchronized
|
||||
},
|
||||
{
|
||||
columnDef: 'isDefault',
|
||||
header: 'Plantilla por defecto',
|
||||
cell: (user: any) => user.isDefault
|
||||
cell: (user: any) => `${user.synchronized}`
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
header: 'Fecha de creación',
|
||||
cell: (user: any) => this.datePipe.transform(user.createdAt, 'dd/MM/yyyy hh:mm:ss')
|
||||
cell: (user: any) => `${this.datePipe.transform(user.createdAt, 'dd/MM/yyyy hh:mm:ss')}`
|
||||
}
|
||||
];
|
||||
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
|
||||
|
@ -84,15 +80,12 @@ export class PxeComponent implements OnInit{
|
|||
}
|
||||
|
||||
search(): void {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
|
||||
this.dataService.getPxeTemplates(this.filters).subscribe(
|
||||
data => {
|
||||
this.dataSource.data = data['hydra:member'];
|
||||
this.length = data['hydra:totalItems'];
|
||||
this.loading = false;
|
||||
this.dataSource.data = data;
|
||||
},
|
||||
error => {
|
||||
this.loading = false;
|
||||
console.error('Error fetching pxe templates', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -107,9 +100,10 @@ export class PxeComponent implements OnInit{
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
editPxeTemplate(template: any) {
|
||||
const dialogRef = this.dialog.open(CreatePxeTemplateComponent, {
|
||||
data: template,
|
||||
data: template, // Pasa los datos del template para edición
|
||||
width: '800px'
|
||||
});
|
||||
|
||||
|
@ -134,6 +128,7 @@ export class PxeComponent implements OnInit{
|
|||
this.search();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al eliminar la subred', error);
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
|
@ -148,7 +143,19 @@ export class PxeComponent implements OnInit{
|
|||
|
||||
showTemplate(event: MouseEvent, data: any): void {
|
||||
event.stopPropagation();
|
||||
const dialogRef = this.dialog.open(ShowTemplateContentComponent, { data: { data }, width: '800px' });
|
||||
const dialogRef = this.dialog.open(ShowTemplateContentComponent, { data: { data }, width: '700px' });
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadAlert(): Observable<any> {
|
||||
|
@ -174,21 +181,20 @@ export class PxeComponent implements OnInit{
|
|||
);
|
||||
}
|
||||
|
||||
onPageChange(event: any): void {
|
||||
onPageChange(event: PageEvent) {
|
||||
this.page = event.pageIndex;
|
||||
this.itemsPerPage = event.pageSize;
|
||||
this.length = event.length;
|
||||
this.search();
|
||||
this.applyFilter();
|
||||
}
|
||||
|
||||
initTour(): void {
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'serverInfoStep',
|
||||
'titleStep',
|
||||
'viewInfoStep',
|
||||
'addTemplateStep',
|
||||
'searchNameStep',
|
||||
'searchIsDefaultStep',
|
||||
'searchSyncStep',
|
||||
'tableStep',
|
||||
'actionsStep',
|
||||
'paginationStep'
|
||||
|
|
|
@ -26,15 +26,8 @@
|
|||
background-color: #f5f5f5;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
border: 1px solid #dcdcdc;
|
||||
}
|
||||
|
||||
.action-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1em;
|
||||
padding: 1.5em;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
<mat-dialog-content class="dialog-content">
|
||||
<div class="info-container">
|
||||
<h3>{{ 'detailsTitle' | translate: { name: data.data.name } }}</h3>
|
||||
<pre class="code-block">{{ data.data.templateContent }}</pre>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<div mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="close()">Cancelar</button>
|
||||
<div class="info-container">
|
||||
<h3>{{ 'detailsTitle' | translate: { name: data.data.name } }}</h3>
|
||||
<pre class="code-block">{{ data.data.templateContent }}</pre>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import { MAT_DIALOG_DATA } from "@angular/material/dialog";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { ConfigService } from "@services/config.service";
|
||||
|
||||
|
@ -15,13 +15,8 @@ export class ShowTemplateContentComponent {
|
|||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private http: HttpClient,
|
||||
private configService: ConfigService,
|
||||
public dialogRef: MatDialogRef<ShowTemplateContentComponent>
|
||||
private configService: ConfigService
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue