develop #41
|
@ -1,4 +1,13 @@
|
|||
# Changelog
|
||||
## [0.22.2] - 2025-09-05
|
||||
### Improved
|
||||
- Se ha mejorado la UX en el asistente de ejecurcion de scripts.
|
||||
|
||||
### Fixed
|
||||
- Se ha corregido en el particionador, el que el tamaño de las particiones EFI no esten fijas a 512 cuando ya haya datos almacenados.
|
||||
- Se ha corregido un bug que hacia que no pasara los clientes seleccionados en el asistente de script, y en las tareas programadas.
|
||||
|
||||
---
|
||||
## [0.22.1] - 2025-09-05
|
||||
### Improved
|
||||
- Se ha mejorado la experiencia de usuario con el despleable de "tipos de particion" en el asistente de particonado.
|
||||
|
|
|
@ -99,8 +99,21 @@ pipeline {
|
|||
}
|
||||
}
|
||||
post {
|
||||
success {
|
||||
script {
|
||||
// Solo lanzar cuando el build sea exitoso y en la rama main
|
||||
if (env.BRANCH_NAME == 'main') {
|
||||
build job: 'Aptly publish nightly repository',
|
||||
wait: false,
|
||||
parameters: [
|
||||
string(name: 'TRIGGERED_BY', value: "${env.JOB_NAME}-${env.BUILD_NUMBER}")
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
always {
|
||||
notifyBuildStatus('narenas@qindel.com')
|
||||
notifyBuildStatus('opengnsys@qindel.com')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -445,23 +445,50 @@ export class DeployImageComponent implements OnInit{
|
|||
|
||||
dialogRef.afterClosed().subscribe((result: { [x: string]: any; }) => {
|
||||
if (result !== undefined) {
|
||||
const payload = {
|
||||
const basePayload: any = {
|
||||
method: this.selectedMethod,
|
||||
diskNumber: this.selectedPartition.diskNumber,
|
||||
partitionNumber: this.selectedPartition.partitionNumber,
|
||||
p2pMode: this.selectedMethod === 'p2p' ? this.p2pMode : null,
|
||||
p2pTime: this.selectedMethod === 'p2p' && this.p2pMode === 'seeder' ? this.p2pTime : null,
|
||||
mcastIp: this.selectedMethod === 'udpcast' ? this.mcastIp : null,
|
||||
mcastPort: this.selectedMethod === 'udpcast' ? this.mcastPort : null,
|
||||
mcastMode: this.selectedMethod === 'udpcast' ? this.mcastMode : null,
|
||||
mcastSpeed: this.selectedMethod === 'udpcast' ? this.mcastSpeed : null,
|
||||
maxTime: this.selectedMethod === 'udpcast' ? this.mcastMaxTime : null,
|
||||
maxClients: this.selectedMethod === 'udpcast' ? this.mcastMaxClients : null,
|
||||
imageName: this.selectedImage.name,
|
||||
imageUuid: this.selectedImage.uuid,
|
||||
type: this.imageType
|
||||
};
|
||||
|
||||
if (this.selectedMethod === 'p2p' && this.p2pMode) {
|
||||
basePayload['p2pMode'] = this.p2pMode;
|
||||
}
|
||||
|
||||
if (this.selectedMethod === 'p2p' && this.p2pMode === 'seeder' && this.p2pTime) {
|
||||
basePayload['p2pTime'] = this.p2pTime;
|
||||
}
|
||||
|
||||
if (this.selectedMethod === 'udpcast' || this.selectedMethod === 'udpcast-direct' && this.mcastIp) {
|
||||
basePayload['mcastIp'] = this.mcastIp;
|
||||
}
|
||||
|
||||
if (this.selectedMethod === 'udpcast' || this.selectedMethod === 'udpcast-direct' && this.mcastPort) {
|
||||
basePayload['mcastPort'] = this.mcastPort;
|
||||
}
|
||||
|
||||
if (this.selectedMethod === 'udpcast' || this.selectedMethod === 'udpcast-direct' && this.mcastMode) {
|
||||
basePayload['mcastMode'] = this.mcastMode;
|
||||
}
|
||||
|
||||
if (this.selectedMethod === 'udpcast' || this.selectedMethod === 'udpcast-direct' && this.mcastSpeed) {
|
||||
basePayload['mcastSpeed'] = this.mcastSpeed;
|
||||
}
|
||||
|
||||
if (this.selectedMethod === 'udpcast' || this.selectedMethod === 'udpcast-direct' && this.mcastMaxTime) {
|
||||
basePayload['maxTime'] = this.mcastMaxTime;
|
||||
}
|
||||
|
||||
if (this.selectedMethod === 'udpcast' || this.selectedMethod === 'udpcast-direct' && this.mcastMaxClients) {
|
||||
basePayload['maxClients'] = this.mcastMaxClients;
|
||||
}
|
||||
|
||||
this.http.post(`${this.baseUrl}/command-task-scripts`, {
|
||||
commandTask: result['taskId'] ? result['taskId']['@id'] : result['@id'],
|
||||
parameters: payload,
|
||||
parameters: basePayload,
|
||||
order: result['executionOrder'] || 1,
|
||||
type: 'deploy-image',
|
||||
}).subscribe({
|
||||
|
|
|
@ -246,14 +246,18 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
if (partition.partitionNumber === 0) {
|
||||
disk!.totalDiskSize = this.convertBytesToGB(partition.size);
|
||||
} else {
|
||||
const isFirstPartitionGPT = partition.partitionNumber === 1 && this.partitionCode === 'GPT';
|
||||
const hasValidExistingData = partition.size > 0 && partition.partitionCode && partition.filesystem;
|
||||
const shouldUseEFIDefaults = isFirstPartitionGPT && !hasValidExistingData;
|
||||
|
||||
disk!.partitions.push({
|
||||
uuid: partition.uuid,
|
||||
partitionNumber: partition.partitionNumber,
|
||||
size: this.convertBytesToGB(partition.partitionNumber === 1 && this.partitionCode === 'GPT' ? 512 : partition.size),
|
||||
size: this.convertBytesToGB(shouldUseEFIDefaults ? 512 : partition.size),
|
||||
memoryUsage: partition.memoryUsage,
|
||||
partitionCode: partition.partitionNumber === 1 && this.partitionCode === 'GPT' ? 'EFI' : this.validatePartitionCode(partition.partitionCode),
|
||||
filesystem: partition.partitionNumber === 1 && this.partitionCode === 'GPT' ? 'FAT32' : partition.filesystem,
|
||||
sizeBytes: partition.partitionNumber === 1 && this.partitionCode === 'GPT' ? 512 : partition.size,
|
||||
partitionCode: shouldUseEFIDefaults ? 'EFI' : this.validatePartitionCode(partition.partitionCode),
|
||||
filesystem: shouldUseEFIDefaults ? 'FAT32' : partition.filesystem,
|
||||
sizeBytes: shouldUseEFIDefaults ? 512 : partition.size,
|
||||
format: false,
|
||||
color: this.getColorForPartition(partition.partitionNumber),
|
||||
percentage: 0,
|
||||
|
|
|
@ -533,4 +533,274 @@ mat-spinner {
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.script-input-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.script-input-header h4 {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.script-stats {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
background: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.stat-item.valid {
|
||||
color: #28a745;
|
||||
border-color: #28a745;
|
||||
background: #f8fff9;
|
||||
}
|
||||
|
||||
.stat-item.invalid {
|
||||
color: #dc3545;
|
||||
border-color: #dc3545;
|
||||
background: #fff8f8;
|
||||
}
|
||||
|
||||
.stat-item mat-icon {
|
||||
font-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.script-textarea {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.detected-params {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.detected-params h4 {
|
||||
margin: 0 0 12px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #856404;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.params-grid .mat-mdc-chip-listbox {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.param-chip {
|
||||
background: #fff !important;
|
||||
border: 1px solid #ffeaa7 !important;
|
||||
color: #856404 !important;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.action-buttons button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.script-selector-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.script-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.script-name {
|
||||
font-weight: 500;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.script-description {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Tarjetas de vista previa */
|
||||
.script-preview-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.script-card, .params-card {
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #e3f2fd !important;
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
.script-card .mat-mdc-card-header,
|
||||
.params-card .mat-mdc-card-header {
|
||||
background: #f8f9fa;
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
|
||||
.script-card .mat-mdc-card-title,
|
||||
.params-card .mat-mdc-card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.script-card .mat-mdc-card-subtitle,
|
||||
.params-card .mat-mdc-card-subtitle {
|
||||
color: #6c757d;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.script-content-wrapper {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.script-preview {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e9ecef;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
min-height: 60px;
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.script-params-section {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.params-count {
|
||||
background: #667eea !important;
|
||||
color: white !important;
|
||||
font-size: 11px;
|
||||
height: 20px;
|
||||
min-height: 20px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.params-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.param-field {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Estados vacíos */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
background: #f8f9fa;
|
||||
border: 2px dashed #e9ecef;
|
||||
border-radius: 12px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.empty-state-content {
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.empty-state-content mat-icon {
|
||||
font-size: 48px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: #dee2e6;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state-content h3 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #495057;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-state-content p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
/* Mejoras en chips de acción */
|
||||
::ng-deep .action-chip mat-icon {
|
||||
font-size: 18px !important;
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.script-input-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.script-stats {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.params-form {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
|
@ -86,50 +86,168 @@
|
|||
<div class="action-chips-container">
|
||||
<mat-chip-listbox [(ngModel)]="commandType" required class="action-chip-listbox">
|
||||
<mat-chip-option value="new" class="action-chip create-chip firmware-chip" (click)="commandType = 'new'">
|
||||
<mat-icon>add_circle</mat-icon>
|
||||
<span>Nuevo Script</span>
|
||||
</mat-chip-option>
|
||||
<mat-chip-option value="existing" class="action-chip update-chip firmware-chip" (click)="commandType = 'existing'">
|
||||
<mat-icon>folder</mat-icon>
|
||||
<span>Script Guardado</span>
|
||||
</mat-chip-option>
|
||||
</mat-chip-listbox>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Nuevo Script -->
|
||||
<div *ngIf="commandType === 'new'" class="new-command-container">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Ingrese el script</mat-label>
|
||||
<textarea matInput [(ngModel)]="newScript" rows="6" placeholder="Escriba su script aquí"></textarea>
|
||||
</mat-form-field>
|
||||
<button mat-flat-button color="primary" (click)="saveNewScript()">Guardar Script</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="commandType === 'existing'">
|
||||
<mat-form-field appearance="fill" class="custom-width">
|
||||
<mat-label>Seleccione script a ejecutar</mat-label>
|
||||
<mat-select [(ngModel)]="selectedScript" (selectionChange)="onScriptChange()">
|
||||
<mat-option *ngFor="let script of scripts" [value]="script">{{ script.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectedScript && commandType === 'existing'" class="script-container">
|
||||
<div class="script-content">
|
||||
<h3>Script:</h3>
|
||||
<div class="script-preview" [innerHTML]="scriptContent"></div>
|
||||
<div class="script-input-header">
|
||||
<h4>
|
||||
<mat-icon>edit_note</mat-icon>
|
||||
Editor de Script
|
||||
</h4>
|
||||
<div class="script-stats" *ngIf="newScript.trim()">
|
||||
<span class="stat-item">
|
||||
<mat-icon>format_list_numbered</mat-icon>
|
||||
{{ getLineCount(newScript) }} líneas
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<mat-icon>text_fields</mat-icon>
|
||||
{{ newScript.length }} caracteres
|
||||
</span>
|
||||
<span class="stat-item" [ngClass]="{'valid': isScriptValid(newScript), 'invalid': !isScriptValid(newScript)}">
|
||||
<mat-icon>{{ isScriptValid(newScript) ? 'check_circle' : 'error' }}</mat-icon>
|
||||
{{ isScriptValid(newScript) ? 'Válido' : 'Requiere contenido' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="script-params" *ngIf="parameterNames.length > 0 && selectedScript.parameters">
|
||||
<h3>Ingrese los parámetros:</h3>
|
||||
<div *ngFor="let paramName of parameterNames">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ paramName }}</mat-label>
|
||||
<input matInput [ngModel]="parameters[paramName]" (ngModelChange)="onParamChange(paramName, $event)" placeholder="Valor para {{ paramName }}">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>Ingrese el script</mat-label>
|
||||
<textarea
|
||||
matInput
|
||||
[(ngModel)]="newScript"
|
||||
(ngModelChange)="onNewScriptChange()"
|
||||
rows="8"
|
||||
placeholder="#!/bin/bash # Escriba su script aquí echo 'Iniciando script...' # Sus comandos aquí"
|
||||
class="script-textarea"></textarea>
|
||||
<mat-hint>Escriba comandos de shell/bash. Use @parametro para variables dinámicas.</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Vista previa de parámetros detectados en nuevo script -->
|
||||
<div *ngIf="newScriptParameters.length > 0" class="detected-params">
|
||||
<h4>
|
||||
<mat-icon>tune</mat-icon>
|
||||
Parámetros detectados
|
||||
</h4>
|
||||
<div class="params-grid">
|
||||
<mat-chip-listbox>
|
||||
<mat-chip-option *ngFor="let param of newScriptParameters" class="param-chip" selected>
|
||||
<mat-icon>label</mat-icon>
|
||||
@{{ param }}
|
||||
</mat-chip-option>
|
||||
</mat-chip-listbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
[disabled]="!isScriptValid(newScript)"
|
||||
(click)="saveNewScript()">
|
||||
<mat-icon>save</mat-icon>
|
||||
Guardar Script
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Existente -->
|
||||
<div *ngIf="commandType === 'existing'">
|
||||
<div class="script-selector-container">
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>Seleccione script a ejecutar</mat-label>
|
||||
<mat-select [(ngModel)]="selectedScript" (selectionChange)="onScriptChange()">
|
||||
<mat-option *ngFor="let script of scripts" [value]="script">
|
||||
<div class="script-option">
|
||||
<span class="script-name">{{ script.name }}</span>
|
||||
<span class="script-description" *ngIf="script.description">{{ script.description }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint *ngIf="scripts.length === 0">No hay scripts guardados disponibles</mat-hint>
|
||||
<mat-hint *ngIf="scripts.length > 0">{{ scripts.length }} script(s) disponible(s)</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Vista previa del script seleccionado -->
|
||||
<div *ngIf="selectedScript && commandType === 'existing'" class="script-preview-container">
|
||||
<mat-card class="script-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title>
|
||||
<mat-icon>preview</mat-icon>
|
||||
Vista previa del script
|
||||
</mat-card-title>
|
||||
<mat-card-subtitle>{{ selectedScript.name }}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<div class="script-content-wrapper">
|
||||
<div class="script-preview" [innerHTML]="scriptContent"></div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<!-- Parámetros del script -->
|
||||
<div *ngIf="parameterNames.length > 0 && selectedScript.parameters" class="script-params-section">
|
||||
<mat-card class="params-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title>
|
||||
<mat-icon>settings</mat-icon>
|
||||
Parámetros del script
|
||||
<mat-chip class="params-count">{{ parameterNames.length }}</mat-chip>
|
||||
</mat-card-title>
|
||||
<mat-card-subtitle>Configure los valores para los parámetros requeridos</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<div class="params-form">
|
||||
<div *ngFor="let paramName of parameterNames; trackBy: trackByParam" class="param-field">
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>{{ paramName }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[ngModel]="parameters[paramName]"
|
||||
(ngModelChange)="onParamChange(paramName, $event)"
|
||||
[placeholder]="'Valor para ' + paramName"
|
||||
[required]="true">
|
||||
<mat-icon matPrefix>label</mat-icon>
|
||||
<mat-hint>Parámetro: @{{ paramName }}</mat-hint>
|
||||
<mat-error *ngIf="!parameters[paramName]">Este parámetro es requerido</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estado vacío para script existente -->
|
||||
<div *ngIf="commandType === 'existing' && !selectedScript" class="empty-state">
|
||||
<div class="empty-state-content">
|
||||
<mat-icon>folder_open</mat-icon>
|
||||
<h3>Selecciona un script</h3>
|
||||
<p>Elige un script guardado de la lista para ver su contenido y configurar parámetros.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estado vacío para nuevo script -->
|
||||
<div *ngIf="commandType === 'new' && !newScript.trim()" class="empty-state">
|
||||
<div class="empty-state-content">
|
||||
<mat-icon>edit_note</mat-icon>
|
||||
<h3>Escribe tu script</h3>
|
||||
<p>Crea un nuevo script escribiendo comandos de shell/bash en el editor de texto.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
selection = new SelectionModel(true, []);
|
||||
parameterNames: string[] = Object.keys(this.parameters);
|
||||
runScriptContext: any = null;
|
||||
newScriptParameters: string[] = [];
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
|
@ -50,15 +51,12 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
this.clientData = JSON.parse(params['clientData']);
|
||||
}
|
||||
if (params['runScriptContext']) {
|
||||
this.runScriptContext = params['runScriptContext'];
|
||||
this.runScriptContext = JSON.parse(params['runScriptContext']);
|
||||
}
|
||||
});
|
||||
this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null;
|
||||
this.clientData.forEach((client: { selected: boolean; status: string}) => { client.selected = true; });
|
||||
|
||||
this.selectedClients = this.clientData.filter(
|
||||
(client: { selected: boolean; status: string }) => client.selected
|
||||
);
|
||||
|
||||
this.initializeClientSelection();
|
||||
|
||||
this.allSelected = this.clientData.length > 0 && this.clientData.every((client: { selected: boolean }) => client.selected);
|
||||
|
||||
|
@ -66,9 +64,6 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.queryParams.subscribe(params => {
|
||||
this.runScriptContext = params['runScriptContext'] ? JSON.parse(params['runScriptContext']) : null;
|
||||
});
|
||||
}
|
||||
|
||||
get runScriptTitle(): string {
|
||||
|
@ -174,6 +169,24 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
this.scriptContent = updatedScript;
|
||||
}
|
||||
|
||||
getLineCount(text: string): number {
|
||||
if (!text) return 0;
|
||||
return text.split('\n').length;
|
||||
}
|
||||
|
||||
isScriptValid(script: string): boolean {
|
||||
return Boolean(script && script.trim().length > 0);
|
||||
}
|
||||
|
||||
onNewScriptChange(): void {
|
||||
const matches = this.newScript.match(/@(\w+)/g) || [];
|
||||
this.newScriptParameters = Array.from(new Set(matches.map(m => m.slice(1))));
|
||||
}
|
||||
|
||||
trackByParam(index: number, paramName: string): string {
|
||||
return paramName;
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const dialogRef = this.dialog.open(QueueConfirmationModalComponent, {
|
||||
width: '400px',
|
||||
|
@ -205,13 +218,21 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
}
|
||||
|
||||
openScheduleModal(): void {
|
||||
let scope = this.runScriptContext.type;
|
||||
let scope = this.runScriptContext?.type || 'clients';
|
||||
let selectedClients = null;
|
||||
|
||||
|
||||
if ((!this.runScriptContext || this.runScriptContext.type === 'client' || this.selectedClients.length === 1) && this.selectedClients && this.selectedClients.length > 0) {
|
||||
if (this.selectedClients.length === 0 && this.clientData.length > 0) {
|
||||
this.updateSelectedClients();
|
||||
}
|
||||
|
||||
const isOrganizationalContext = this.runScriptContext?.type &&
|
||||
['organizational-unit', 'classroom', 'classrooms-group', 'clients-group'].includes(this.runScriptContext.type);
|
||||
|
||||
if (!isOrganizationalContext && this.selectedClients && this.selectedClients.length > 0) {
|
||||
scope = 'clients';
|
||||
selectedClients = this.selectedClients;
|
||||
} else if (isOrganizationalContext && this.selectedClients && this.selectedClients.length > 0) {
|
||||
selectedClients = null;
|
||||
}
|
||||
|
||||
const dialogRef = this.dialog.open(CreateTaskComponent, {
|
||||
|
@ -229,9 +250,9 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
console.log(result);
|
||||
if (result) {
|
||||
this.http.post(`${this.baseUrl}/command-task-scripts`, {
|
||||
commandTask: result.taskId['@id'],
|
||||
commandTask: result['@id'],
|
||||
content: this.commandType === 'existing' ? this.scriptContent : this.newScript,
|
||||
order: result.executionOrder,
|
||||
order: result['executionOrder'] || 1,
|
||||
type: 'run-script',
|
||||
}).subscribe({
|
||||
next: () => {
|
||||
|
@ -244,4 +265,35 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private initializeClientSelection(): void {
|
||||
const context = this.runScriptContext;
|
||||
|
||||
this.clientData.forEach((client: { selected: boolean; status: string}) => {
|
||||
client.selected = true;
|
||||
});
|
||||
|
||||
if (context && typeof context === 'object' && context.type &&
|
||||
['classroom', 'classrooms-group', 'clients-group'].includes(context.type)) {
|
||||
this.clientData.forEach((client: { selected: boolean; status: string}) => {
|
||||
client.selected = false;
|
||||
});
|
||||
}
|
||||
|
||||
else if (context && typeof context === 'object' && context.type === 'client') {
|
||||
this.clientData.forEach((client: { selected: boolean; status: string; name: string; uuid: string}) => {
|
||||
client.selected = client.name === context.name || client.uuid === context.uuid || client.uuid === context['@id'];
|
||||
});
|
||||
}
|
||||
|
||||
else if (context && Array.isArray(context)) {
|
||||
this.clientData.forEach((client: { selected: boolean; status: string; name: string; uuid: string}) => {
|
||||
client.selected = context.some(ctx =>
|
||||
ctx.name === client.name || ctx.uuid === client.uuid || ctx['@id'] === client.uuid
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this.updateSelectedClients();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue