refs #2756. Fixed bug with stored comands
parent
4e22286e04
commit
2d9c66919a
|
@ -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,
|
||||
|
@ -168,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',
|
||||
|
@ -228,11 +247,12 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
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: () => {
|
||||
|
|
Loading…
Reference in New Issue