refs #2158. Added script to existint command-task

pull/24/head
Manuel Aranda Rosales 2025-06-02 08:09:20 +02:00
parent 4e23723717
commit f94d522420
9 changed files with 209 additions and 28 deletions

View File

@ -6,6 +6,11 @@
padding: 20px;
}
.select-task {
padding: 20px;
margin-bottom: 16px;
}
.full-width {
width: 100%;
}

View File

@ -1,9 +1,40 @@
<h2 mat-dialog-title class="dialog-title">{{ editing ? ('editTask' | translate) : ('createTask' | translate) }}</h2>
<h2 mat-dialog-title class="dialog-title">
{{ editing ? ('editTask' | translate) : ('createTask' | translate) }}
</h2>
<mat-dialog-content class="dialog-content">
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
<form *ngIf="taskForm && !loading" [formGroup]="taskForm" class="task-form">
<!-- Toggle entre crear o añadir -->
<mat-radio-group *ngIf="data?.source === 'assistant'" [(ngModel)]="taskMode" class="task-mode-selection" name="taskMode">
<mat-radio-button value="create">Crear tarea</mat-radio-button>
<mat-radio-button value="add">Introducir en tarea existente</mat-radio-button>
</mat-radio-group>
<!-- Selección de tarea existente -->
<div *ngIf="taskMode === 'add'" class="select-task">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Seleccione una tarea</mat-label>
<mat-select [(ngModel)]="selectedExistingTask" name="existingTask">
<mat-option *ngFor="let task of existingTasks" [value]="task">{{ task.name }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Orden de ejecución</mat-label>
<input
matInput
type="number"
[(ngModel)]="executionOrder"
name="executionOrder"
min="1"
placeholder="Introduce el orden"
>
</mat-form-field>
</div>
<!-- Formulario de nueva tarea -->
<form *ngIf="taskMode === 'create' && taskForm && !loading" [formGroup]="taskForm" class="task-form">
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'nameLabel' | translate }}</mat-label>
<input matInput formControlName="name" placeholder="{{ 'nameLabel' | translate }}">
@ -17,17 +48,17 @@
<mat-form-field appearance="fill" class="full-width">
<mat-label>Ámbito</mat-label>
<mat-select formControlName="scope" class="full-width" (selectionChange)="onScopeChange($event.value)">
<mat-option [value]="'organizational-unit'">Unidad Organizativa</mat-option>
<mat-option [value]="'classrooms-group'">Grupo de aulas</mat-option>
<mat-option [value]="'classroom'">Aulas</mat-option>
<mat-option [value]="'clients-group'">Grupos de clientes</mat-option>
<mat-select formControlName="scope" (selectionChange)="onScopeChange($event.value)">
<mat-option value="organizational-unit">Unidad Organizativa</mat-option>
<mat-option value="classrooms-group">Grupo de aulas</mat-option>
<mat-option value="classroom">Aulas</mat-option>
<mat-option value="clients-group">Grupos de clientes</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'organizationalUnitLabel' | translate }}</mat-label>
<mat-select formControlName="organizationalUnit" >
<mat-select formControlName="organizationalUnit">
<mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
<div class="unit-name">{{ unit.name }}</div>
<div style="font-size: smaller; color: gray;">{{ unit.path }}</div>
@ -35,11 +66,18 @@
</mat-select>
</mat-form-field>
<mat-checkbox *ngIf="!editing" formControlName="scheduleAfterCreate">¿Quieres programar la tarea al finalizar su creación?</mat-checkbox>
<mat-checkbox *ngIf="!editing" formControlName="scheduleAfterCreate">
¿Quieres programar la tarea al finalizar su creación?
</mat-checkbox>
</form>
</mat-dialog-content>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" [disabled]="!taskForm.valid" (click)="saveTask()">{{ 'buttonSave' | translate }}</button>
<button
class="submit-button"
(click)="taskMode === 'create' ? saveTask() : addToExistingTask()"
>
{{ 'buttonSave' | translate }}
</button>
</mat-dialog-actions>

