From 3270f75f15dafe640b73ef48382e7122e32764fc Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 5 Aug 2025 10:19:46 +0200 Subject: [PATCH] refs #2596. Create task options --- .../commands-task/commands-task.component.ts | 4 +- .../create-task-schedule.component.html | 3 - .../create-task/create-task.component.css | 153 ++++++++++++++++++ .../create-task/create-task.component.html | 43 ++++- .../create-task/create-task.component.ts | 70 +++++++- .../show-task-schedule.component.ts | 2 +- .../execute-command.component.ts | 8 +- 7 files changed, 262 insertions(+), 21 deletions(-) diff --git a/ogWebconsole/src/app/components/commands/commands-task/commands-task.component.ts b/ogWebconsole/src/app/components/commands/commands-task/commands-task.component.ts index c8e27e5..030387c 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/commands-task.component.ts +++ b/ogWebconsole/src/app/components/commands/commands-task/commands-task.component.ts @@ -33,9 +33,9 @@ export class CommandsTaskComponent implements OnInit { columns = [ { columnDef: 'id', header: 'ID', cell: (task: any) => task.id }, { columnDef: 'name', header: 'Nombre de tarea', cell: (task: any) => task.name }, - { columnDef: 'organizationalUnit', header: 'Ámbito', cell: (task: any) => task.organizationalUnit.name }, + { columnDef: 'organizationalUnit', header: 'Ámbito', cell: (task: any) => task.scope }, { 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 }, ]; diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.html b/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.html index 45e2e74..0cb7fc9 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.html +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.html @@ -22,7 +22,6 @@ -
@@ -37,7 +36,6 @@
-
@@ -52,7 +50,6 @@
-
Desde diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.css b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.css index 11a5938..33634a1 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.css +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.css @@ -19,6 +19,14 @@ mat-form-field { margin-bottom: 16px; } +mat-form-field.mat-form-field-disabled { + opacity: 0.7; +} + +mat-form-field.mat-form-field-disabled .mat-form-field-label { + color: #666; +} + .loading-spinner { display: block; margin: 0 auto; @@ -63,3 +71,148 @@ mat-form-field { gap: 1em; padding: 1.5em; } + +/* Estilos para la selección de clientes */ +.clients-selection { + margin-bottom: 16px; +} + +.clients-selection h4 { + margin-bottom: 16px; + color: #333; + font-weight: 500; +} + +.pre-selected-info { + display: flex; + align-items: center; + gap: 8px; + padding: 12px; + background-color: #e3f2fd; + border: 1px solid #2196f3; + border-radius: 4px; + margin-bottom: 16px; + color: #1976d2; + font-size: 14px; +} + +.pre-selected-info mat-icon { + color: #2196f3; + font-size: 20px; +} + +.loading-clients { + display: flex; + align-items: center; + gap: 8px; + padding: 16px; + color: #666; +} + +.clients-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + padding: 8px 0; + border-bottom: 1px solid #eee; +} + +.selected-count { + font-weight: 500; + color: #1976d2; +} + +.clients-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 12px; + max-height: 300px; + overflow-y: auto; + padding: 8px 0; +} + +.client-card { + transition: all 0.2s ease; + border: 2px solid transparent; + position: relative; +} + +.client-card.pre-selected { + border-color: #4caf50; + background-color: #e8f5e8; +} + +.client-card.pre-selected:hover { + border-color: #45a049; + background-color: #d4edda; +} + +.client-card mat-card-content { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px; +} + +.client-info { + flex: 1; +} + +.client-name { + font-weight: 500; + margin-bottom: 4px; + color: #333; +} + +.client-details { + display: flex; + gap: 8px; + font-size: 12px; + color: #666; +} + +.client-ip { + font-family: monospace; +} + +.client-status { + padding: 2px 6px; + border-radius: 4px; + font-size: 10px; + text-transform: uppercase; + font-weight: 500; +} + +.status-og-live { + background-color: #4caf50; + color: white; +} + +.status-offline { + background-color: #f44336; + color: white; +} + +.status-unknown { + background-color: #ff9800; + color: white; +} + +.selected-icon { + color: #1976d2; + font-size: 20px; +} + +/* Responsive design */ +@media (max-width: 768px) { + .clients-grid { + grid-template-columns: 1fr; + } + + .clients-header { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } +} diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html index 79de16a..2ec144a 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html @@ -5,13 +5,11 @@ - Crear tarea Introducir en tarea existente -
Seleccione una tarea @@ -33,7 +31,6 @@
-
{{ 'nameLabel' | translate }} @@ -48,15 +45,17 @@ Ámbito - + Unidad Organizativa Grupo de aulas Aulas Grupos de clientes + Clientes - + {{ 'organizationalUnitLabel' | translate }} @@ -66,6 +65,40 @@ +
+

Clientes seleccionados

+ +
+ info + Los clientes han sido pre-seleccionados desde el componente de despliegue de imágenes. +
+ +
+
+ + +
+
{{ client.name || client.hostname }}
+
+ {{ client.ip }} + + {{ client.status }} + +
+
+ + check_circle + +
+
+
+
+
+ ¿Quieres programar la tarea al finalizar su creación? diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts index 9ff9ba6..e550e5d 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts @@ -29,6 +29,7 @@ export class CreateTaskComponent implements OnInit { existingTasks: any[] = []; selectedExistingTask: string | null = null; executionOrder: number | null = null; + selectedClients: any[] = []; constructor( private fb: FormBuilder, @@ -41,13 +42,32 @@ export class CreateTaskComponent implements OnInit { ) { this.baseUrl = this.configService.apiUrl; this.apiUrl = `${this.baseUrl}/command-tasks`; + + let initialScope = ''; + if (this.data?.selectedClients && this.data.selectedClients.length > 0) { + initialScope = 'clients'; + } else if (this.data?.scope) { + initialScope = this.data.scope; + } else if (this.data?.runScriptContext) { + initialScope = this.data.runScriptContext.type || ''; + } + this.taskForm = this.fb.group({ - scope: [ this.data?.scope ? this.data.scope : '', Validators.required], + scope: [initialScope, Validators.required], name: ['', Validators.required], - organizationalUnit: [ this.data?.organizationalUnit ? this.data.organizationalUnit : null, Validators.required], + organizationalUnit: [ this.data?.organizationalUnit ? this.data.organizationalUnit : null], notes: [''], scheduleAfterCreate: [false] }); + + if (this.data?.selectedClients && Array.isArray(this.data.selectedClients)) { + this.selectedClients = [...this.data.selectedClients]; + this.clients = [...this.data.selectedClients]; + } + + setTimeout(() => { + this.onScopeChange(initialScope); + }, 0); } ngOnInit(): void { @@ -111,10 +131,31 @@ export class CreateTaskComponent implements OnInit { } onScopeChange(scope: string): void { - this.filterUnits(scope).subscribe(filteredUnits => { - this.availableOrganizationalUnits = filteredUnits; + if (scope === 'clients') { + if (this.data?.selectedClients && this.data.selectedClients.length > 0) { + this.selectedClients = [...this.data.selectedClients]; + this.clients = [...this.data.selectedClients]; + } else { + this.toastr.error('No hay clientes pre-seleccionados para este ámbito'); + this.taskForm.get('scope')?.setValue(''); + return; + } this.taskForm.get('organizationalUnit')?.setValue(''); - }); + this.taskForm.get('organizationalUnit')?.clearValidators(); + } else { + this.filterUnits(scope).subscribe(filteredUnits => { + this.availableOrganizationalUnits = filteredUnits; + if (!this.data?.organizationalUnit) { + this.taskForm.get('organizationalUnit')?.setValue(''); + } + this.taskForm.get('organizationalUnit')?.setValidators(Validators.required); + }); + } + this.taskForm.get('organizationalUnit')?.updateValueAndValidity(); + } + + isClientPreSelected(client: any): boolean { + return this.data?.selectedClients && this.data.selectedClients.some((c: any) => c.uuid === client.uuid); } startUnitsFilter(): Promise { @@ -209,14 +250,31 @@ export class CreateTaskComponent implements OnInit { } const formData = this.taskForm.value; + const scope = formData.scope; + + if (scope === 'clients' && this.selectedClients.length === 0) { + this.toastr.error('Debe seleccionar al menos un cliente'); + return; + } + + if (scope !== 'clients' && !formData.organizationalUnit) { + this.toastr.error('Debe seleccionar una unidad organizativa'); + return; + } const payload: any = { name: formData.name, scope: formData.scope, - organizationalUnit: formData.organizationalUnit, notes: formData.notes || '', }; + if (scope === 'clients') { + payload.clients = this.selectedClients.map(client => client.uuid); + payload.organizationalUnit = null; + } else { + payload.organizationalUnit = formData.organizationalUnit; + } + if (this.editing) { const taskId = this.data.task.uuid; this.http.patch(`${this.apiUrl}/${taskId}`, payload).subscribe({ diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.ts b/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.ts index 88ea5bf..108197a 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.ts +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.ts @@ -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 } diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts index 0912afc..27ace1b 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts @@ -107,18 +107,18 @@ export class ExecuteCommandComponent implements OnInit { this.arrayCommands = this.arrayCommands.map(command => { if (allOffOrDisconnected) { - command.disabled = command.slug !== 'power-on'; + command.disabled = command.slug !== 'power-on' && command.slug !== 'create-image'; } else if (allSameState) { if (states[0] === 'off' || states[0] === 'disconnected') { - command.disabled = command.slug !== 'power-on'; + command.disabled = !['power-on', 'create-image'].includes(command.slug); } else { command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'remove-cache-image', 'partition', 'run-script', 'software-inventory'].includes(command.slug); } } else { - if (command.slug === 'create-image'|| command.slug === 'software-inventory') { + if (command.slug === 'software-inventory') { command.disabled = multipleClients; } else if ( - ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'remove-cache-image', 'run-script'].includes(command.slug) + ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'remove-cache-image', 'run-script', 'create-image'].includes(command.slug) ) { command.disabled = false; } else {