View File

@ -25,6 +25,10 @@ export class CreateTaskComponent implements OnInit {
clients: any[] = [];
allOrganizationalUnits: any[] = [];
loading: boolean = false;
taskMode: 'create' | 'add' = 'create';
existingTasks: any[] = [];
selectedExistingTask: string | null = null;
executionOrder: number | null = null;
constructor(
private fb: FormBuilder,
@ -53,6 +57,7 @@ export class CreateTaskComponent implements OnInit {
this.loadIndividualCommands(),
this.loadOrganizationalUnits(),
this.startUnitsFilter(),
this.loadTasks()
];
Promise.all(observables).then(() => {
@ -90,6 +95,21 @@ export class CreateTaskComponent implements OnInit {
})
}
loadTasks(): Promise<void> {
return new Promise((resolve, reject) => {
this.http.get<any>(`${this.apiUrl}?page=1&itemsPerPage=100`).subscribe(
(data) => {
this.existingTasks = data['hydra:member'];
resolve();
},
(error) => {
this.toastr.error('Error al cargar las tareas existentes');
reject(error);
}
);
});
}
onScopeChange(scope: string): void {
this.filterUnits(scope).subscribe(filteredUnits => {
this.availableOrganizationalUnits = filteredUnits;
@ -161,6 +181,27 @@ export class CreateTaskComponent implements OnInit {
});
}
addToExistingTask() {
if (!this.selectedExistingTask) {
this.toastr.error('Debes seleccionar una tarea existente.');
return;
}
if (this.executionOrder == null || this.executionOrder < 1) {
this.toastr.error('Debes introducir un orden de ejecución válido (mayor que 0).');
return;
}
const data = {
taskId: this.selectedExistingTask,
executionOrder: this.executionOrder
};
this.toastr.success('Tarea actualizada con éxito');
this.dialogRef.close(data);
}
saveTask(): void {
if (this.taskForm.invalid) {
this.toastr.error('Por favor, rellene todos los campos obligatorios');

View File

@ -10,11 +10,15 @@
</h4>
</div>
<div class="button-row">
<button class="action-button" [disabled]="!allSelected || !selectedModelClient || !selectedImage || !selectedMethod || !selectedPartition" (click)="save()">Ejecutar</button>
<button class="action-button"
[disabled]="!isFormValid()"
(click)="save()">Ejecutar</button>
</div>
<div>
<button mat-stroked-button color="accent" [disabled]="!allSelected || !selectedModelClient || !selectedImage || !selectedMethod || !selectedPartition" (click)="openScheduleModal()">
<button mat-stroked-button color="accent"
[disabled]="!isFormValid()"
(click)="openScheduleModal()">
<mat-icon>schedule</mat-icon> Opciones de programación
</button>
</div>
@ -142,18 +146,21 @@
<div *ngIf="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct')" class="input-group">
<mat-form-field appearance="fill" class="input-field">
<mat-label>Puerto</mat-label>
<input matInput [(ngModel)]="mcastPort" name="mcastPort" type="number">
<input matInput [(ngModel)]="mcastPort" name="mcastPort" type="number"
[required]="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct')">
</mat-form-field>
<mat-form-field appearance="fill" class="input-field">
<mat-label>Dirección</mat-label>
<input matInput [(ngModel)]="mcastIp" name="mcastIp">
<input matInput [(ngModel)]="mcastIp" name="mcastIp"
[required]="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct')">
</mat-form-field>
<mat-form-field appearance="fill" class="input-field">
<mat-label i18n="@@mcastModeLabel">Modo Multicast</mat-label>
<mat-select [(ngModel)]="mcastMode" name="mcastMode">
<mat-option *ngFor="let option of multicastModeOptions" [value]="option.value">
<mat-select [(ngModel)]="mcastMode" name="mcastMode"
[required]="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct')">
<mat-option *ngFor="let option of multicastModeOptions" [value]="option.value">
{{ option.name }}
</mat-option>
</mat-select>
@ -161,25 +168,29 @@
<mat-form-field appearance="fill" class="input-field">
<mat-label>Velocidad</mat-label>
<input matInput [(ngModel)]="mcastSpeed" name="mcastSpeed" type="number">
<input matInput [(ngModel)]="mcastSpeed" name="mcastSpeed" type="number"
[required]="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct')">
</mat-form-field>
<mat-form-field appearance="fill" class="input-field">
<mat-label>Máximo Clientes</mat-label>
<input matInput [(ngModel)]="mcastMaxClients" name="mcastMaxClients" type="number">
<input matInput [(ngModel)]="mcastMaxClients" name="mcastMaxClients" type="number"
[required]="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct')">
</mat-form-field>
<mat-form-field appearance="fill" class="input-field">
<mat-label>Tiempo Máximo de Espera</mat-label>
<input matInput [(ngModel)]="mcastMaxTime" name="mcastMaxTime" type="number">
<input matInput [(ngModel)]="mcastMaxTime" name="mcastMaxTime" type="number"
[required]="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct')">
</mat-form-field>
</div>
<div *ngIf="isMethod('p2p')" class="input-group">
<mat-form-field appearance="fill" class="input-field">
<mat-label i18n="@@p2pModeLabel">Modo P2P</mat-label>
<mat-select [(ngModel)]="p2pMode" name="p2pMode">
<mat-option *ngFor="let option of p2pModeOptions" [value]="option.value">
<mat-select [(ngModel)]="p2pMode" name="p2pMode"
[required]="isMethod('p2p')">
<mat-option *ngFor="let option of p2pModeOptions" [value]="option.value">
{{ option.name }}
</mat-option>
</mat-select>
@ -187,7 +198,8 @@
<mat-form-field appearance="fill" class="input-field">
<mat-label>Semilla</mat-label>
<input matInput [(ngModel)]="p2pTime" name="p2pTime" type="number">
<input matInput [(ngModel)]="p2pTime" name="p2pTime" type="number"
[required]="isMethod('p2p')">
</mat-form-field>
</div>
</div>

View File

@ -332,13 +332,34 @@ export class DeployImageComponent implements OnInit{
});
}
isFormValid(): boolean {
if (!this.allSelected || !this.selectedModelClient || !this.selectedImage || !this.selectedMethod || !this.selectedPartition) {
return false;
}
if (this.isMethod('udpcast') || this.isMethod('uftp') || this.isMethod('udpcast-direct')) {
if (!this.mcastPort || !this.mcastIp || !this.mcastMode || !this.mcastSpeed || !this.mcastMaxClients || !this.mcastMaxTime) {
return false;
}
}
if (this.isMethod('p2p')) {
if (!this.p2pMode || !this.p2pTime) {
return false;
}
}
return true;
}
openScheduleModal(): void {
const dialogRef = this.dialog.open(CreateTaskComponent, {
width: '800px',
data: {
scope: this.runScriptContext.type,
organizationalUnit: this.runScriptContext['@id']
organizationalUnit: this.runScriptContext['@id'],
source: 'assistant'
}
});
@ -359,9 +380,9 @@ export class DeployImageComponent implements OnInit{
};
this.http.post(`${this.baseUrl}/command-task-scripts`, {
commandTask: result['@id'],
commandTask: result['taskId'] ? result['taskId']['@id'] : result['@id'],
parameters: payload,
order: 1,
order: result['executionOrder'] || 1,
type: 'deploy-image',
}).subscribe({
next: () => {

View File

@ -267,5 +267,18 @@ button.remove-btn:hover {
font-weight: 500;
}
.instructions-box {
margin-top: 15px;
background-color: #f5f5f5;
border: 1px solid #ccc;
padding: 15px;
border-radius: 6px;
}
.instructions-textarea textarea {
font-family: monospace;
white-space: pre;
}

View File

@ -13,6 +13,12 @@
<button class="action-button" [disabled]="data.status === 'busy' || !selectedModelClient || !allSelected || !selectedDisk || (selectedDisk.totalDiskSize - selectedDisk.used) <= 0" (click)="save()">Ejecutar</button>
</div>
<div class="button-row">
<button class="action-button" (click)="generateInstructions()">
Generar instrucciones
</button>
</div>
<div>
<button mat-stroked-button color="accent" [disabled]="data.status === 'busy' || !selectedModelClient || !allSelected || !selectedDisk || (selectedDisk.totalDiskSize - selectedDisk.used) <= 0" (click)="openScheduleModal()">
<mat-icon>schedule</mat-icon> Opciones de programación
@ -86,6 +92,13 @@
</div>
<div class="partition-assistant" *ngIf="selectedDisk">
<div *ngIf="generatedInstructions" class="instructions-box">
<mat-form-field class="instructions-textarea" appearance="fill" style="width: 100%;">
<textarea matInput rows="10" readonly [value]="generatedInstructions"></textarea>
</mat-form-field>
</div>
<div class="row-button">
<button class="action-button" [disabled]="partitionCode === 'MSDOS'" (click)="addPartition(selectedDisk.diskNumber)">Añadir partición</button>
<mat-chip *ngIf="selectedModelClient.firmwareType">

View File

@ -52,6 +52,7 @@ export class PartitionAssistantComponent implements OnInit{
selectedClients: any[] = [];
selectedModelClient: any = null;
partitionCode: string = '';
generatedInstructions: string = '';
constructor(
private http: HttpClient,
@ -426,7 +427,8 @@ export class PartitionAssistantComponent implements OnInit{
width: '800px',
data: {
scope: this.runScriptContext.type,
organizationalUnit: this.runScriptContext['@id']
organizationalUnit: this.runScriptContext['@id'],
source: 'assistant'
}
});
@ -473,4 +475,39 @@ export class PartitionAssistantComponent implements OnInit{
}
});
}
generateInstructions(): void {
if (!this.selectedDisk || !this.selectedDisk.partitions) {
this.generatedInstructions = 'No hay particiones configuradas para generar instrucciones.';
return;
}
const diskNumber = this.selectedDisk.diskNumber;
const partitionTable = this.partitionCode || 'MSDOS';
let instructions = `ogCreatePartitionTable ${diskNumber} ${partitionTable}\n`;
instructions += `ogEcho log session "[0] $MSG_HELP_ogCreatePartitions"\n`;
instructions += `ogEcho session "[10] $MSG_HELP_ogUnmountAll ${diskNumber}"\n`;
instructions += `ogUnmountAll ${diskNumber} 2>/dev/null\n`;
instructions += `ogUnmountCache\n`;
instructions += `ogEcho session "[30] $MSG_HELP_ogUpdatePartitionTable ${diskNumber}"\n`;
instructions += `ogDeletePartitionTable ${diskNumber}\n`;
instructions += `ogUpdatePartitionTable ${diskNumber}\n`;
this.selectedDisk.partitions.forEach((partition: { removed: any; partitionNumber: any; partitionCode: any; filesystem: any; size: any; format: any; }, index: any) => {
if (partition.removed) return;
const partNumber = partition.partitionNumber;
const partType = partition.partitionCode;
const fs = partition.filesystem;
const size = partition.size;
const shouldFormat = partition.format ? 'yes' : 'no';
instructions += `ogCreatePartition ${diskNumber} ${partNumber} ${partType} ${fs} ${size}MB ${shouldFormat}\n`;
});
instructions += `ogExecAndLog command session ogListPartitions ${diskNumber}\n`;
this.generatedInstructions = instructions;
}
}

View File

@ -203,7 +203,8 @@ export class RunScriptAssistantComponent implements OnInit{
width: '800px',
data: {
scope: this.runScriptContext.type,
organizationalUnit: this.runScriptContext['@id']
organizationalUnit: this.runScriptContext['@id'],
source: 'assistant'
}
});