From 558a3205ab06d98224adfba9e44f7ad89be64f22 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Mon, 21 Apr 2025 14:56:09 +0200 Subject: [PATCH 01/47] refs #1884 Add clientState input to execute-command component for dynamic state handling --- .../execute-command.component.ts | 42 ++++++++++++++++--- .../components/groups/groups.component.html | 4 +- 2 files changed, 39 insertions(+), 7 deletions(-) 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 90af7b0..721a97f 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 @@ -10,6 +10,7 @@ import { ConfigService } from '@services/config.service'; styleUrls: ['./execute-command.component.css'] }) export class ExecuteCommandComponent implements OnInit { + @Input() clientState: string = 'off'; @Input() clientData: any[] = []; @Input() buttonType: 'icon' | 'text' | 'menu-item' = 'icon'; @Input() buttonText: string = 'Ejecutar Comandos'; @@ -45,6 +46,39 @@ export class ExecuteCommandComponent implements OnInit { ngOnInit(): void { this.clientData = this.clientData || []; + this.updateCommandStates(); + } + + ngOnChanges(): void { + this.updateCommandStates(); + } + + private updateCommandStates(): void { + let states: string[] = []; + + if (this.clientData.length > 0) { + states = this.clientData.map(client => client.status); + } else if (this.clientState) { + states = [this.clientState]; + } + + const allOff = states.every(state => state === 'off'); + const allSameState = states.every(state => state === states[0]); + + this.arrayCommands = this.arrayCommands.map(command => { + if (allOff) { + command.disabled = command.slug !== 'power-on'; + } else if (allSameState) { + if (states[0] === 'off') { + command.disabled = command.slug !== 'power-on'; + } else { + command.disabled = command.slug === 'power-on'; + } + } else { + command.disabled = true; + } + return command; + }); } onCommandSelect(action: any): void { @@ -137,7 +171,7 @@ export class ExecuteCommandComponent implements OnInit { const clientDataToSend = this.clientData.map(client => ({ name: client.name, mac: client.mac, - uuid: '/clients/'+client.uuid, + uuid: '/clients/' + client.uuid, status: client.status, partitions: client.partitions, firmwareType: client.firmwareType, @@ -161,7 +195,7 @@ export class ExecuteCommandComponent implements OnInit { const clientDataToSend = this.clientData.map(client => ({ name: client.name, mac: client.mac, - uuid: '/clients/'+client.uuid, + uuid: '/clients/' + client.uuid, status: client.status, partitions: client.partitions, ip: client.ip @@ -178,7 +212,7 @@ export class ExecuteCommandComponent implements OnInit { const clientDataToSend = this.clientData.map(client => ({ name: client.name, mac: client.mac, - uuid: '/clients/'+client.uuid, + uuid: '/clients/' + client.uuid, status: client.status, partitions: client.partitions, ip: client.ip @@ -190,6 +224,4 @@ export class ExecuteCommandComponent implements OnInit { console.log('Navigated to run script with data:', clientDataToSend); }); } - - } diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index 116dbc3..95db6c5 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -260,7 +260,7 @@ {{ client.mac }}
- - From 6621f7a8fe336eac21ad731d9d8f30160d34e572 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Tue, 22 Apr 2025 12:59:49 +0200 Subject: [PATCH 02/47] refs #1884 Enhance command execution logic to handle 'disconnected' state and update UI for multiple clients --- .../execute-command.component.ts | 17 +++++++++++++---- .../app/components/groups/groups.component.html | 1 - .../app/components/groups/groups.component.ts | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) 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 721a97f..6ebe109 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 @@ -62,20 +62,29 @@ export class ExecuteCommandComponent implements OnInit { states = [this.clientState]; } - const allOff = states.every(state => state === 'off'); + const allOffOrDisconnected = states.every(state => state === 'off' || state === 'disconnected'); const allSameState = states.every(state => state === states[0]); + const multipleClients = this.clientData.length > 1; this.arrayCommands = this.arrayCommands.map(command => { - if (allOff) { + if (allOffOrDisconnected) { command.disabled = command.slug !== 'power-on'; } else if (allSameState) { - if (states[0] === 'off') { + if (states[0] === 'off' || states[0] === 'disconnected') { command.disabled = command.slug !== 'power-on'; } else { command.disabled = command.slug === 'power-on'; } } else { - command.disabled = true; + if (command.slug === 'create-image') { + command.disabled = multipleClients; + } else if ( + ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug) + ) { + command.disabled = false; + } else { + command.disabled = true; + } } return command; }); diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index 95db6c5..b15de2e 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -202,7 +202,6 @@ [buttonText]="'Ejecutar comandos'" [icon]="'terminal'" [disabled]="!((selectedNode?.clients ?? []).length > 0)"> -
diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 0235ab9..b1a85ec 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -85,6 +85,7 @@ export class GroupsComponent implements OnInit, OnDestroy { { value: 'windows-session', name: 'Windows Session' }, { value: 'busy', name: 'Ocupado' }, { value: 'mac', name: 'Mac' }, + { value: 'disconnected', name: 'Desconectado'} ]; displayedColumns: string[] = ['select', 'status', 'ip', 'firmwareType', 'name', 'oglive', 'subnet', 'pxeTemplate', 'actions']; From a418d2661511efb619213d609180785dc8065a07 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Tue, 22 Apr 2025 13:19:22 +0200 Subject: [PATCH 03/47] refs #1884 Refactor command state handling to improve client status management and enable/disable commands based on client states --- .../execute-command.component.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) 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 6ebe109..5824047 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 @@ -55,41 +55,49 @@ export class ExecuteCommandComponent implements OnInit { private updateCommandStates(): void { let states: string[] = []; - + + // Obtener los estados de los clientes if (this.clientData.length > 0) { states = this.clientData.map(client => client.status); } else if (this.clientState) { states = [this.clientState]; } - + const allOffOrDisconnected = states.every(state => state === 'off' || state === 'disconnected'); const allSameState = states.every(state => state === states[0]); - const multipleClients = this.clientData.length > 1; - + const multipleClients = this.clientData.length > 1; + this.arrayCommands = this.arrayCommands.map(command => { if (allOffOrDisconnected) { + // Si todos los clientes están apagados o desconectados, solo habilitar "Encender" command.disabled = command.slug !== 'power-on'; } else if (allSameState) { + // Si todos los clientes tienen el mismo estado if (states[0] === 'off' || states[0] === 'disconnected') { command.disabled = command.slug !== 'power-on'; } else { - command.disabled = command.slug === 'power-on'; + // Habilitar comandos específicos para un cliente que no está apagado ni desconectado + command.disabled = !['reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug); } } else { + // Si los estados son distintos if (command.slug === 'create-image') { + // "Crear imagen" solo está habilitado para un único cliente command.disabled = multipleClients; } else if ( ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug) ) { + // Habilitar los comandos permitidos cuando los estados son distintos command.disabled = false; } else { + // Deshabilitar otros comandos command.disabled = true; } } return command; }); } - + onCommandSelect(action: any): void { if (action === 'partition') { this.openPartitionAssistant(); From 88aaf39b65aa40b8435aaf7fddd3d25fb1ce0ac6 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Tue, 22 Apr 2025 13:30:01 +0200 Subject: [PATCH 04/47] refs #1884 Update command enabling logic to include 'create-image' for active clients --- .../main-commands/execute-command/execute-command.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5824047..f89642d 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 @@ -77,7 +77,7 @@ export class ExecuteCommandComponent implements OnInit { command.disabled = command.slug !== 'power-on'; } else { // Habilitar comandos específicos para un cliente que no está apagado ni desconectado - command.disabled = !['reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug); + command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'partition', 'run-script'].includes(command.slug); } } else { // Si los estados son distintos From ca0140e2750bb052cea486252bbd4ebfa3590952 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 23 Apr 2025 12:45:32 +0200 Subject: [PATCH 05/47] refs #1931 Add ClientDetailsComponent for enhanced client information display and management --- ogWebconsole/src/app/app.module.ts | 4 +- .../execute-command.component.ts | 16 +- .../app/components/groups/groups.component.ts | 12 +- .../client-details.component.css | 326 ++++++++++++++++++ .../client-details.component.html | 64 ++++ .../client-details.component.spec.ts | 52 +++ .../client-details.component.ts | 320 +++++++++++++++++ 7 files changed, 777 insertions(+), 17 deletions(-) create mode 100644 ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css create mode 100644 ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html create mode 100644 ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.spec.ts create mode 100644 ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 2bd3f99..2b1fa14 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -143,6 +143,7 @@ import { import { EditImageComponent } from './components/repositories/edit-image/edit-image.component'; import { ShowGitImagesComponent } from './components/repositories/show-git-images/show-git-images.component'; import { RenameImageComponent } from './components/repositories/rename-image/rename-image.component'; +import { ClientDetailsComponent } from './components/groups/shared/client-details/client-details.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); @@ -243,7 +244,8 @@ registerLocaleData(localeEs, 'es-ES'); SaveScriptComponent, EditImageComponent, ShowGitImagesComponent, - RenameImageComponent + RenameImageComponent, + ClientDetailsComponent ], bootstrap: [AppComponent], imports: [BrowserModule, 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 f89642d..5aff89e 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 @@ -55,49 +55,41 @@ export class ExecuteCommandComponent implements OnInit { private updateCommandStates(): void { let states: string[] = []; - - // Obtener los estados de los clientes + if (this.clientData.length > 0) { states = this.clientData.map(client => client.status); } else if (this.clientState) { states = [this.clientState]; } - + const allOffOrDisconnected = states.every(state => state === 'off' || state === 'disconnected'); const allSameState = states.every(state => state === states[0]); const multipleClients = this.clientData.length > 1; - + this.arrayCommands = this.arrayCommands.map(command => { if (allOffOrDisconnected) { - // Si todos los clientes están apagados o desconectados, solo habilitar "Encender" command.disabled = command.slug !== 'power-on'; } else if (allSameState) { - // Si todos los clientes tienen el mismo estado if (states[0] === 'off' || states[0] === 'disconnected') { command.disabled = command.slug !== 'power-on'; } else { - // Habilitar comandos específicos para un cliente que no está apagado ni desconectado command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'partition', 'run-script'].includes(command.slug); } } else { - // Si los estados son distintos if (command.slug === 'create-image') { - // "Crear imagen" solo está habilitado para un único cliente command.disabled = multipleClients; } else if ( ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug) ) { - // Habilitar los comandos permitidos cuando los estados son distintos command.disabled = false; } else { - // Deshabilitar otros comandos command.disabled = true; } } return command; }); } - + onCommandSelect(action: any): void { if (action === 'partition') { this.openPartitionAssistant(); diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index b1a85ec..0c537b1 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit, OnDestroy, ViewChild, QueryList, ViewChildren, ChangeDetectorRef} from '@angular/core'; +import { Component, OnInit, OnDestroy, ViewChild, QueryList, ViewChildren, ChangeDetectorRef } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; @@ -26,6 +26,7 @@ import { Subject } from 'rxjs'; import { ConfigService } from '@services/config.service'; import { BreakpointObserver } from '@angular/cdk/layout'; import { MatMenuTrigger } from '@angular/material/menu'; +import { ClientDetailsComponent } from './shared/client-details/client-details.component'; enum NodeType { OrganizationalUnit = 'organizational-unit', @@ -85,7 +86,7 @@ export class GroupsComponent implements OnInit, OnDestroy { { value: 'windows-session', name: 'Windows Session' }, { value: 'busy', name: 'Ocupado' }, { value: 'mac', name: 'Mac' }, - { value: 'disconnected', name: 'Desconectado'} + { value: 'disconnected', name: 'Desconectado' } ]; displayedColumns: string[] = ['select', 'status', 'ip', 'firmwareType', 'name', 'oglive', 'subnet', 'pxeTemplate', 'actions']; @@ -182,7 +183,7 @@ export class GroupsComponent implements OnInit, OnDestroy { const index = this.arrayClients.findIndex(client => client['@id'] === clientUuid); if (index !== -1) { - const updatedClient = {...this.arrayClients[index], status}; + const updatedClient = { ...this.arrayClients[index], status }; this.arrayClients = [ ...this.arrayClients.slice(0, index), updatedClient, @@ -609,7 +610,10 @@ export class GroupsComponent implements OnInit, OnDestroy { onShowClientDetail(event: MouseEvent, client: Client): void { event.stopPropagation(); - this.router.navigate(['clients', client.uuid], { state: { clientData: client } }); + this.dialog.open(ClientDetailsComponent, { + width: '600px', + data: { clientData: client }, + }) } diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css new file mode 100644 index 0000000..3ca7524 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css @@ -0,0 +1,326 @@ +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; +} + +.client-container { + flex-grow: 1; + box-sizing: border-box; + overflow: hidden; + display: flex; + flex-direction: column; + padding: 0rem 1rem 0rem 0.5rem; +} + +.client-icon { + flex-shrink: 0; + margin-right: 20px; + display: flex; + align-items: center; + justify-content: center; + min-width: 120px; + min-height: 120px; +} + +.row-container { + justify-content: space-between; + width: 100%; +} + +.table-container { + padding-right: 10px; +} + +.charts-wrapper { + width: 100%; + margin-top: 20px; +} + +.charts-row { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 20px; +} + +.disk-usage { + text-align: center; + flex: 1; + min-width: 200px; +} + +.circular-chart { + max-width: 150px; + max-height: 150px; + margin: 0 auto; +} + +.chart { + display: flex; + justify-content: center; +} + +.icon-pc { + font-size: 25px; + color: #3b82f6; +} + +.client-title h1 { + font-size: 2rem; + margin-bottom: 10px; +} + +.client-title p { + margin: 2px 0; + font-size: 1rem; + color: #666; +} + +.client-info { + margin: 20px 0; + border-radius: 12px; + background-color: #f5f7fa; + padding: 20px; + border: 2px solid #d1d9e6; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); +} + +.info-section { + background-color: #fff; + border-radius: 12px; +} + +.two-column-table { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-top: 15px; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); +} + +.table-row { + display: flex; + justify-content: space-between; + padding: 10px; + border-bottom: 1px solid #e0e0e0; +} + +.column.property { + font-weight: bold; + text-align: left; + width: 45%; +} + +.column.value { + text-align: right; + width: 45%; +} + +.mat-tab-group { + min-height: 400px; +} + +.mat-tab-body-wrapper { + min-height: inherit; +} + +.info-section h2 { + font-size: 1.4rem; + margin-bottom: 10px; + color: #0056b3; +} + +.info-section p { + font-size: 1rem; + margin: 5px 0; +} + +.second-section { + display: grid; + gap: 20px; +} + +.client-button-row { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 20px; +} + +.buttons-row { + display: flex; + flex-direction: column; + background-color: #fff; + padding: 20px; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + justify-content: flex-start; +} + +.buttons-row button { + margin-bottom: 10px; + width: 100%; +} + +.circular-chart { + display: block; + margin: 0 auto; + max-width: 100%; + max-height: 150px; +} + +.circle-bg { + fill: none; + stroke: #f0f0f0; + stroke-width: 3.8; +} + +.circle { + fill: none; + stroke-width: 3.8; + stroke: #00bfa5; + stroke-linecap: round; + animation: progress 1s ease-out forwards; +} + +.percentage { + fill: #333; + font-size: 0.7rem; + text-anchor: middle; +} + +.disk-usage h3 { + margin: 0 0 10px 0; + font-size: 1.2rem; + color: #333; +} + +@keyframes progress { + 0% { + stroke-dasharray: 0, 100; + } +} + +.assistants-container { + background-color: #fff; + margin-top: 10px; + padding: 20px; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.circular-chart { + display: block; + margin: 0 auto; + max-width: 100%; + max-height: 150px; +} + +.circle-bg { + fill: none; + stroke: #f0f0f0; + stroke-width: 3.8; +} + +.circle { + fill: none; + stroke-width: 3.8; + stroke-linecap: round; + animation: progress 1s ease-out forwards; +} + +.partition-0 { + stroke: #00bfa5; +} + +.partition-1 { + stroke: #ff6f61; +} + +.partition-2 { + stroke: #ffb400; +} + +.partition-3 { + stroke: #3498db; +} + +.percentage { + fill: #333; + font-size: 0.7rem; + text-anchor: middle; +} + +.disk-container { + display: flex; + flex-direction: row; + gap: 20px; + background-color: #f5f7fa; + border: 2px solid #d1d9e6; + border-radius: 10px; + padding: 20px; + margin-top: 20px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); + flex-wrap: wrap; + justify-content: center; + align-items: stretch; + margin-bottom: 20px; +} + +.table-container { + flex: 3; + display: flex; + justify-content: center; + align-items: center; +} + +table.mat-elevation-z8 { + width: 100%; + max-width: 800px; + background-color: white; + border-radius: 8px; + overflow: hidden; +} + +.mat-header-cell { + background-color: #d1d9e6 !important; + color: #333; + font-weight: bold; + text-align: center; +} + +.mat-cell { + text-align: center; +} + +.mat-chip { + font-weight: bold; + color: white; +} + +.charts-container { + flex: 2; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.disk-usage { + background-color: white; + padding: 15px; + border-radius: 8px; + justify-self: center; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); + text-align: center; +} + +.chart { + display: flex; + justify-content: center; +} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html new file mode 100644 index 0000000..0a52ea4 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html @@ -0,0 +1,64 @@ + + +
+
+

{{ 'clientDetailsTitle' | translate }} {{ clientData.name }}

+
+ + + +
+
+
+
+
{{ clientData?.property }}
+
{{ clientData?.value }}
+
+
+
+
+
{{ clientData?.property }}
+
{{ clientData?.value }}
+
+
+
+
+
+

Discos/Particiones

+
+ +
+
+ + + + + + + +
{{ column.header }} + + {{ column.cell(image) }} + + + + {{ (image.size / 1024).toFixed(2) }} GB + + +
+
+ +
+ +
+ + + +

Disco {{ disk.diskNumber }}

+

Usado: {{ (disk.used).toFixed(2) }} GB ({{ disk.percentage }}%)

+

Total: {{ disk.total }} GB

+
+
+
+
+
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.spec.ts b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.spec.ts new file mode 100644 index 0000000..bd363f4 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.spec.ts @@ -0,0 +1,52 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ClientDetailsComponent } from './client-details.component'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { ToastrModule } from 'ngx-toastr'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { ConfigService } from '@services/config.service'; +import { LoadingComponent } from 'src/app/shared/loading/loading.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { MatDividerModule } from '@angular/material/divider'; +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatTableModule } from '@angular/material/table'; + +describe('ClientDetailsComponent', () => { + let component: ClientDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url', + mercureUrl: 'http://mock-mercure-url' + }; + await TestBed.configureTestingModule({ + declarations: [ClientDetailsComponent, LoadingComponent], + imports: [ + MatDialogModule, + HttpClientTestingModule, + ToastrModule.forRoot(), + MatDividerModule, + TranslateModule.forRoot(), + CommonModule, + MatTableModule, + MatProgressSpinnerModule + ], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } }, + { provide: ConfigService, useValue: mockConfigService } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ClientDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts new file mode 100644 index 0000000..240bb3a --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts @@ -0,0 +1,320 @@ +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { DatePipe } from "@angular/common"; +import { MatTableDataSource } from "@angular/material/table"; +import { MatDialog } from "@angular/material/dialog"; +import { Router } from "@angular/router"; +import { ToastrService } from "ngx-toastr"; +import { ManageClientComponent } from "../../shared/clients/manage-client/manage-client.component"; +import { ConfigService } from '@services/config.service'; + +interface ClientInfo { + property: string; + value: any; +} + +@Component({ + selector: 'app-client-details', + templateUrl: './client-details.component.html', + styleUrl: './client-details.component.css' +}) +export class ClientDetailsComponent { + baseUrl: string; + @ViewChild('assistantContainer') assistantContainer!: ElementRef; + clientUuid: string; + clientData: any = {}; + isPartitionAssistantVisible: boolean = false; + isBootImageVisible: boolean = false; + isDiskUsageVisible: boolean = true; + dataSource = new MatTableDataSource(); + generalData: ClientInfo[] = []; + networkData: ClientInfo[] = []; + classroomData: ClientInfo[] = []; + diskUsageData: any[] = []; + partitions: any[] = []; + commands: any[] = []; + chartDisk: any[] = []; + view: [number, number] = [300, 200]; + showLegend: boolean = true; + + arrayCommands: any[] = [ + { name: 'Enceder', slug: 'power-on' }, + { name: 'Apagar', slug: 'power-off' }, + { name: 'Reiniciar', slug: 'reboot' }, + { name: 'Iniciar Sesión', slug: 'login' }, + { name: 'Crear imagen', slug: 'create-image' }, + { name: 'Clonar/desplegar imagen', slug: 'deploy-image' }, + { name: 'Eliminar Imagen Cache', slug: 'delete-image-cache' }, + { name: 'Particionar y Formatear', slug: 'partition' }, + { name: 'Inventario Software', slug: 'software-inventory' }, + { name: 'Inventario Hardware', slug: 'hardware-inventory' }, + { name: 'Ejecutar comando', slug: 'run-script' }, + ]; + + datePipe: DatePipe = new DatePipe('es-ES'); + columns = [ + { + columnDef: 'diskNumber', + header: 'Disco', + cell: (partition: any) => `${partition.diskNumber}`, + }, + { + columnDef: 'partitionNumber', + header: 'Particion', + cell: (partition: any) => `${partition.partitionNumber}` + }, + { + columnDef: 'description', + header: 'Sistema de ficheros', + cell: (partition: any) => `${partition.filesystem}` + }, + { + columnDef: 'size', + header: 'Tamaño', + cell: (partition: any) => `${(partition.size / 1024).toFixed(2)} GB` + }, + { + columnDef: 'memoryUsage', + header: 'Uso', + cell: (partition: any) => `${partition.memoryUsage} %` + }, + { + columnDef: 'operativeSystem', + header: 'SO', + cell: (partition: any) => `${partition.operativeSystem?.name}` + }, + ]; + displayedColumns = [...this.columns.map(column => column.columnDef)]; + isDiskUsageEmpty: boolean = true; + loading: boolean = true; + + constructor( + private http: HttpClient, + private dialog: MatDialog, + private configService: ConfigService, + private router: Router, + private toastService: ToastrService, + @Inject(MAT_DIALOG_DATA) public data: { clientData: any } + ) { + this.baseUrl = this.configService.apiUrl; + const url = window.location.href; + const segments = url.split('/'); + this.clientUuid = segments[segments.length - 1]; + } + + ngOnInit() { + if (this.data && this.data.clientData) { + this.clientData = this.data.clientData; + this.updateGeneralData(); + this.updateNetworkData(); + this.calculateDiskUsage(); + this.loading = false; + } else { + console.error('No se recibieron datos del cliente.'); + } + } + + + loadClient = (uuid: string) => { + this.http.get(`${this.baseUrl}${uuid}`).subscribe({ + next: data => { + this.clientData = data; + this.updateGeneralData(); + this.updateNetworkData(); + this.loadPartitions() + this.loading = false; + }, + error: error => { + console.error('Error al obtener el cliente:', error); + } + }); + } + + updateGeneralData() { + this.generalData = [ + { property: 'Nombre', value: this.clientData?.name || '' }, + { property: 'IP', value: this.clientData?.ip || '' }, + { property: 'MAC', value: this.clientData?.mac || '' }, + { property: 'Nº de serie', value: this.clientData?.serialNumber || '' }, + { property: 'Netiface', value: this.clientData?.netiface || this.clientData?.organizationalUnit?.networkSettings?.netiface || '' }, + { property: 'Perfil hardware', value: this.clientData?.hardwareProfile?.description || '' }, + ]; + } + + updateNetworkData() { + this.networkData = [ + { property: 'Padre', value: this.clientData?.organizationalUnit?.name || '' }, + { property: 'Pxe', value: this.clientData?.template?.name || '' }, + { property: 'Remote Pc', value: this.clientData.remotePc || '' }, + { property: 'Subred', value: this.clientData?.subnet || '' }, + { property: 'OGlive', value: this.clientData?.ogLive?.name || '' }, + { property: 'Repositorio', value: this.clientData?.repository?.name || '' }, + ]; + } + + calculateDiskUsage() { + const diskUsageMap = new Map(); + + this.partitions.forEach((partition: any) => { + const diskNumber = partition.diskNumber; + + if (!diskUsageMap.has(diskNumber)) { + diskUsageMap.set(diskNumber, { total: 0, used: 0, partitions: [] }); + } + + const diskData = diskUsageMap.get(diskNumber); + + if (partition.partitionNumber === 0) { + diskData!.total = Number((partition.size / 1024).toFixed(2)); + } else { + diskData!.used += Number((partition.size / 1024).toFixed(2)); + diskData!.partitions.push(partition); + } + }); + + this.chartDisk = Array.from(diskUsageMap.entries()).map(([diskNumber, { total, used, partitions }]) => { + const partitionData = partitions.map(partition => ({ + name: `Partición ${partition.partitionNumber}`, + value: Number((partition.size / 1024).toFixed(2)) + })); + + const freeSpace = total - used; + if (freeSpace > 0) { + partitionData.push({ + name: 'Espacio libre', + value: Number(freeSpace.toFixed(2)) + }); + } + + return { + diskNumber, + chartData: partitionData, + total, + used, + percentage: total > 0 ? Math.round((used / total) * 100) : 0 + }; + }); + + this.diskUsageData = this.chartDisk; + this.isDiskUsageEmpty = this.diskUsageData.length === 0; + } + + onEditClick(event: MouseEvent, uuid: string): void { + event.stopPropagation(); + const dialogRef = this.dialog.open(ManageClientComponent, { data: { uuid }, width: '900px' }); + dialogRef.afterClosed().subscribe(); + } + + loadPartitions(): void { + if (!this.clientData?.id) { + console.error('El ID del cliente no está disponible.'); + return; + } + + this.http.get(`${this.baseUrl}/partitions?client.id=${this.clientData.id}&order[diskNumber, partitionNumber]=ASC`).subscribe({ + next: data => { + const filteredPartitions = data['hydra:member'].filter((partition: any) => partition.partitionNumber !== 0); + this.dataSource = filteredPartitions; + this.partitions = filteredPartitions; + this.calculateDiskUsage(); + }, + error: error => { + console.error('Error al obtener las particiones:', error); + } + }); + } + + + loadCommands(): void { + this.http.get(`${this.baseUrl}/commands?`).subscribe({ + next: data => { + this.commands = data['hydra:member']; + }, + error: error => { + console.error('Error al obtener las particiones:', error); + } + }); + } + + onCommandSelect(action: any): void { + if (action === 'partition') { + this.openPartitionAssistant(); + } + + if (action === 'create-image') { + this.openCreateImageAssistant(); + } + + if (action === 'deploy-image') { + this.openDeployImageAssistant(); + } + + if (action === 'reboot') { + this.rebootClient(); + } + + if (action === 'power-off') { + this.powerOffClient(); + } + + if (action === 'power-on') { + this.powerOnClient(); + } + } + + rebootClient(): void { + this.http.post(`${this.baseUrl}/clients/server/${this.clientData.uuid}/reboot`, {}).subscribe( + response => { + this.toastService.success('Cliente actualizado correctamente'); + }, + error => { + this.toastService.error('Error de conexión con el cliente'); + } + ); + } + + powerOnClient(): void { + const payload = { + client: this.clientData['@id'] + } + + this.http.post(`${this.baseUrl}${this.clientData.repository['@id']}/wol`, payload).subscribe( + response => { + this.toastService.success('Cliente actualizado correctamente'); + }, + error => { + this.toastService.error('Error de conexión con el cliente'); + } + ); + } + + powerOffClient(): void { + this.http.post(`${this.baseUrl}/clients/server/${this.clientData.uuid}/power-off`, {}).subscribe( + response => { + this.toastService.success('Cliente actualizado correctamente'); + }, + error => { + this.toastService.error('Error de conexión con el cliente'); + } + ); + } + + openPartitionAssistant(): void { + this.router.navigate([`/clients/${this.clientData.uuid}/partition-assistant`]).then(r => { + console.log('navigated', r); + }); + } + + openCreateImageAssistant(): void { + this.router.navigate([`/clients/${this.clientData.uuid}/create-image`]).then(r => { + console.log('navigated', r); + }); + } + + openDeployImageAssistant(): void { + this.router.navigate([`/clients/deploy-image`]).then(r => { + console.log('navigated', r); + }); + } +} From a40be684b524301ddbec0e7c1495a24b85e008e2 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 23 Apr 2025 13:05:33 +0200 Subject: [PATCH 06/47] refs #1931 Load partitions data and log relevant information for debugging --- .../groups/shared/client-details/client-details.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts index 240bb3a..0576a65 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts @@ -109,6 +109,7 @@ export class ClientDetailsComponent { this.updateGeneralData(); this.updateNetworkData(); this.calculateDiskUsage(); + this.loadPartitions(); this.loading = false; } else { console.error('No se recibieron datos del cliente.'); @@ -197,6 +198,7 @@ export class ClientDetailsComponent { }); this.diskUsageData = this.chartDisk; + console.log(this.chartDisk) this.isDiskUsageEmpty = this.diskUsageData.length === 0; } @@ -214,9 +216,11 @@ export class ClientDetailsComponent { this.http.get(`${this.baseUrl}/partitions?client.id=${this.clientData.id}&order[diskNumber, partitionNumber]=ASC`).subscribe({ next: data => { + console.log(data) const filteredPartitions = data['hydra:member'].filter((partition: any) => partition.partitionNumber !== 0); this.dataSource = filteredPartitions; this.partitions = filteredPartitions; + console.log(this.partitions) this.calculateDiskUsage(); }, error: error => { From 70e21c6ca2adc011c8f5de3ae59bb8800abfe309 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 23 Apr 2025 13:51:45 +0200 Subject: [PATCH 07/47] refs #1931 Refactor ClientDetailsComponent to enhance layout and improve data handling in client details view --- .../client-details.component.css | 332 +++--------------- .../client-details.component.html | 108 +++--- .../client-details.component.ts | 3 - 3 files changed, 101 insertions(+), 342 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css index 3ca7524..b408c18 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css @@ -1,326 +1,88 @@ -.header-container { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px; -} - -.client-container { - flex-grow: 1; - box-sizing: border-box; - overflow: hidden; +.modal-content { + max-height: 80vh; + overflow-y: auto; + background-color: #fff; display: flex; flex-direction: column; - padding: 0rem 1rem 0rem 0.5rem; } -.client-icon { - flex-shrink: 0; - margin-right: 20px; - display: flex; - align-items: center; - justify-content: center; - min-width: 120px; - min-height: 120px; +.section-header { + margin-bottom: 8px; } -.row-container { - justify-content: space-between; - width: 100%; -} - -.table-container { - padding-right: 10px; -} - -.charts-wrapper { - width: 100%; - margin-top: 20px; -} - -.charts-row { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - gap: 20px; -} - -.disk-usage { - text-align: center; - flex: 1; - min-width: 200px; -} - -.circular-chart { - max-width: 150px; - max-height: 150px; - margin: 0 auto; -} - -.chart { - display: flex; - justify-content: center; -} - -.icon-pc { - font-size: 25px; - color: #3b82f6; -} - -.client-title h1 { - font-size: 2rem; - margin-bottom: 10px; -} - -.client-title p { - margin: 2px 0; - font-size: 1rem; - color: #666; -} - -.client-info { - margin: 20px 0; +.client-info-card { + background-color: #e4e4e4; + padding: 24px; border-radius: 12px; - background-color: #f5f7fa; - padding: 20px; - border: 2px solid #d1d9e6; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); + margin-bottom: 2em; + margin-top: 1.5em; } -.info-section { - background-color: #fff; - border-radius: 12px; -} - -.two-column-table { +.info-columns { display: grid; grid-template-columns: 1fr 1fr; - gap: 10px; - margin-top: 15px; + gap: 24px; } -.mat-elevation-z8 { - box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); -} - -.table-row { - display: flex; - justify-content: space-between; - padding: 10px; - border-bottom: 1px solid #e0e0e0; -} - -.column.property { - font-weight: bold; - text-align: left; - width: 45%; -} - -.column.value { - text-align: right; - width: 45%; -} - -.mat-tab-group { - min-height: 400px; -} - -.mat-tab-body-wrapper { - min-height: inherit; -} - -.info-section h2 { - font-size: 1.4rem; - margin-bottom: 10px; - color: #0056b3; -} - -.info-section p { - font-size: 1rem; - margin: 5px 0; -} - -.second-section { - display: grid; - gap: 20px; -} - -.client-button-row { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - gap: 20px; -} - -.buttons-row { +.info-column { display: flex; flex-direction: column; - background-color: #fff; - padding: 20px; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - justify-content: flex-start; + gap: 16px; } -.buttons-row button { - margin-bottom: 10px; - width: 100%; +.info-pair { + display: flex; + flex-direction: column; } -.circular-chart { - display: block; - margin: 0 auto; - max-width: 100%; - max-height: 150px; +.label { + font-size: 0.9rem; + font-weight: 550; + color: #3f51b5; } -.circle-bg { - fill: none; - stroke: #f0f0f0; - stroke-width: 3.8; -} - -.circle { - fill: none; - stroke-width: 3.8; - stroke: #00bfa5; - stroke-linecap: round; - animation: progress 1s ease-out forwards; -} - -.percentage { - fill: #333; - font-size: 0.7rem; - text-anchor: middle; -} - -.disk-usage h3 { - margin: 0 0 10px 0; - font-size: 1.2rem; - color: #333; -} - -@keyframes progress { - 0% { - stroke-dasharray: 0, 100; - } -} - -.assistants-container { - background-color: #fff; - margin-top: 10px; - padding: 20px; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); -} - -.circular-chart { - display: block; - margin: 0 auto; - max-width: 100%; - max-height: 150px; -} - -.circle-bg { - fill: none; - stroke: #f0f0f0; - stroke-width: 3.8; -} - -.circle { - fill: none; - stroke-width: 3.8; - stroke-linecap: round; - animation: progress 1s ease-out forwards; -} - -.partition-0 { - stroke: #00bfa5; -} - -.partition-1 { - stroke: #ff6f61; -} - -.partition-2 { - stroke: #ffb400; -} - -.partition-3 { - stroke: #3498db; -} - -.percentage { - fill: #333; - font-size: 0.7rem; - text-anchor: middle; +.value { + font-size: 1rem; + color: #222; + word-break: break-word; } .disk-container { display: flex; - flex-direction: row; - gap: 20px; - background-color: #f5f7fa; - border: 2px solid #d1d9e6; - border-radius: 10px; - padding: 20px; - margin-top: 20px; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); - flex-wrap: wrap; - justify-content: center; - align-items: stretch; - margin-bottom: 20px; + flex-direction: column; + gap: 32px; } .table-container { - flex: 3; - display: flex; - justify-content: center; - align-items: center; -} - -table.mat-elevation-z8 { - width: 100%; - max-width: 800px; - background-color: white; + overflow-x: auto; border-radius: 8px; - overflow: hidden; + background: #fff; + padding: 16px; + box-shadow: 0 2px 6px rgba(0,0,0,0.05); } -.mat-header-cell { - background-color: #d1d9e6 !important; - color: #333; - font-weight: bold; - text-align: center; -} - -.mat-cell { - text-align: center; -} - -.mat-chip { - font-weight: bold; - color: white; +.mat-elevation-z8 { + width: 100%; + border-radius: 8px; } .charts-container { - flex: 2; display: flex; - flex-direction: column; - align-items: center; - justify-content: center; + flex-wrap: wrap; + gap: 24px; } .disk-usage { - background-color: white; - padding: 15px; - border-radius: 8px; - justify-self: center; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); - text-align: center; + flex: 1 1 300px; + padding: 16px; + background: #f9f9f9; + border-radius: 12px; + box-shadow: 0 2px 6px rgba(0,0,0,0.05); } .chart { - display: flex; - justify-content: center; -} \ No newline at end of file + width: 100%; + height: 200px; + margin-bottom: 12px; +} diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html index 0a52ea4..1238980 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html @@ -1,64 +1,64 @@ - + + -
-
-

{{ 'clientDetailsTitle' | translate }} {{ clientData.name }}

-
+
+
+

{{ 'clientDetailsTitle' | translate }} {{ clientData.name }}

+
- - -
-
-
-
-
{{ clientData?.property }}
-
{{ clientData?.value }}
+
+
+
+
+
{{ data?.property }}
+
{{ data?.value || '--' }}
+
-
-
-
-
{{ clientData?.property }}
-
{{ clientData?.value }}
+
+
+
{{ data?.property }}
+
{{ data?.value || '--' }}
+
-
-
-

Discos/Particiones

-
-
-
- - - - +
+

Discos/Particiones

+
+ +
+
+
{{ column.header }} - - {{ column.cell(image) }} - - - - {{ (image.size / 1024).toFixed(2) }} GB - - -
+ + + + + + +
{{ column.header }} + + {{ column.cell(image) }} + + + + {{ (image.size / 1024).toFixed(2) }} GB + + +
+
+ +
+ +
+ + +

Disco {{ disk.diskNumber }}

+

Usado: {{ (disk.used).toFixed(2) }} GB ({{ disk.percentage }}%)

+

Total: {{ disk.total }} GB

+
- - - -
- -
- -
- - - -

Disco {{ disk.diskNumber }}

-

Usado: {{ (disk.used).toFixed(2) }} GB ({{ disk.percentage }}%)

-

Total: {{ disk.total }} GB

-
-
+
-
\ No newline at end of file + \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts index 0576a65..f1961ed 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts @@ -198,7 +198,6 @@ export class ClientDetailsComponent { }); this.diskUsageData = this.chartDisk; - console.log(this.chartDisk) this.isDiskUsageEmpty = this.diskUsageData.length === 0; } @@ -216,11 +215,9 @@ export class ClientDetailsComponent { this.http.get(`${this.baseUrl}/partitions?client.id=${this.clientData.id}&order[diskNumber, partitionNumber]=ASC`).subscribe({ next: data => { - console.log(data) const filteredPartitions = data['hydra:member'].filter((partition: any) => partition.partitionNumber !== 0); this.dataSource = filteredPartitions; this.partitions = filteredPartitions; - console.log(this.partitions) this.calculateDiskUsage(); }, error: error => { From f016c66d559c2b0b8b821cef6fa8e1da57f8c93a Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 23 Apr 2025 14:22:12 +0200 Subject: [PATCH 08/47] refs #1931 Refactor ClientMainViewComponent and related files: remove component and styles, update routing, and adjust dialog dimensions for improved client detail display --- ogWebconsole/src/app/app-routing.module.ts | 3 - ogWebconsole/src/app/app.module.ts | 2 - .../client-main-view.component.css | 351 ------------------ .../client-main-view.component.html | 71 ---- .../client-main-view.component.ts | 313 ---------------- .../app/components/groups/groups.component.ts | 2 +- .../client-details.component.css | 26 +- .../client-details.component.html | 3 +- ogWebconsole/src/locale/en.json | 2 +- ogWebconsole/src/locale/es.json | 2 +- 10 files changed, 22 insertions(+), 753 deletions(-) delete mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css delete mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html delete mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts diff --git a/ogWebconsole/src/app/app-routing.module.ts b/ogWebconsole/src/app/app-routing.module.ts index cbd353a..3a0ca83 100644 --- a/ogWebconsole/src/app/app-routing.module.ts +++ b/ogWebconsole/src/app/app-routing.module.ts @@ -18,8 +18,6 @@ import { CommandsComponent } from './components/commands/main-commands/commands. import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component'; import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component'; import { TaskLogsComponent } from './components/commands/commands-task/task-logs/task-logs.component'; -import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component'; -import { ImagesComponent } from './components/images/images.component'; import {SoftwareComponent} from "./components/software/software.component"; import {SoftwareProfileComponent} from "./components/software-profile/software-profile.component"; import {OperativeSystemComponent} from "./components/operative-system/operative-system.component"; @@ -67,7 +65,6 @@ const routes: Routes = [ { path: 'clients/deploy-image', component: DeployImageComponent }, { path: 'clients/partition-assistant', component: PartitionAssistantComponent }, { path: 'clients/run-script', component: RunScriptAssistantComponent }, - { path: 'clients/:id', component: ClientMainViewComponent }, { path: 'clients/:id/create-image', component: CreateClientImageComponent }, { path: 'repositories', component: RepositoriesComponent }, { path: 'repository/:id', component: MainRepositoryViewComponent }, diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 2b1fa14..a356d2e 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -89,7 +89,6 @@ import { CreateTaskComponent } from './components/commands/commands-task/create- import { DetailTaskComponent } from './components/commands/commands-task/detail-task/detail-task.component'; import { TaskLogsComponent } from './components/commands/commands-task/task-logs/task-logs.component'; import { MatSliderModule } from '@angular/material/slider'; -import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component'; import { ImagesComponent } from './components/images/images.component'; import { CreateImageComponent } from './components/images/create-image/create-image.component'; import { CreateClientImageComponent } from './components/groups/components/client-main-view/create-image/create-image.component'; @@ -205,7 +204,6 @@ registerLocaleData(localeEs, 'es-ES'); TaskLogsComponent, ServerInfoDialogComponent, StatusComponent, - ClientMainViewComponent, ImagesComponent, CreateImageComponent, PartitionAssistantComponent, diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css deleted file mode 100644 index 664c4d0..0000000 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css +++ /dev/null @@ -1,351 +0,0 @@ -.header-container { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px; -} - -.client-container { - flex-grow: 1; - box-sizing: border-box; - overflow: hidden; - display: flex; - flex-direction: column; - padding: 0rem 1rem 0rem 0.5rem; -} - -.client-icon { - flex-shrink: 0; - margin-right: 20px; - display: flex; - align-items: center; - justify-content: center; - min-width: 120px; - min-height: 120px; -} - -.row-container { - justify-content: space-between; - width: 100%; -} - -.table-container { - padding-right: 10px; -} - -.charts-wrapper { - width: 100%; - margin-top: 20px; -} - -.charts-row { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - gap: 20px; -} - -.disk-usage { - text-align: center; - flex: 1; - min-width: 200px; -} - -.circular-chart { - max-width: 150px; - max-height: 150px; - margin: 0 auto; -} - -.chart { - display: flex; - justify-content: center; -} - -.icon-pc { - font-size: 25px; - color: #3b82f6; -} - -.client-title h1 { - font-size: 2rem; - margin-bottom: 10px; -} - -.client-title p { - margin: 2px 0; - font-size: 1rem; - color: #666; -} - -.client-info { - margin: 20px 0; - border-radius: 12px; - background-color: #f5f7fa; - padding: 20px; - border: 2px solid #d1d9e6; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); -} - -.info-section { - background-color: #fff; - border-radius: 12px; -} - -.two-column-table { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 10px; - margin-top: 15px; -} - -.mat-elevation-z8 { - box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); -} - -.table-row { - display: flex; - justify-content: space-between; - padding: 10px; - border-bottom: 1px solid #e0e0e0; -} - -.column.property { - font-weight: bold; - text-align: left; - width: 45%; -} - -.column.value { - text-align: right; - width: 45%; -} - -.mat-tab-group { - min-height: 400px; -} - -.mat-tab-body-wrapper { - min-height: inherit; -} - -.info-section h2 { - font-size: 1.4rem; - margin-bottom: 10px; - color: #0056b3; -} - -.info-section p { - font-size: 1rem; - margin: 5px 0; -} - -.second-section { - display: grid; - gap: 20px; -} - -.client-button-row { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - gap: 20px; -} - -.buttons-row { - display: flex; - flex-direction: column; - background-color: #fff; - padding: 20px; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - justify-content: flex-start; -} - -.buttons-row button { - margin-bottom: 10px; - width: 100%; -} - -.circular-chart { - display: block; - margin: 0 auto; - max-width: 100%; - max-height: 150px; -} - -.circle-bg { - fill: none; - stroke: #f0f0f0; - stroke-width: 3.8; -} - -.circle { - fill: none; - stroke-width: 3.8; - stroke: #00bfa5; - stroke-linecap: round; - animation: progress 1s ease-out forwards; -} - -.percentage { - fill: #333; - font-size: 0.7rem; - text-anchor: middle; -} - -.disk-usage h3 { - margin: 0 0 10px 0; - font-size: 1.2rem; - color: #333; -} - -@keyframes progress { - 0% { - stroke-dasharray: 0, 100; - } -} - -.assistants-container { - background-color: #fff; - margin-top: 10px; - padding: 20px; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); -} - -.circular-chart { - display: block; - margin: 0 auto; - max-width: 100%; - max-height: 150px; -} - -.circle-bg { - fill: none; - stroke: #f0f0f0; - stroke-width: 3.8; -} - -.circle { - fill: none; - stroke-width: 3.8; - stroke-linecap: round; - animation: progress 1s ease-out forwards; -} - -.partition-0 { - stroke: #00bfa5; -} - -.partition-1 { - stroke: #ff6f61; -} - -.partition-2 { - stroke: #ffb400; -} - -.partition-3 { - stroke: #3498db; -} - -.percentage { - fill: #333; - font-size: 0.7rem; - text-anchor: middle; -} - -.disk-container { - display: flex; - flex-direction: row; - gap: 20px; - background-color: #f5f7fa; - border: 2px solid #d1d9e6; - border-radius: 10px; - padding: 20px; - margin-top: 20px; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); - flex-wrap: wrap; - justify-content: center; - align-items: stretch; - margin-bottom: 20px; -} - -.table-container { - flex: 3; - display: flex; - justify-content: center; - align-items: center; -} - -table.mat-elevation-z8 { - width: 100%; - max-width: 800px; - background-color: white; - border-radius: 8px; - overflow: hidden; -} - -.mat-header-cell { - background-color: #d1d9e6 !important; - color: #333; - font-weight: bold; - text-align: center; -} - -.mat-cell { - text-align: center; -} - -.mat-chip { - font-weight: bold; - color: white; -} - -.charts-container { - flex: 2; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -.disk-usage { - background-color: white; - padding: 15px; - border-radius: 8px; - justify-self: center; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); - text-align: center; -} - -.chart { - display: flex; - justify-content: center; -} - -.back-button { - display: flex; - align-items: center; - gap: 5px; - margin-left: 0.5em; - background-color: #3f51b5; - color: white; - padding: 8px 18px 8px 8px; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 16px; - transition: transform 0.3s ease; - font-family: Roboto, "Helvetica Neue", sans-serif; -} - -.back-button:hover:not(:disabled) { - background-color: #485ac0d7; -} - -.back-button:disabled { - background-color: #ced0df; - cursor: not-allowed; -} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html deleted file mode 100644 index f0c7970..0000000 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html +++ /dev/null @@ -1,71 +0,0 @@ - - -
-
-

{{ 'clientDetailsTitle' | translate }} {{ clientData.name }}

-
- -
-
- - - -
-
-
-
-
{{ clientData?.property }}
-
{{ clientData?.value }}
-
-
-
-
-
{{ clientData?.property }}
-
{{ clientData?.value }}
-
-
-
-
-
-

Discos/Particiones

-
- -
-
- - - - - - - -
{{ column.header }} - - {{ column.cell(image) }} - - - - {{ (image.size / 1024).toFixed(2) }} GB - - -
-
- -
- -
- - - -

Disco {{ disk.diskNumber }}

-

Usado: {{ (disk.used).toFixed(2) }} GB ({{ disk.percentage }}%)

-

Total: {{ disk.total }} GB

-
-
-
-
-
- diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts deleted file mode 100644 index bf77392..0000000 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { DatePipe } from "@angular/common"; -import { MatTableDataSource } from "@angular/material/table"; -import { MatDialog } from "@angular/material/dialog"; -import { Router } from "@angular/router"; -import { ToastrService } from "ngx-toastr"; -import { ManageClientComponent } from "../../shared/clients/manage-client/manage-client.component"; -import { ConfigService } from '@services/config.service'; - -interface ClientInfo { - property: string; - value: any; -} - -@Component({ - selector: 'app-client-main-view', - templateUrl: './client-main-view.component.html', - styleUrl: './client-main-view.component.css' -}) -export class ClientMainViewComponent implements OnInit { - baseUrl: string; - @ViewChild('assistantContainer') assistantContainer!: ElementRef; - clientUuid: string; - clientData: any = {}; - isPartitionAssistantVisible: boolean = false; - isBootImageVisible: boolean = false; - isDiskUsageVisible: boolean = true; - dataSource = new MatTableDataSource(); - generalData: ClientInfo[] = []; - networkData: ClientInfo[] = []; - classroomData: ClientInfo[] = []; - diskUsageData: any[] = []; - partitions: any[] = []; - commands: any[] = []; - chartDisk: any[] = []; - view: [number, number] = [300, 200]; - showLegend: boolean = true; - - arrayCommands: any[] = [ - { name: 'Enceder', slug: 'power-on' }, - { name: 'Apagar', slug: 'power-off' }, - { name: 'Reiniciar', slug: 'reboot' }, - { name: 'Iniciar Sesión', slug: 'login' }, - { name: 'Crear imagen', slug: 'create-image' }, - { name: 'Clonar/desplegar imagen', slug: 'deploy-image' }, - { name: 'Eliminar Imagen Cache', slug: 'delete-image-cache' }, - { name: 'Particionar y Formatear', slug: 'partition' }, - { name: 'Inventario Software', slug: 'software-inventory' }, - { name: 'Inventario Hardware', slug: 'hardware-inventory' }, - { name: 'Ejecutar comando', slug: 'run-script' }, - ]; - - datePipe: DatePipe = new DatePipe('es-ES'); - columns = [ - { - columnDef: 'diskNumber', - header: 'Disco', - cell: (partition: any) => `${partition.diskNumber}`, - }, - { - columnDef: 'partitionNumber', - header: 'Particion', - cell: (partition: any) => `${partition.partitionNumber}` - }, - { - columnDef: 'description', - header: 'Sistema de ficheros', - cell: (partition: any) => `${partition.filesystem}` - }, - { - columnDef: 'size', - header: 'Tamaño', - cell: (partition: any) => `${(partition.size / 1024).toFixed(2)} GB` - }, - { - columnDef: 'memoryUsage', - header: 'Uso', - cell: (partition: any) => `${partition.memoryUsage} %` - }, - { - columnDef: 'operativeSystem', - header: 'SO', - cell: (partition: any) => `${partition.operativeSystem?.name}` - }, - ]; - displayedColumns = [...this.columns.map(column => column.columnDef)]; - isDiskUsageEmpty: boolean = true; - loading: boolean = true; - - constructor( - private http: HttpClient, - private dialog: MatDialog, - private configService: ConfigService, - private router: Router, - private toastService: ToastrService - ) { - this.baseUrl = this.configService.apiUrl; - const url = window.location.href; - const segments = url.split('/'); - this.clientUuid = segments[segments.length - 1]; - } - - ngOnInit() { - this.clientData = history.state.clientData['@id']; - this.loadClient(this.clientData) - this.loadCommands() - this.calculateDiskUsage(); - this.loading = false; - } - - - loadClient = (uuid: string) => { - this.http.get(`${this.baseUrl}${uuid}`).subscribe({ - next: data => { - this.clientData = data; - this.updateGeneralData(); - this.updateNetworkData(); - this.loadPartitions() - this.loading = false; - }, - error: error => { - console.error('Error al obtener el cliente:', error); - } - }); - } - - navigateToGroups() { - this.router.navigate(['/groups']); - } - - updateGeneralData() { - this.generalData = [ - { property: 'Nombre', value: this.clientData?.name || '' }, - { property: 'IP', value: this.clientData?.ip || '' }, - { property: 'MAC', value: this.clientData?.mac || '' }, - { property: 'Nº de serie', value: this.clientData?.serialNumber || '' }, - { property: 'Netiface', value: this.clientData?.netiface || this.clientData?.organizationalUnit?.networkSettings?.netiface || '' }, - { property: 'Perfil hardware', value: this.clientData?.hardwareProfile?.description || '' }, - ]; - } - - updateNetworkData() { - this.networkData = [ - { property: 'Padre', value: this.clientData?.organizationalUnit?.name || '' }, - { property: 'Pxe', value: this.clientData?.template?.name || '' }, - { property: 'Remote Pc', value: this.clientData.remotePc || '' }, - { property: 'Subred', value: this.clientData?.subnet || '' }, - { property: 'OGlive', value: this.clientData?.ogLive?.name || '' }, - { property: 'Repositorio', value: this.clientData?.repository?.name || '' }, - ]; - } - - calculateDiskUsage() { - const diskUsageMap = new Map(); - - this.partitions.forEach((partition: any) => { - const diskNumber = partition.diskNumber; - - if (!diskUsageMap.has(diskNumber)) { - diskUsageMap.set(diskNumber, { total: 0, used: 0, partitions: [] }); - } - - const diskData = diskUsageMap.get(diskNumber); - - if (partition.partitionNumber === 0) { - diskData!.total = Number((partition.size / 1024).toFixed(2)); - } else { - diskData!.used += Number((partition.size / 1024).toFixed(2)); - diskData!.partitions.push(partition); - } - }); - - this.chartDisk = Array.from(diskUsageMap.entries()).map(([diskNumber, { total, used, partitions }]) => { - const partitionData = partitions.map(partition => ({ - name: `Partición ${partition.partitionNumber}`, - value: Number((partition.size / 1024).toFixed(2)) - })); - - const freeSpace = total - used; - if (freeSpace > 0) { - partitionData.push({ - name: 'Espacio libre', - value: Number(freeSpace.toFixed(2)) - }); - } - - return { - diskNumber, - chartData: partitionData, - total, - used, - percentage: total > 0 ? Math.round((used / total) * 100) : 0 - }; - }); - - this.diskUsageData = this.chartDisk; - this.isDiskUsageEmpty = this.diskUsageData.length === 0; - } - - onEditClick(event: MouseEvent, uuid: string): void { - event.stopPropagation(); - const dialogRef = this.dialog.open(ManageClientComponent, { data: { uuid }, width: '900px' }); - dialogRef.afterClosed().subscribe(); - } - - loadPartitions(): void { - this.http.get(`${this.baseUrl}/partitions?client.id=${this.clientData?.id}&order[diskNumber, partitionNumber]=ASC`).subscribe({ - next: data => { - const filteredPartitions = data['hydra:member'].filter((partition: any) => partition.partitionNumber !== 0); - this.dataSource = filteredPartitions; - this.partitions = filteredPartitions; - this.calculateDiskUsage(); - }, - error: error => { - console.error('Error al obtener las particiones:', error); - } - }); - } - - - loadCommands(): void { - this.http.get(`${this.baseUrl}/commands?`).subscribe({ - next: data => { - this.commands = data['hydra:member']; - }, - error: error => { - console.error('Error al obtener las particiones:', error); - } - }); - } - - onCommandSelect(action: any): void { - if (action === 'partition') { - this.openPartitionAssistant(); - } - - if (action === 'create-image') { - this.openCreateImageAssistant(); - } - - if (action === 'deploy-image') { - this.openDeployImageAssistant(); - } - - if (action === 'reboot') { - this.rebootClient(); - } - - if (action === 'power-off') { - this.powerOffClient(); - } - - if (action === 'power-on') { - this.powerOnClient(); - } - } - - rebootClient(): void { - this.http.post(`${this.baseUrl}/clients/server/${this.clientData.uuid}/reboot`, {}).subscribe( - response => { - this.toastService.success('Cliente actualizado correctamente'); - }, - error => { - this.toastService.error('Error de conexión con el cliente'); - } - ); - } - - powerOnClient(): void { - const payload = { - client: this.clientData['@id'] - } - - this.http.post(`${this.baseUrl}${this.clientData.repository['@id']}/wol`, payload).subscribe( - response => { - this.toastService.success('Cliente actualizado correctamente'); - }, - error => { - this.toastService.error('Error de conexión con el cliente'); - } - ); - } - - powerOffClient(): void { - this.http.post(`${this.baseUrl}/clients/server/${this.clientData.uuid}/power-off`, {}).subscribe( - response => { - this.toastService.success('Cliente actualizado correctamente'); - }, - error => { - this.toastService.error('Error de conexión con el cliente'); - } - ); - } - - openPartitionAssistant(): void { - this.router.navigate([`/clients/${this.clientData.uuid}/partition-assistant`]).then(r => { - console.log('navigated', r); - }); - } - - openCreateImageAssistant(): void { - this.router.navigate([`/clients/${this.clientData.uuid}/create-image`]).then(r => { - console.log('navigated', r); - }); - } - - openDeployImageAssistant(): void { - this.router.navigate([`/clients/deploy-image`]).then(r => { - console.log('navigated', r); - }); - } -} diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 0c537b1..875c02f 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -611,7 +611,7 @@ export class GroupsComponent implements OnInit, OnDestroy { onShowClientDetail(event: MouseEvent, client: Client): void { event.stopPropagation(); this.dialog.open(ClientDetailsComponent, { - width: '600px', + width: '900px', data: { clientData: client }, }) } diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css index b408c18..3e0ba22 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css @@ -4,19 +4,19 @@ background-color: #fff; display: flex; flex-direction: column; + padding: 1em 4em 2em 4em; } -.section-header { - margin-bottom: 8px; +.title { + margin-bottom: 1.5em; } .client-info-card { - background-color: #e4e4e4; + background-color: #f1f1f1; padding: 24px; border-radius: 12px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); - margin-bottom: 2em; - margin-top: 1.5em; + margin-bottom: 60px; } .info-columns { @@ -58,8 +58,7 @@ overflow-x: auto; border-radius: 8px; background: #fff; - padding: 16px; - box-shadow: 0 2px 6px rgba(0,0,0,0.05); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); } .mat-elevation-z8 { @@ -73,16 +72,25 @@ gap: 24px; } +.charts-container.single-disk { + justify-content: center; +} + +.charts-container.single-disk .disk-usage { + max-width: 400px; + flex: 0 1 auto; +} + .disk-usage { flex: 1 1 300px; padding: 16px; background: #f9f9f9; border-radius: 12px; - box-shadow: 0 2px 6px rgba(0,0,0,0.05); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); } .chart { width: 100%; height: 200px; margin-bottom: 12px; -} +} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html index 1238980..4e1e5e0 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html @@ -48,7 +48,7 @@
-
+
@@ -59,6 +59,7 @@
+
\ No newline at end of file diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index 6bc649a..daaae76 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -272,7 +272,7 @@ "classroomOption": "Classroom", "clientsGroupOption": "PC Groups", "roomMapOption": "Classroom map", - "clientDetailsTitle": "Client details", + "clientDetailsTitle": "Details of", "commandsButton": "Commands", "networkPropertiesTab": "Network properties", "disksPartitionsTitle": "Disks/Partitions", diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index fb6d793..d02bddb 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -276,7 +276,7 @@ "classroomOption": "Aula", "clientsGroupOption": "Grupos de PCs", "roomMapOption": "Plano de aula", - "clientDetailsTitle": "Datos de cliente", + "clientDetailsTitle": "Datos de", "commandsButton": "Comandos", "excludeParentChanges": "Excluir cambios de las unidades organizativas superiores", "networkPropertiesTab": "Propiedades de red", From 256b3ba7889569ff3bdde785fd6623ef99fd0e6a Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 23 Apr 2025 14:56:17 +0200 Subject: [PATCH 09/47] refs #1931 Enhance ClientDetailsComponent layout and styling: increase dialog width, adjust chart dimensions, and improve disk layout for better data presentation --- .../app/components/groups/groups.component.ts | 2 +- .../client-details.component.css | 25 +++++--- .../client-details.component.html | 61 ++++++++++--------- .../client-details.component.ts | 5 +- 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 875c02f..2fba48b 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -611,7 +611,7 @@ export class GroupsComponent implements OnInit, OnDestroy { onShowClientDetail(event: MouseEvent, client: Client): void { event.stopPropagation(); this.dialog.open(ClientDetailsComponent, { - width: '900px', + width: '1200px', data: { clientData: client }, }) } diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css index 3e0ba22..8f3de76 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css @@ -54,22 +54,32 @@ gap: 32px; } +.disk-layout { + display: flex; + flex-direction: row; + gap: 32px; + flex-wrap: wrap; +} + .table-container { + flex: 1 1 500px; overflow-x: auto; border-radius: 8px; background: #fff; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); } -.mat-elevation-z8 { - width: 100%; - border-radius: 8px; -} - .charts-container { display: flex; flex-wrap: wrap; gap: 24px; + flex: 1 1 300px; + justify-content: flex-start; +} + +.mat-elevation-z8 { + width: 100%; + border-radius: 8px; } .charts-container.single-disk { @@ -82,7 +92,8 @@ } .disk-usage { - flex: 1 1 300px; + flex: 1 1 260px; + min-width: 240px; padding: 16px; background: #f9f9f9; border-radius: 12px; @@ -91,6 +102,6 @@ .chart { width: 100%; - height: 200px; + height: 160px; margin-bottom: 12px; } \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html index 4e1e5e0..9e8edc0 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html @@ -28,38 +28,39 @@
-
- - - - +
+
+
{{ column.header }} - - {{ column.cell(image) }} - - - - {{ (image.size / 1024).toFixed(2) }} GB - - -
+ + + + + + +
{{ column.header }} + + {{ column.cell(image) }} + + + + {{ (image.size / 1024).toFixed(2) }} GB + + +
+
+ +
+ +
+ + +

Disco {{ disk.diskNumber }}

+

Usado: {{ (disk.used).toFixed(2) }} GB ({{ disk.percentage }}%)

+

Total: {{ disk.total }} GB

+
- - - +
- -
- -
- - -

Disco {{ disk.diskNumber }}

-

Usado: {{ (disk.used).toFixed(2) }} GB ({{ disk.percentage }}%)

-

Total: {{ disk.total }} GB

-
-
-
-
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts index f1961ed..919e84e 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts @@ -35,7 +35,7 @@ export class ClientDetailsComponent { partitions: any[] = []; commands: any[] = []; chartDisk: any[] = []; - view: [number, number] = [300, 200]; + view: [number, number] = [260, 160]; showLegend: boolean = true; arrayCommands: any[] = [ @@ -115,8 +115,7 @@ export class ClientDetailsComponent { console.error('No se recibieron datos del cliente.'); } } - - + loadClient = (uuid: string) => { this.http.get(`${this.baseUrl}${uuid}`).subscribe({ next: data => { From e8b713ea0967ccb34f36ef571f6da8926fc4d5c4 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 23 Apr 2025 15:03:59 +0200 Subject: [PATCH 10/47] refs #1931 Refactor table styles in ClientDetailsComponent: remove border-radius and box-shadow for a cleaner look --- .../shared/client-details/client-details.component.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css index 8f3de76..902cb8f 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css @@ -64,9 +64,6 @@ .table-container { flex: 1 1 500px; overflow-x: auto; - border-radius: 8px; - background: #fff; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); } .charts-container { @@ -82,6 +79,12 @@ border-radius: 8px; } +table { + border: 2px solid #f3f3f3; + border-radius: 0px !important; + box-shadow: none; +} + .charts-container.single-disk { justify-content: center; } From 9582ce338cc953dd88b7898d8697937355c8f22a Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Thu, 24 Apr 2025 14:08:42 +0200 Subject: [PATCH 11/47] refs #1928 Add runScriptContext input to ExecuteCommandComponent and update related components for improved script execution context handling --- .../execute-command.component.ts | 70 ++++++++++--------- .../run-script-assistant.component.html | 2 +- .../run-script-assistant.component.ts | 4 ++ .../components/groups/groups.component.html | 21 ++++-- .../app/components/groups/groups.component.ts | 18 +++++ 5 files changed, 74 insertions(+), 41 deletions(-) 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 5aff89e..cd63c1e 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 @@ -16,6 +16,7 @@ export class ExecuteCommandComponent implements OnInit { @Input() buttonText: string = 'Ejecutar Comandos'; @Input() icon: string = 'terminal'; @Input() disabled: boolean = false; + @Input() runScriptContext: string = ''; baseUrl: string; loading: boolean = true; @@ -54,40 +55,40 @@ export class ExecuteCommandComponent implements OnInit { } private updateCommandStates(): void { - let states: string[] = []; + // let states: string[] = []; - if (this.clientData.length > 0) { - states = this.clientData.map(client => client.status); - } else if (this.clientState) { - states = [this.clientState]; - } + // if (this.clientData.length > 0) { + // states = this.clientData.map(client => client.status); + // } else if (this.clientState) { + // states = [this.clientState]; + // } - const allOffOrDisconnected = states.every(state => state === 'off' || state === 'disconnected'); - const allSameState = states.every(state => state === states[0]); - const multipleClients = this.clientData.length > 1; + // const allOffOrDisconnected = states.every(state => state === 'off' || state === 'disconnected'); + // const allSameState = states.every(state => state === states[0]); + // const multipleClients = this.clientData.length > 1; - this.arrayCommands = this.arrayCommands.map(command => { - if (allOffOrDisconnected) { - command.disabled = command.slug !== 'power-on'; - } else if (allSameState) { - if (states[0] === 'off' || states[0] === 'disconnected') { - command.disabled = command.slug !== 'power-on'; - } else { - command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'partition', 'run-script'].includes(command.slug); - } - } else { - if (command.slug === 'create-image') { - command.disabled = multipleClients; - } else if ( - ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug) - ) { - command.disabled = false; - } else { - command.disabled = true; - } - } - return command; - }); + // this.arrayCommands = this.arrayCommands.map(command => { + // if (allOffOrDisconnected) { + // command.disabled = command.slug !== 'power-on'; + // } else if (allSameState) { + // if (states[0] === 'off' || states[0] === 'disconnected') { + // command.disabled = command.slug !== 'power-on'; + // } else { + // command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'partition', 'run-script'].includes(command.slug); + // } + // } else { + // if (command.slug === 'create-image') { + // command.disabled = multipleClients; + // } else if ( + // ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug) + // ) { + // command.disabled = false; + // } else { + // command.disabled = true; + // } + // } + // return command; + // }); } onCommandSelect(action: any): void { @@ -228,9 +229,12 @@ export class ExecuteCommandComponent implements OnInit { })); this.router.navigate(['/clients/run-script'], { - queryParams: { clientData: JSON.stringify(clientDataToSend) } + queryParams: { + clientData: JSON.stringify(clientDataToSend) , + runScriptContext: this.runScriptContext + } }).then(() => { - console.log('Navigated to run script with data:', clientDataToSend); + console.log('Navigated to run script with data:', clientDataToSend, 'runScriptContext:', this.runScriptContext); }); } } diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html index e41eea9..0ad4fbf 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html @@ -3,7 +3,7 @@

- {{ 'runScript' | translate }} + {{ 'runScript' | translate }} {{this.runScriptContext}}

diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts index 83bb0d0..23e599a 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts @@ -32,6 +32,7 @@ export class RunScriptAssistantComponent { newScript: string = ''; selection = new SelectionModel(true, []); parameterNames: string[] = Object.keys(this.parameters); + runScriptContext: string = ''; constructor( private http: HttpClient, @@ -46,6 +47,9 @@ export class RunScriptAssistantComponent { if (params['clientData']) { this.clientData = JSON.parse(params['clientData']); } + if (params['runScriptContext']) { + this.runScriptContext = params['runScriptContext']; + } }); this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null; this.clientData.forEach((client: { selected: boolean; status: string}) => { diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index b15de2e..7fd5771 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -199,7 +199,9 @@ {{ 'delete' | translate }} + [buttonText]="'Ejecutar comandos'" [icon]="'terminal'" + [disabled]="!((selectedNode?.clients ?? []).length > 0)" [runScriptContext]="selectedNode?.name || ''" + [runScriptContext]="getRunScriptContext(selectedNode?.clients || [])">
@@ -217,7 +219,8 @@
+ [buttonText]="'Ejecutar comandos'" [disabled]="selection.selected.length === 0" + [runScriptContext]="getRunScriptContext(selection.selected)"> @@ -259,8 +262,10 @@ {{ client.mac }}
- + - +
-
+
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 2fba48b..99607e0 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -828,4 +828,22 @@ export class GroupsComponent implements OnInit, OnDestroy { clientSearchStatusInput.value = null; this.fetchClientsForNode(this.selectedNode); } + + getRunScriptContext(clientData: any[]): string { + const selectedClientNames = clientData.map(client => client.name); + + if (clientData.length === 1) { + return selectedClientNames[0]; + } else if ( + clientData.length === this.selectedClients.data.length && + selectedClientNames.every(name => this.selectedClients.data.some(c => c.name === name)) + ) { + return this.selectedNode?.name || ''; + } else if (clientData.length > 1) { + return selectedClientNames.join(', '); + } else if (this.selectedNode?.name && clientData.length === 0) { + return this.selectedNode.name; + } + return ''; + } } From 2bc17e8b568c0254df5a36a572449416c2f6756c Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Thu, 24 Apr 2025 14:09:54 +0200 Subject: [PATCH 12/47] Refactor updateCommandStates method in ExecuteCommandComponent: restore and optimize command state management logic for improved functionality --- .../execute-command.component.ts | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) 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 cd63c1e..e459e66 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 @@ -55,40 +55,40 @@ export class ExecuteCommandComponent implements OnInit { } private updateCommandStates(): void { - // let states: string[] = []; + let states: string[] = []; - // if (this.clientData.length > 0) { - // states = this.clientData.map(client => client.status); - // } else if (this.clientState) { - // states = [this.clientState]; - // } + if (this.clientData.length > 0) { + states = this.clientData.map(client => client.status); + } else if (this.clientState) { + states = [this.clientState]; + } - // const allOffOrDisconnected = states.every(state => state === 'off' || state === 'disconnected'); - // const allSameState = states.every(state => state === states[0]); - // const multipleClients = this.clientData.length > 1; + const allOffOrDisconnected = states.every(state => state === 'off' || state === 'disconnected'); + const allSameState = states.every(state => state === states[0]); + const multipleClients = this.clientData.length > 1; - // this.arrayCommands = this.arrayCommands.map(command => { - // if (allOffOrDisconnected) { - // command.disabled = command.slug !== 'power-on'; - // } else if (allSameState) { - // if (states[0] === 'off' || states[0] === 'disconnected') { - // command.disabled = command.slug !== 'power-on'; - // } else { - // command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'partition', 'run-script'].includes(command.slug); - // } - // } else { - // if (command.slug === 'create-image') { - // command.disabled = multipleClients; - // } else if ( - // ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug) - // ) { - // command.disabled = false; - // } else { - // command.disabled = true; - // } - // } - // return command; - // }); + this.arrayCommands = this.arrayCommands.map(command => { + if (allOffOrDisconnected) { + command.disabled = command.slug !== 'power-on'; + } else if (allSameState) { + if (states[0] === 'off' || states[0] === 'disconnected') { + command.disabled = command.slug !== 'power-on'; + } else { + command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'partition', 'run-script'].includes(command.slug); + } + } else { + if (command.slug === 'create-image') { + command.disabled = multipleClients; + } else if ( + ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug) + ) { + command.disabled = false; + } else { + command.disabled = true; + } + } + return command; + }); } onCommandSelect(action: any): void { From 6fd04fb46e630631d8a34ecfe95e4cdf29262d9e Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 25 Apr 2025 14:02:15 +0200 Subject: [PATCH 13/47] refs #1889 Add PartitionTypeOrganizatorComponent: implement partition management modal and integrate into GroupsComponent --- ogWebconsole/src/app/app.module.ts | 4 +- .../components/groups/groups.component.html | 4 ++ .../app/components/groups/groups.component.ts | 17 ++++++- .../partition-type-organizator.component.css | 46 +++++++++++++++++++ .../partition-type-organizator.component.html | 42 +++++++++++++++++ ...rtition-type-organizator.component.spec.ts | 42 +++++++++++++++++ .../partition-type-organizator.component.ts | 29 ++++++++++++ ogWebconsole/src/locale/en.json | 3 +- ogWebconsole/src/locale/es.json | 3 +- 9 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.css create mode 100644 ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html create mode 100644 ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.spec.ts create mode 100644 ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index a356d2e..0203339 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -143,6 +143,7 @@ import { EditImageComponent } from './components/repositories/edit-image/edit-im import { ShowGitImagesComponent } from './components/repositories/show-git-images/show-git-images.component'; import { RenameImageComponent } from './components/repositories/rename-image/rename-image.component'; import { ClientDetailsComponent } from './components/groups/shared/client-details/client-details.component'; +import { PartitionTypeOrganizatorComponent } from './components/groups/shared/partition-type-organizator/partition-type-organizator.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); @@ -243,7 +244,8 @@ registerLocaleData(localeEs, 'es-ES'); EditImageComponent, ShowGitImagesComponent, RenameImageComponent, - ClientDetailsComponent + ClientDetailsComponent, + PartitionTypeOrganizatorComponent ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index 7fd5771..5415b20 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -198,6 +198,10 @@ delete {{ 'delete' | translate }} + client.name); - + if (clientData.length === 1) { return selectedClientNames[0]; } else if ( @@ -846,4 +847,18 @@ export class GroupsComponent implements OnInit, OnDestroy { } return ''; } + + openPartitionTypeModal(event: MouseEvent, node: TreeNode | null = null): void { + event.stopPropagation(); + + const simplifiedClientsData = node?.clients?.map((client: any) => ({ + name: client.name, + partitions: client.partitions + })); + + this.dialog.open(PartitionTypeOrganizatorComponent, { + width: '1200px', + data: simplifiedClientsData + }); + } } diff --git a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.css b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.css new file mode 100644 index 0000000..aa2921c --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.css @@ -0,0 +1,46 @@ +.modal-content { + max-height: 80vh; + overflow-y: auto; + background-color: #fff; + display: flex; + flex-direction: column; + padding: 1em 4em 2em 4em; + box-sizing: border-box; +} + +.client-section { + margin-bottom: 2em; +} + +.client-title { + font-size: 1.5em; + font-weight: 500; + color: #3f51b5; + margin-bottom: 1em; + border-bottom: 2px solid #e0e0e0; + padding-bottom: 0.25em; +} + +.partition-table { + width: 100%; + border-spacing: 0; + border-collapse: collapse; +} + +.partition-table th, +.partition-table td { + padding: 0.75em 1em; + text-align: left; + font-size: 0.95em; + border-bottom: 1px solid #ddd; +} + +.partition-table th { + background-color: #f5f5f5; + font-weight: 500; + color: #444; +} + +.partition-table tr:hover td { + background-color: #f9f9f9; +} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html new file mode 100644 index 0000000..9804ff9 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html @@ -0,0 +1,42 @@ + +
+

{{ client.name }}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Disk{{ element.diskNumber }}Partition #{{ element.partitionNumber }}Code{{ element.partitionCode }}Size{{ element.size }}FS{{ element.filesystem }}Memory{{ element.memoryUsage }}
+
+
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.spec.ts b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.spec.ts new file mode 100644 index 0000000..aba862d --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.spec.ts @@ -0,0 +1,42 @@ +import { TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { PartitionTypeOrganizatorComponent } from './partition-type-organizator.component'; +import { MatTableModule } from '@angular/material/table'; + +describe('PartitionTypeOrganizatorComponent', () => { + let component: PartitionTypeOrganizatorComponent; + let fixture: any; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [PartitionTypeOrganizatorComponent], + imports: [ + MatDialogModule, + MatTableModule + ], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: [ + { + name: 'Client 1', + partitions: [ + { diskNumber: 1, partitionNumber: 1, partitionCode: 'EXT4', size: 1024, filesystem: 'ext4', memoryUsage: 50 }, + { diskNumber: 1, partitionNumber: 2, partitionCode: 'NTFS', size: 2048, filesystem: 'ntfs', memoryUsage: 75 } + ] + } + ] + } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(PartitionTypeOrganizatorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts new file mode 100644 index 0000000..d6e1471 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts @@ -0,0 +1,29 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; + +@Component({ + selector: 'app-partition-type-organizator', + templateUrl: './partition-type-organizator.component.html', + styleUrl: './partition-type-organizator.component.css' +}) +export class PartitionTypeOrganizatorComponent implements OnInit { + constructor(@Inject(MAT_DIALOG_DATA) public data: any) { } + + public simplifiedData: any[] = []; + displayedColumns: string[] = ['diskNumber', 'partitionNumber', 'partitionCode', 'size', 'filesystem', 'memoryUsage']; + + ngOnInit(): void { + this.simplifiedData = this.data.map((client: any) => ({ + name: client.name, + partitions: client.partitions.map((p: any) => ({ + diskNumber: p.diskNumber, + partitionNumber: p.partitionNumber, + partitionCode: p.partitionCode, + size: p.size, + filesystem: p.filesystem, + memoryUsage: p.memoryUsage + })) + })); + } + +} diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index daaae76..95a731c 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -482,5 +482,6 @@ "processes": "Processes", "usedPercentageLabel": "Used", "errorLoadingData": "Error fetching data. Service not available", - "repositoryTitleStep": "On this screen you can manage image repositories." + "repositoryTitleStep": "On this screen you can manage image repositories.", + "partitions": "Particiones" } diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index d02bddb..28a8640 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -484,5 +484,6 @@ "processes": "Procesos", "usedPercentageLabel": "Usado", "errorLoadingData": "Error al cargar los datos. Servicio inactivo", - "repositoryTitleStep": "En esta pantalla se pueden gestionar los repositorios de imágenes." + "repositoryTitleStep": "En esta pantalla se pueden gestionar los repositorios de imágenes.", + "partitions": "Particiones" } From 34ea12fc503dc476fe1ee511d915207a71567f1f Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 25 Apr 2025 14:23:39 +0200 Subject: [PATCH 14/47] Translate table headers in PartitionTypeOrganizatorComponent to Spanish for better localization --- .../partition-type-organizator.component.html | 12 ++++++------ .../partition-type-organizator.component.ts | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html index 9804ff9..6f25123 100644 --- a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html @@ -5,32 +5,32 @@ - + - + - + - + - + - + diff --git a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts index d6e1471..2f47bd9 100644 --- a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts @@ -13,6 +13,8 @@ export class PartitionTypeOrganizatorComponent implements OnInit { displayedColumns: string[] = ['diskNumber', 'partitionNumber', 'partitionCode', 'size', 'filesystem', 'memoryUsage']; ngOnInit(): void { + console.log('Data recibida en modal:', this.data); + this.simplifiedData = this.data.map((client: any) => ({ name: client.name, partitions: client.partitions.map((p: any) => ({ @@ -25,5 +27,6 @@ export class PartitionTypeOrganizatorComponent implements OnInit { })) })); } + } From ed073deb5d1e25cb9135b05ce2c8ca045b63a80c Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Tue, 29 Apr 2025 14:05:43 +0200 Subject: [PATCH 15/47] Refactor runScriptContext handling in ExecuteCommandComponent and RunScriptAssistantComponent for improved data management; update related HTML to reflect changes. --- .../execute-command.component.ts | 8 ++- .../run-script-assistant.component.html | 2 +- .../run-script-assistant.component.ts | 49 ++++++++++++++----- .../app/components/groups/groups.component.ts | 18 +++---- .../partition-type-organizator.component.ts | 4 -- 5 files changed, 50 insertions(+), 31 deletions(-) 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 e459e66..2e64e69 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 @@ -10,13 +10,13 @@ import { ConfigService } from '@services/config.service'; styleUrls: ['./execute-command.component.css'] }) export class ExecuteCommandComponent implements OnInit { + @Input() runScriptContext: any = null; @Input() clientState: string = 'off'; @Input() clientData: any[] = []; @Input() buttonType: 'icon' | 'text' | 'menu-item' = 'icon'; @Input() buttonText: string = 'Ejecutar Comandos'; @Input() icon: string = 'terminal'; @Input() disabled: boolean = false; - @Input() runScriptContext: string = ''; baseUrl: string; loading: boolean = true; @@ -231,10 +231,8 @@ export class ExecuteCommandComponent implements OnInit { this.router.navigate(['/clients/run-script'], { queryParams: { clientData: JSON.stringify(clientDataToSend) , - runScriptContext: this.runScriptContext + runScriptContext: JSON.stringify(this.runScriptContext) } - }).then(() => { - console.log('Navigated to run script with data:', clientDataToSend, 'runScriptContext:', this.runScriptContext); - }); + }) } } diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html index 0ad4fbf..a581a6a 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html @@ -3,7 +3,7 @@

- {{ 'runScript' | translate }} {{this.runScriptContext}} + {{ 'runScript' | translate }} {{runScriptTitle}}

diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts index 23e599a..bd93e49 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts @@ -1,11 +1,11 @@ -import {Component, EventEmitter, Output} from '@angular/core'; -import {SelectionModel} from "@angular/cdk/collections"; -import {HttpClient} from "@angular/common/http"; -import {ToastrService} from "ngx-toastr"; -import {ConfigService} from "@services/config.service"; -import {ActivatedRoute, Router} from "@angular/router"; -import {SaveScriptComponent} from "./save-script/save-script.component"; -import {MatDialog} from "@angular/material/dialog"; +import { Component, EventEmitter, Output } from '@angular/core'; +import { SelectionModel } from "@angular/cdk/collections"; +import { HttpClient } from "@angular/common/http"; +import { ToastrService } from "ngx-toastr"; +import { ConfigService } from "@services/config.service"; +import { ActivatedRoute, Router } from "@angular/router"; +import { SaveScriptComponent } from "./save-script/save-script.component"; +import { MatDialog } from "@angular/material/dialog"; @Component({ selector: 'app-run-script-assistant', @@ -32,7 +32,7 @@ export class RunScriptAssistantComponent { newScript: string = ''; selection = new SelectionModel(true, []); parameterNames: string[] = Object.keys(this.parameters); - runScriptContext: string = ''; + runScriptContext: any = null; constructor( private http: HttpClient, @@ -52,7 +52,7 @@ export class RunScriptAssistantComponent { } }); this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null; - this.clientData.forEach((client: { selected: boolean; status: string}) => { + this.clientData.forEach((client: { selected: boolean; status: string }) => { if (client.status === 'og-live') { client.selected = true; } @@ -63,6 +63,31 @@ export class RunScriptAssistantComponent { this.loadScripts() } + ngOnInit(): void { + this.route.queryParams.subscribe(params => { + this.runScriptContext = params['runScriptContext'] ? JSON.parse(params['runScriptContext']) : null; + this.clientData = params['clientData'] ? JSON.parse(params['clientData']) : []; + }); + } + + get runScriptTitle(): string { + const ctx = this.runScriptContext; + if (!ctx) { + return ''; + } + // Si es un array de clientes + if (Array.isArray(ctx)) { + return ctx.map(c => c.name).join(', '); + } + // Si es un objeto con propiedad name + if (typeof ctx === 'object' && 'name' in ctx) { + return ctx.name; + } + // Si es un string plano + return String(ctx); + } + + loadScripts(): void { this.loading = true; @@ -118,7 +143,7 @@ export class RunScriptAssistantComponent { } return client.partitions - .map((p: { partitionNumber: any; size: any; filesystem: any }) => `#${p.partitionNumber} ${p.filesystem} - ${p.size / 1024 }GB`) + .map((p: { partitionNumber: any; size: any; filesystem: any }) => `#${p.partitionNumber} ${p.filesystem} - ${p.size / 1024}GB`) .join('\n'); } @@ -164,7 +189,7 @@ export class RunScriptAssistantComponent { this.http.post(`${this.baseUrl}/commands/run-script`, { clients: this.selectedClients.map((client: any) => client.uuid), - script: this.commandType === 'existing' ? this.scriptContent : this.newScript, + script: this.commandType === 'existing' ? this.scriptContent : this.newScript, }).subscribe( response => { this.toastService.success('Script ejecutado correctamente'); diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 677b65e..ba768cc 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -830,24 +830,24 @@ export class GroupsComponent implements OnInit, OnDestroy { this.fetchClientsForNode(this.selectedNode); } - getRunScriptContext(clientData: any[]): string { + getRunScriptContext(clientData: any[]): any { const selectedClientNames = clientData.map(client => client.name); - + if (clientData.length === 1) { - return selectedClientNames[0]; + return clientData[0]; // devuelve el objeto cliente completo } else if ( clientData.length === this.selectedClients.data.length && selectedClientNames.every(name => this.selectedClients.data.some(c => c.name === name)) ) { - return this.selectedNode?.name || ''; + return this.selectedNode || null; // devuelve el nodo completo } else if (clientData.length > 1) { - return selectedClientNames.join(', '); - } else if (this.selectedNode?.name && clientData.length === 0) { - return this.selectedNode.name; + return clientData; // devuelve array de objetos cliente + } else if (this.selectedNode && clientData.length === 0) { + return this.selectedNode; } - return ''; + return null; } - + openPartitionTypeModal(event: MouseEvent, node: TreeNode | null = null): void { event.stopPropagation(); diff --git a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts index 2f47bd9..b9eb8f5 100644 --- a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts @@ -13,8 +13,6 @@ export class PartitionTypeOrganizatorComponent implements OnInit { displayedColumns: string[] = ['diskNumber', 'partitionNumber', 'partitionCode', 'size', 'filesystem', 'memoryUsage']; ngOnInit(): void { - console.log('Data recibida en modal:', this.data); - this.simplifiedData = this.data.map((client: any) => ({ name: client.name, partitions: client.partitions.map((p: any) => ({ @@ -27,6 +25,4 @@ export class PartitionTypeOrganizatorComponent implements OnInit { })) })); } - - } From bf100943a0ca1764766982ae9b401899d1f7dd13 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 30 Apr 2025 13:15:06 +0200 Subject: [PATCH 16/47] refs #1906. Updated UX to CommandTask --- ogWebconsole/src/app/app.module.ts | 12 +- .../commands-task.component.html | 42 +- .../commands-task/commands-task.component.ts | 93 ++++- .../create-task-schedule.component.css | 128 +++++++ .../create-task-schedule.component.html | 87 +++++ .../create-task-schedule.component.spec.ts | 100 +++++ .../create-task-schedule.component.ts | 196 ++++++++++ .../create-task-script.component.css | 284 ++++++++++++++ .../create-task-script.component.html | 65 ++++ .../create-task-script.component.spec.ts | 89 +++++ .../create-task-script.component.ts | 137 +++++++ .../create-task/create-task.component.css | 46 ++- .../create-task/create-task.component.html | 111 ++---- .../create-task/create-task.component.ts | 283 +++++++------- .../show-task-schedule.component.css | 89 +++++ .../show-task-schedule.component.html | 100 +++++ .../show-task-schedule.component.spec.ts | 86 +++++ .../show-task-schedule.component.ts | 107 ++++++ .../show-task-script.component.css | 89 +++++ .../show-task-script.component.html | 70 ++++ .../show-task-script.component.spec.ts | 86 +++++ .../show-task-script.component.ts | 104 +++++ .../view-parameters-modal.component.css | 0 .../view-parameters-modal.component.html | 7 + .../view-parameters-modal.component.spec.ts | 69 ++++ .../view-parameters-modal.component.ts | 18 + .../execute-command.component.ts | 16 +- .../client-main-view.component.css | 358 ++++++++++++++++++ .../client-main-view.component.html | 72 ++++ .../deploy-image/deploy-image.component.html | 11 + .../deploy-image/deploy-image.component.ts | 77 +++- .../partition-assistant.component.css | 12 +- .../partition-assistant.component.html | 19 +- .../partition-assistant.component.ts | 84 +++- .../run-script-assistant.component.css | 11 + .../run-script-assistant.component.html | 45 ++- .../run-script-assistant.component.spec.ts | 4 + .../run-script-assistant.component.ts | 45 ++- .../main-layout/main-layout.component.css | 3 +- ogWebconsole/src/locale/en.json | 8 +- ogWebconsole/src/locale/es.json | 8 +- 41 files changed, 2929 insertions(+), 342 deletions(-) create mode 100644 ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.css create mode 100644 ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.html create mode 100644 ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.spec.ts create mode 100644 ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.ts create mode 100644 ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.css create mode 100644 ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.html create mode 100644 ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.spec.ts create mode 100644 ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.ts create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.css create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.html create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.spec.ts create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.ts create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.css create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.html create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.spec.ts create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.ts create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.css create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.html create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.spec.ts create mode 100644 ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.ts create mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css create mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 0203339..13cef17 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -144,6 +144,11 @@ import { ShowGitImagesComponent } from './components/repositories/show-git-image import { RenameImageComponent } from './components/repositories/rename-image/rename-image.component'; import { ClientDetailsComponent } from './components/groups/shared/client-details/client-details.component'; import { PartitionTypeOrganizatorComponent } from './components/groups/shared/partition-type-organizator/partition-type-organizator.component'; +import { CreateTaskScheduleComponent } from './components/commands/commands-task/create-task-schedule/create-task-schedule.component'; +import { ShowTaskScheduleComponent } from './components/commands/commands-task/show-task-schedule/show-task-schedule.component'; +import { ShowTaskScriptComponent } from './components/commands/commands-task/show-task-script/show-task-script.component'; +import { CreateTaskScriptComponent } from './components/commands/commands-task/create-task-script/create-task-script.component'; +import { ViewParametersModalComponent } from './components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); @@ -245,7 +250,12 @@ registerLocaleData(localeEs, 'es-ES'); ShowGitImagesComponent, RenameImageComponent, ClientDetailsComponent, - PartitionTypeOrganizatorComponent + PartitionTypeOrganizatorComponent, + CreateTaskScheduleComponent, + ShowTaskScheduleComponent, + ShowTaskScriptComponent, + CreateTaskScriptComponent, + ViewParametersModalComponent ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/commands/commands-task/commands-task.component.html b/ogWebconsole/src/app/components/commands/commands-task/commands-task.component.html index 86f3f8e..39666d8 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/commands-task.component.html +++ b/ogWebconsole/src/app/components/commands/commands-task/commands-task.component.html @@ -1,3 +1,5 @@ + +
DiskDisco {{ element.diskNumber }} Partition #Partición {{ element.partitionNumber }} CodeTipo {{ element.partitionCode }} SizeTamaño {{ element.size }} FSFyle System {{ element.filesystem }} MemoryMemoria {{ element.memoryUsage }}
- - - - + + + - - - - - - - - - - - - - - - - + + + + + - + diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index ba768cc..16a8dc7 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -583,11 +583,11 @@ export class GroupsComponent implements OnInit, OnDestroy { onRoomMap(room: TreeNode | null): void { if (!room || !room['@id']) return; this.subscriptions.add( - this.http.get<{ clients: Client[] }>(`${this.baseUrl}${room['@id']}`).subscribe( + this.http.get(`${this.baseUrl}/clients?organizationalUnit.id=${room.id}`, { }).subscribe( (response) => { this.dialog.open(ClassroomViewDialogComponent, { width: '90vw', - data: { clients: response.clients }, + data: { clients: response['hydra:member'] }, }); }, (error) => { @@ -612,7 +612,7 @@ export class GroupsComponent implements OnInit, OnDestroy { onShowClientDetail(event: MouseEvent, client: Client): void { event.stopPropagation(); this.dialog.open(ClientDetailsComponent, { - width: '1200px', + width: '1300px', data: { clientData: client }, }) } @@ -850,12 +850,12 @@ export class GroupsComponent implements OnInit, OnDestroy { openPartitionTypeModal(event: MouseEvent, node: TreeNode | null = null): void { event.stopPropagation(); - + const simplifiedClientsData = node?.clients?.map((client: any) => ({ name: client.name, partitions: client.partitions })); - + this.dialog.open(PartitionTypeOrganizatorComponent, { width: '1200px', data: simplifiedClientsData From f6dbd6dad99b75d0ec0f53fac8ecd75ff5137a73 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 2 May 2025 13:24:06 +0200 Subject: [PATCH 20/47] refs #1941 Refactor partition type organizer: enhance data structure and improve client grouping logic --- .../app/components/groups/groups.component.ts | 10 +-- .../partition-type-organizator.component.css | 13 ++- .../partition-type-organizator.component.html | 33 ++++--- .../partition-type-organizator.component.ts | 87 +++++++++++++++++-- 4 files changed, 113 insertions(+), 30 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 16a8dc7..3e07050 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -583,11 +583,11 @@ export class GroupsComponent implements OnInit, OnDestroy { onRoomMap(room: TreeNode | null): void { if (!room || !room['@id']) return; this.subscriptions.add( - this.http.get(`${this.baseUrl}/clients?organizationalUnit.id=${room.id}`, { }).subscribe( + this.http.get<{ clients: Client[] }>(`${this.baseUrl}${room['@id']}`).subscribe( (response) => { this.dialog.open(ClassroomViewDialogComponent, { width: '90vw', - data: { clients: response['hydra:member'] }, + data: { clients: response.clients }, }); }, (error) => { @@ -612,7 +612,7 @@ export class GroupsComponent implements OnInit, OnDestroy { onShowClientDetail(event: MouseEvent, client: Client): void { event.stopPropagation(); this.dialog.open(ClientDetailsComponent, { - width: '1300px', + width: '1200px', data: { clientData: client }, }) } @@ -832,7 +832,7 @@ export class GroupsComponent implements OnInit, OnDestroy { getRunScriptContext(clientData: any[]): any { const selectedClientNames = clientData.map(client => client.name); - + if (clientData.length === 1) { return clientData[0]; // devuelve el objeto cliente completo } else if ( @@ -847,7 +847,7 @@ export class GroupsComponent implements OnInit, OnDestroy { } return null; } - + openPartitionTypeModal(event: MouseEvent, node: TreeNode | null = null): void { event.stopPropagation(); diff --git a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.css b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.css index aa2921c..8172cde 100644 --- a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.css +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.css @@ -36,11 +36,20 @@ } .partition-table th { - background-color: #f5f5f5; + background-color: #ebebeb; font-weight: 500; - color: #444; + color: #252525; } .partition-table tr:hover td { background-color: #f9f9f9; +} + +.summary-row { + font-weight: 500; + background-color: #ebebeb; +} + +.partition-table tr:hover td { + background-color: unset; } \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html index 6f25123..8c66c0a 100644 --- a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html @@ -1,42 +1,47 @@ -
-

{{ client.name }}

+
+

+ {{ group.clientNames.length === 1 ? group.clientNames[0] : 'Clientes (' + group.clientNames.length + '): ' + + group.clientNames.join(', ') }} +

-
{{ 'idColumn' | translate }} {{ task.id }} {{ column.header }} + + {{ column.cell(task) }} + - - {{ 'infoColumn' | translate }} {{ task.notes }} {{ 'createdByColumn' | translate }} {{ task.createdBy }} {{ 'executionDateColumn' | translate }} {{ task.dateTime | date:'short' }} {{ 'statusColumn' | translate }} {{ task.enabled ? ('enabled' | translate) : ('disabled' | translate) }} {{ 'columnActions' | translate }} - + + + + + +
+ +
+ +
+
+ + +
+ + Desde + + + + + + + Hasta + + + + +
+ + Activar tarea + + + info + + {{ summaryText }} + + + + + + + + + + diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.spec.ts b/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.spec.ts new file mode 100644 index 0000000..1af7cf1 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.spec.ts @@ -0,0 +1,100 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateTaskScheduleComponent } from './create-task-schedule.component'; +import {LoadingComponent} from "../../../../shared/loading/loading.component"; +import {HttpClientTestingModule} from "@angular/common/http/testing"; +import {ToastrModule} from "ngx-toastr"; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {MatDividerModule} from "@angular/material/divider"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatInputModule} from "@angular/material/input"; +import {MatIconModule} from "@angular/material/icon"; +import {MatButtonModule} from "@angular/material/button"; +import {MatTableModule} from "@angular/material/table"; +import {MatPaginatorModule} from "@angular/material/paginator"; +import {MatTooltipModule} from "@angular/material/tooltip"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog"; +import {MatSelectModule} from "@angular/material/select"; +import {MatTabsModule} from "@angular/material/tabs"; +import {MatAutocompleteModule} from "@angular/material/autocomplete"; +import {MatListModule} from "@angular/material/list"; +import {MatCardModule} from "@angular/material/card"; +import {MatMenuModule} from "@angular/material/menu"; +import {MatTreeModule} from "@angular/material/tree"; +import {TranslateModule, TranslateService} from "@ngx-translate/core"; +import {JoyrideModule} from "ngx-joyride"; +import {ConfigService} from "@services/config.service"; +import {ActivatedRoute} from "@angular/router"; +import {MatDatepickerModule} from "@angular/material/datepicker"; +import {MatButtonToggleModule} from "@angular/material/button-toggle"; +import { + DateAdapter, + MAT_DATE_FORMATS, + MAT_NATIVE_DATE_FORMATS, + MatNativeDateModule, + provideNativeDateAdapter +} from "@angular/material/core"; +import {MatCheckboxModule} from "@angular/material/checkbox"; + +describe('CreateTaskScheduleComponent', () => { + let component: CreateTaskScheduleComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url', + mercureUrl: 'http://mock-mercure-url' + }; + + await TestBed.configureTestingModule({ + declarations: [CreateTaskScheduleComponent, LoadingComponent], + imports: [ + HttpClientTestingModule, + ToastrModule.forRoot(), + BrowserAnimationsModule, + MatDividerModule, + MatFormFieldModule, + MatInputModule, + MatIconModule, + MatButtonModule, + MatTableModule, + MatPaginatorModule, + MatTooltipModule, + FormsModule, + ReactiveFormsModule, + MatProgressSpinnerModule, + MatDialogModule, + MatSelectModule, + MatTabsModule, + MatAutocompleteModule, + MatListModule, + MatCardModule, + MatMenuModule, + MatTreeModule, + MatDatepickerModule, + MatButtonToggleModule, + MatNativeDateModule, + MatCheckboxModule, + TranslateModule.forRoot(), + JoyrideModule.forRoot(), + ], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } }, + { provide: ConfigService, useValue: mockConfigService }, + { provide: ActivatedRoute, useValue: { queryParams: { subscribe: () => {} } } }, + { provide: MAT_DATE_FORMATS, useValue: MAT_NATIVE_DATE_FORMATS }, + ] + }).compileComponents(); + + fixture = TestBed.createComponent(CreateTaskScheduleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.ts b/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.ts new file mode 100644 index 0000000..f47d7ca --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.ts @@ -0,0 +1,196 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {FormBuilder, FormGroup} from "@angular/forms"; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {HttpClient} from "@angular/common/http"; +import {ToastrService} from "ngx-toastr"; +import {ConfigService} from "@services/config.service"; + +@Component({ + selector: 'app-create-task-schedule', + templateUrl: './create-task-schedule.component.html', + styleUrl: './create-task-schedule.component.css' +}) +export class CreateTaskScheduleComponent implements OnInit{ + form: FormGroup; + baseUrl: string; + apiUrl: string; + recurrenceTypes = ['none', 'custom']; + weekDays: string[] = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']; + isSingleDateSelected: boolean = true; + monthsList: string[] = [ + 'january', 'february', 'march', 'april', 'may', 'june', + 'july', 'august', 'september', 'october', 'november', 'december' + ]; + + monthRows: string[][] = []; + editing: boolean = false; + selectedMonths: { [key: string]: boolean } = {}; + selectedDays: { [key: string]: boolean } = {}; + + constructor( + private fb: FormBuilder, + public dialogRef: MatDialogRef, + private http: HttpClient, + private toastr: ToastrService, + private configService: ConfigService, + @Inject(MAT_DIALOG_DATA) public data: any + ) { + + this.baseUrl = this.configService.apiUrl; + this.apiUrl = `${this.baseUrl}/command-task-schedules`; + this.form = this.fb.group({ + executionDate: [new Date()], + executionTime: ['08:00'], + recurrenceType: ['none'], + recurrenceDetails: this.fb.group({ + daysOfWeek: [[]], + months: this.fb.control([]), + initDate: [null], + endDate: [null] + }), + enabled: [true] + }); + + if (this.data.schedule) { + this.editing = true; + this.loadData(); + } + + this.form.get('recurrenceType')?.valueChanges.subscribe((value) => { + if (value === 'none') { + this.form.get('recurrenceDetails')?.disable(); + } else { + this.form.get('recurrenceDetails')?.enable(); + } + }); + } + + ngOnInit(): void { + this.monthRows = [ + this.monthsList.slice(0, 6), + this.monthsList.slice(6, 12) + ]; + } + + loadData(): void { + this.http.get(`${this.baseUrl}${this.data.schedule['@id']}`).subscribe( + (data) => { + const formattedExecutionTime = this.formatExecutionTime(data.executionTime); + + this.form.patchValue({ + executionDate: data.executionDate, + executionTime: formattedExecutionTime, + recurrenceType: data.recurrenceType, + recurrenceDetails: { + ...data.recurrenceDetails, + initDate: data.recurrenceDetails.initDate || null, + endDate: data.recurrenceDetails.endDate || null, + daysOfWeek: data.recurrenceDetails.daysOfWeek || [], + months: data.recurrenceDetails.months || [] + }, + enabled: data.enabled + }); + this.selectedDays = data.recurrenceDetails.daysOfWeek.reduce((acc: any, day: string) => { + acc[day] = true; + return acc; + }, {}); + this.selectedMonths = data.recurrenceDetails.months.reduce((acc: any, month: string) => { + acc[month] = true; + return acc; + }, {}); + }, + (error) => { + console.error('Error loading schedule data', error); + } + ); + } + + formatExecutionTime(time: string | Date): string { + const date = (time instanceof Date) ? time : new Date(time); + if (isNaN(date.getTime())) { + console.error('Invalid execution time:', time); + return ''; + } + return date.toISOString().substring(11, 16); + } + + onSubmit() { + const formData = this.form.value; + + const payload: any = { + commandTask: this.data.task['@id'], + 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 || [] + }, + enabled: formData.enabled + } + + if (this.editing) { + const taskId = this.data.task.uuid; + this.http.patch(`${this.baseUrl}${this.data.schedule['@id']}`, payload).subscribe({ + next: () => { + this.toastr.success('Programacion de tarea actualizada con éxito'); + this.dialogRef.close(true); + }, + error: () => { + this.toastr.error('Error al actualizar la tarea'); + } + }); + } else { + this.http.post(this.apiUrl, payload).subscribe({ + next: () => { + this.toastr.success('Programacion de tarea creada con éxito'); + this.dialogRef.close(true); + }, + error: () => { + this.toastr.error('Error al crear la tarea'); + } + }); + } + + } + + onCancel(): void { + this.dialogRef.close(false); + } + + get summaryText(): string { + const recurrence = this.form.get('recurrenceType')?.value; + const start = this.form.get('recurrenceType')?.value === 'none' ? this.form.get('executionDate')?.value : this.form.get('recurrenceDetails.initDate')?.value; + const end = this.form.get('recurrenceType')?.value === 'none' ? this.form.get('executionDate')?.value : this.form.get('recurrenceDetails.endDate')?.value; + const time = this.form.get('executionTime')?.value; + const days = Object.keys(this.selectedDays).filter(day => this.selectedDays[day]); + const months = Object.keys(this.selectedMonths).filter(month => this.selectedMonths[month]); + + if (recurrence === 'none') { + return `Esta acción se ejecutará una sola vez el ${ this.formatDate(start)} a las ${time}.`; + } + + return `Esta acción se ejecutará todos los ${days.join(', ')} de ${months.join(', ')}, desde el ${this.formatDate(start)} hasta el ${this.formatDate(end)} a las ${time}.`; + } + + formatDate(date: string | Date): string { + const realDate = (date instanceof Date) ? date : new Date(date); + return new Intl.DateTimeFormat('es-ES', { dateStyle: 'long' }).format(realDate); + } + + toggleDay(day: string) { + this.selectedDays[day] = !this.selectedDays[day]; + const days = Object.keys(this.selectedDays).filter(d => this.selectedDays[d]); + this.form.get('recurrenceDetails.daysOfWeek')?.setValue(days); + } + + toggleMonth(month: string) { + this.selectedMonths[month] = !this.selectedMonths[month]; + const months = Object.keys(this.selectedMonths).filter(m => this.selectedMonths[m]); + this.form.get('recurrenceDetails.months')?.setValue(months); + } + +} diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.css b/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.css new file mode 100644 index 0000000..1cf8b3c --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.css @@ -0,0 +1,284 @@ + +.divider { + margin: 20px 0; +} + +table { + width: 100%; + margin-top: 50px; +} + +.task-form { + padding: 20px; +} + +.search-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + box-sizing: border-box; +} + +.deploy-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + gap: 10px; +} + +.script-container { + gap: 20px; + padding: 20px; + background-color: #eaeff6; + border-radius: 12px; + margin-top: 20px; +} + +.script-content { + flex: 2; + min-width: 60%; +} + +.script-params { + flex: 1; + min-width: 35%; +} + +@media (max-width: 768px) { + .script-container { + flex-direction: column; + } + + .script-content, .script-params { + min-width: 100%; + } +} + +.select-container { + margin-top: 20px; + align-items: center; + padding: 20px; + box-sizing: border-box; +} + +.input-group { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin-top: 20px; +} + +.input-field { + flex: 1 1 calc(33.33% - 16px); + min-width: 250px; +} + +.script-preview { + background-color: #f4f4f4; + border: 1px solid #ccc; + padding: 10px; + border-radius: 5px; + font-family: monospace; + white-space: pre-wrap; + min-height: 50px; +} + +.custom-width { + width: 50%; + margin-bottom: 16px; +} + +.search-string { + flex: 2; + padding: 5px; +} + +.search-boolean { + flex: 1; + padding: 5px; +} + +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 10px; + border-bottom: 1px solid #ddd; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +.paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} + +.clients-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 8px; +} + +.client-item { + position: relative; +} + +.client-card { + background: #ffffff; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; + position: relative; + padding: 8px; + text-align: center; + cursor: pointer; + transition: background-color 0.3s, transform 0.2s; + + &:hover { + background-color: #f0f0f0; + transform: scale(1.02); + } +} + +.client-details { + margin-top: 4px; +} + +.client-name { + font-size: 0.9em; + font-weight: 600; + color: #333; + margin-bottom: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; + display: inline-block; +} + +.client-ip { + display: block; + font-size: 0.9em; + color: #666; +} + +.header-container-title { + flex-grow: 1; + text-align: left; + padding-left: 1em; +} + +.button-row { + display: flex; + padding-right: 1em; +} + +.client-card { + background: #ffffff; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; + position: relative; + padding: 8px; + text-align: center; + cursor: pointer; + transition: background-color 0.3s, transform 0.2s; + + &:hover { + background-color: #f0f0f0; + transform: scale(1.02); + } +} + +::ng-deep .custom-tooltip { + white-space: pre-line !important; + max-width: 200px; + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 8px; + border-radius: 4px; +} + +.selected-client { + background-color: #a0c2e5 !important; + color: white !important; +} + +.button-row { + display: flex; + padding-right: 1em; +} + +.disabled-client { + pointer-events: none; + opacity: 0.5; +} + +.action-button { + margin-top: 10px; + margin-bottom: 10px; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} + +.mat-expansion-panel-header-description { + justify-content: space-between; + align-items: center; +} + +.new-command-container { + display: flex; + flex-direction: column; + gap: 10px; + padding: 15px; + background-color: #eaeff6; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); + margin-top: 15px; +} + +.new-command-container mat-form-field { + width: 100%; +} + +.new-command-container textarea { + font-family: monospace; + resize: vertical; +} + +.new-command-container .action-button { + align-self: flex-end; + color: white; + padding: 8px 16px; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.full-width { + width: 100%; +} + +.script-selector-card { + margin: 20px 20px; + padding: 16px; +} + +.toggle-options { + display: flex; + justify-content: start; + margin: 16px 0; +} + + diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.html b/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.html new file mode 100644 index 0000000..7a7ec4d --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.html @@ -0,0 +1,65 @@ +

Añadir acción a: {{ data.task?.name }}

+ + +
+ +
+ + + edit Nuevo Script + + + storage Script Guardado + + +
+ +
+ + Orden de ejecucion + + + + Ingrese el script + + + +
+ +
+ + Seleccione script a ejecutar + + {{ script.name }} + + +
+ +
+ + Orden de ejecucion + + + +
+

Script:

+
+
+ +
+

Ingrese los parámetros:

+
+ + {{ paramName }} + + +
+
+
+
+
+ + + + + diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.spec.ts b/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.spec.ts new file mode 100644 index 0000000..9595ed0 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.spec.ts @@ -0,0 +1,89 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateTaskScriptComponent } from './create-task-script.component'; +import {GroupsComponent} from "../../../groups/groups.component"; +import {ExecuteCommandComponent} from "../../main-commands/execute-command/execute-command.component"; +import {LoadingComponent} from "../../../../shared/loading/loading.component"; +import {HttpClientTestingModule} from "@angular/common/http/testing"; +import {ToastrModule} from "ngx-toastr"; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {MatDividerModule} from "@angular/material/divider"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatInputModule} from "@angular/material/input"; +import {MatIconModule} from "@angular/material/icon"; +import {MatButtonModule} from "@angular/material/button"; +import {MatTableModule} from "@angular/material/table"; +import {MatPaginatorModule} from "@angular/material/paginator"; +import {MatTooltipModule} from "@angular/material/tooltip"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog"; +import {MatSelectModule} from "@angular/material/select"; +import {MatTabsModule} from "@angular/material/tabs"; +import {MatAutocompleteModule} from "@angular/material/autocomplete"; +import {MatListModule} from "@angular/material/list"; +import {MatCardModule} from "@angular/material/card"; +import {MatMenuModule} from "@angular/material/menu"; +import {MatTreeModule} from "@angular/material/tree"; +import {TranslateModule, TranslateService} from "@ngx-translate/core"; +import {JoyrideModule} from "ngx-joyride"; +import {ConfigService} from "@services/config.service"; +import {ActivatedRoute} from "@angular/router"; +import {MatButtonToggleModule} from "@angular/material/button-toggle"; + +describe('CreateTaskScriptComponent', () => { + let component: CreateTaskScriptComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url', + mercureUrl: 'http://mock-mercure-url' + }; + + await TestBed.configureTestingModule({ + declarations: [CreateTaskScriptComponent, LoadingComponent], + imports: [ + HttpClientTestingModule, + ToastrModule.forRoot(), + BrowserAnimationsModule, + MatDividerModule, + MatFormFieldModule, + MatInputModule, + MatIconModule, + MatButtonModule, + MatTableModule, + MatPaginatorModule, + MatTooltipModule, + FormsModule, + ReactiveFormsModule, + MatProgressSpinnerModule, + MatDialogModule, + MatSelectModule, + MatTabsModule, + MatAutocompleteModule, + MatListModule, + MatCardModule, + MatMenuModule, + MatButtonToggleModule, + MatTreeModule, + TranslateModule.forRoot(), + JoyrideModule.forRoot(), + ], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } }, + { provide: ConfigService, useValue: mockConfigService }, + { provide: ActivatedRoute, useValue: { queryParams: { subscribe: () => {} } } }, + ] + }).compileComponents(); + + fixture = TestBed.createComponent(CreateTaskScriptComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.ts b/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.ts new file mode 100644 index 0000000..4b3497f --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.ts @@ -0,0 +1,137 @@ +import {Component, EventEmitter, Inject, OnInit, Output} from '@angular/core'; +import {SelectionModel} from "@angular/cdk/collections"; +import {HttpClient} from "@angular/common/http"; +import {ToastrService} from "ngx-toastr"; +import {ConfigService} from "@services/config.service"; +import {ActivatedRoute, Router} from "@angular/router"; +import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog"; +import { + SaveScriptComponent +} from "../../../groups/components/client-main-view/run-script-assistant/save-script/save-script.component"; +import {FormBuilder, FormGroup} from "@angular/forms"; + +@Component({ + selector: 'app-create-task-script', + templateUrl: './create-task-script.component.html', + styleUrl: './create-task-script.component.css' +}) +export class CreateTaskScriptComponent implements OnInit { + form: FormGroup; + baseUrl: string; + @Output() dataChange = new EventEmitter(); + errorMessage = ''; + loading: boolean = false; + scripts: any[] = []; + scriptContent: string = ""; + parameters: any = {}; + commandType: string = 'existing'; + selectedScript: any = null; + newScript: string = ''; + executionOrder: Number = 0; + selection = new SelectionModel(true, []); + parameterNames: string[] = Object.keys(this.parameters); + + constructor( + private fb: FormBuilder, + private http: HttpClient, + public dialogRef: MatDialogRef, + private toastService: ToastrService, + private configService: ConfigService, + private router: Router, + private dialog: MatDialog, + private route: ActivatedRoute, + @Inject(MAT_DIALOG_DATA) public data: any + ) { + this.baseUrl = this.configService.apiUrl; + this.loadScripts() + this.form = this.fb.group({ + content: [''], + order: [''], + }) + } + + ngOnInit(): void { + + } + + loadScripts(): void { + this.loading = true; + + this.http.get(`${this.baseUrl}/commands?readOnly=false&enabled=true`).subscribe((data: any) => { + this.scripts = data['hydra:member']; + this.loading = false; + }, (error) => { + this.toastService.error(error.error['hydra:description']); + this.loading = false; + }); + } + + saveNewScript() { + if (!this.newScript.trim()) { + this.toastService.error('Debe ingresar un script antes de guardar.'); + return; + } + const dialogRef = this.dialog.open(SaveScriptComponent, { + width: '400px', + data: this.newScript + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.toastService.success('Script guardado correctamente'); + } + }); + } + + onScriptChange() { + if (this.selectedScript) { + this.scriptContent = this.selectedScript.script; + + const matches = this.scriptContent.match(/@(\w+)/g) || []; + const uniqueParams = Array.from(new Set(matches.map(m => m.slice(1)))); + + this.parameters = {}; + uniqueParams.forEach(param => this.parameters[param] = ''); + + this.parameterNames = uniqueParams; + + this.updateScript(); + } + } + + onParamChange(name: string, value: string): void { + this.parameters[name] = value; + this.updateScript(); + } + + updateScript(): void { + let updatedScript = this.selectedScript.script; + + for (const [key, value] of Object.entries(this.parameters)) { + const regex = new RegExp(`@${key}\\b`, 'g'); + updatedScript = updatedScript.replace(regex, value || `@${key}`); + } + + this.scriptContent = updatedScript; + } + + onSubmit() { + this.http.post(`${this.baseUrl}/command-task-scripts`, { + commandTask: this.data.task['@id'], + content: this.commandType === 'existing' ? this.scriptContent : this.newScript, + order: this.executionOrder, + }).subscribe({ + next: () => { + this.toastService.success('Tarea creada con éxito'); + this.dialogRef.close(true); + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }) + } + + onCancel(): void { + this.dialogRef.close(false); + } +} 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 b69339e..d14ed3c 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 @@ -10,19 +10,51 @@ width: 100%; } -.button-container { - display: flex; - justify-content: flex-end; - margin-top: 20px; - padding: 1.5rem; -} - mat-form-field { margin-bottom: 16px; } +.loading-spinner { + display: block; + margin: 0 auto; + align-items: center; + justify-content: center; +} + .section-title { margin-top: 24px; margin-bottom: 8px; font-weight: 500; } + +.summary-section { + background-color: #f9f9f9; + border-bottom: 1px solid #ddd; + margin-bottom: 10px; +} + +.summary-block { + margin-top: 10px; +} + +.date-time-row { + display: flex; + gap: 16px; + margin-top: 12px; +} + +.half-width { + flex: 1; + min-width: 0; +} + +.full-width { + width: 100%; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} 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 9d5b756..b9a8aa8 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 @@ -1,106 +1,45 @@

{{ editing ? ('editTask' | translate) : ('createTask' | translate) }}

-
- + + -

Información

- + - Información - + {{ 'nameLabel' | translate }} + + {{ 'requiredFieldError' | translate }} -

{{ 'informationSectionTitle' | translate }}

- - {{ 'informationLabel' | translate }} + {{ 'notesLabel' | translate }} -

{{ 'commandSelectionSectionTitle' | translate }}

- - {{ 'selectCommandsLabel' | translate }} - - - {{ group.name }} - - - {{ 'requiredFieldError' | translate }} - - - - {{ 'selectIndividualCommandsLabel' | translate }} - - - {{ command.name }} - + Ámbito + + Unidad Organizativa + Grupo de aulas + Aulas + Grupos de clientes -

{{ 'executionDateTimeSectionTitle' | translate }}

- - - {{ 'executionDateLabel' | translate }} - - - - {{ 'requiredFieldError' | translate }} - - - - {{ 'executionTimeLabel' | translate }} - - {{ 'requiredFieldError' | translate }} - - -

{{ 'destinationSelectionSectionTitle' | translate }}

- - - {{ 'selectOrganizationalUnitLabel' | translate }} - + + {{ 'organizationalUnitLabel' | translate }} + - {{ unit.name }} - - - {{ 'requiredFieldError' | translate }} - - - - {{ 'selectClassroomLabel' | translate }} - - - {{ child.name }} +
{{ unit.name }}
+
{{ unit.path }}
- - {{ 'selectClientsLabel' | translate }} - - - {{ 'selectAllClients' | translate }} - - - {{ client.name }} ({{ client.ip }}) - - - + ¿Quieres programar la tarea al finalizar su creación? + +
- - Selecciona Clientes - - - Seleccionar todos - - - {{ client.name }} ({{ client.ip }}) - - - -
- - -
- -
+ + + + 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 4f61c00..92c6440 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 @@ -1,9 +1,12 @@ import { Component, OnInit, Inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog'; import { ToastrService } from 'ngx-toastr'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ConfigService } from '@services/config.service'; +import {of} from "rxjs"; +import {startWith, switchMap} from "rxjs/operators"; +import {CreateTaskScheduleComponent} from "../create-task-schedule/create-task-schedule.component"; @Component({ selector: 'app-create-task', @@ -19,170 +22,143 @@ export class CreateTaskComponent implements OnInit { apiUrl: string; editing: boolean = false; availableOrganizationalUnits: any[] = []; - selectedUnitChildren: any[] = []; - selectedClients: any[] = []; - selectedClientIds: Set = new Set(); + clients: any[] = []; + allOrganizationalUnits: any[] = []; + loading: boolean = false; constructor( private fb: FormBuilder, private http: HttpClient, private configService: ConfigService, private toastr: ToastrService, + private dialog: MatDialog, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any ) { this.baseUrl = this.configService.apiUrl; this.apiUrl = `${this.baseUrl}/command-tasks`; this.taskForm = this.fb.group({ - commandGroup: ['', Validators.required], - extraCommands: [[]], - date: ['', Validators.required], - time: ['', Validators.required], + scope: [ this.data?.scope ? this.data.scope : '', Validators.required], + name: ['', Validators.required], + organizationalUnit: [ this.data?.organizationalUnit ? this.data.organizationalUnit : null, Validators.required], notes: [''], - organizationalUnit: ['', Validators.required], - selectedChild: [''], - selectedClients: [[]] + scheduleAfterCreate: [false] }); } ngOnInit(): void { - this.loadCommandGroups(); - this.loadIndividualCommands(); - this.loadOrganizationalUnits(); - if (this.data && this.data.task) { - this.editing = true; - this.loadTaskData(this.data.task); - } + this.loading = true; + const observables = [ + this.loadCommandGroups(), + this.loadIndividualCommands(), + this.loadOrganizationalUnits(), + this.startUnitsFilter(), + ]; + + Promise.all(observables).then(() => { + + if (this.data.task) { + this.editing = true; + this.loadData().then(() => { + this.loading = false; + }) + } else { + this.loading = false; + } + }).catch(() => { + this.loading = false; + }) } - loadCommandGroups(): void { + loadData(): Promise { + return new Promise((resolve, reject) => { + this.http.get(`${this.baseUrl}${this.data.task['@id']}`).subscribe( + (data) => { + this.taskForm.patchValue({ + name: data.name, + scope: data.scope, + organizationalUnit: data.organizationalUnit ? data.organizationalUnit['@id'] : null, + notes: data.notes, + }); + resolve(); + }, + (error) => { + this.toastr.error('Error al cargar los datos de la tarea'); + reject(error); + } + ); + }) + } + + onScopeChange(scope: string): void { + this.filterUnits(scope).subscribe(filteredUnits => { + this.availableOrganizationalUnits = filteredUnits; + this.taskForm.get('organizationalUnit')?.setValue(''); + }); + } + + startUnitsFilter(): Promise { + return new Promise((resolve, reject) => { + this.taskForm.get('scope')?.valueChanges.pipe( + startWith(this.taskForm.get('scope')?.value), + switchMap((value) => this.filterUnits(value)) + ).subscribe(filteredUnits => { + this.availableOrganizationalUnits = filteredUnits; + resolve(); + }, error => { + this.toastr.error('Error al filtrar las unidades organizacionales'); + reject(error); + }); + }) + } + + filterUnits(value: string) { + const filtered = this.allOrganizationalUnits.filter(unit => unit.type === value); + return of(filtered); + } + + loadCommandGroups(): Promise { + return new Promise((resolve, reject) => { this.http.get(`${this.baseUrl}/command-groups`).subscribe( (data) => { this.availableCommandGroups = data['hydra:member']; + resolve(); }, (error) => { this.toastr.error('Error al cargar los grupos de comandos'); - } - ); - } - - loadIndividualCommands(): void { - this.http.get(`${this.baseUrl}/commands`).subscribe( - (data) => { - this.availableIndividualCommands = data['hydra:member']; - }, - (error) => { - this.toastr.error('Error al cargar los comandos individuales'); - } - ); - } - - loadOrganizationalUnits(): void { - this.http.get(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=30`).subscribe( - (data) => { - this.availableOrganizationalUnits = data['hydra:member'].filter((unit: any) => unit['type'] === 'organizational-unit'); - }, - (error) => { - this.toastr.error('Error al cargar las unidades organizacionales'); - } - ); - } - - loadTaskData(task: any): void { - this.taskForm.patchValue({ - commandGroup: task.commandGroup ? task.commandGroup['@id'] : '', - extraCommands: task.commands ? task.commands.map((cmd: any) => cmd['@id']) : [], - date: task.dateTime ? task.dateTime.split('T')[0] : '', - time: task.dateTime ? task.dateTime.split('T')[1].slice(0, 5) : '', - notes: task.notes || '', - organizationalUnit: task.organizationalUnit ? task.organizationalUnit['@id'] : '' + reject(error); + }); }); - - if (task.commandGroup) { - this.selectedGroupCommands = task.commandGroup.commands; - } } - private collectClassrooms(unit: any): any[] { - let classrooms = []; - if (unit.type === 'classroom') { - classrooms.push(unit); - } - if (unit.children && unit.children.length > 0) { - for (let child of unit.children) { - classrooms = classrooms.concat(this.collectClassrooms(child)); - } - } - return classrooms; + loadIndividualCommands(): Promise { + return new Promise((resolve, reject) => { + this.http.get(`${this.baseUrl}/commands`).subscribe( + (data) => { + this.availableIndividualCommands = data['hydra:member']; + resolve(); + }, + (error) => { + this.toastr.error('Error al cargar los comandos individuales'); + reject(error); + }); + }); } - onOrganizationalUnitChange(): void { - const selectedUnitId = this.taskForm.get('organizationalUnit')?.value; - const selectedUnit = this.availableOrganizationalUnits.find(unit => unit['@id'] === selectedUnitId); - - if (selectedUnit) { - this.selectedUnitChildren = this.collectClassrooms(selectedUnit); - } else { - this.selectedUnitChildren = []; - } - - this.taskForm.patchValue({ selectedChild: '', selectedClients: [] }); - this.selectedClients = []; - this.selectedClientIds.clear(); - } - - onChildChange(): void { - const selectedChildId = this.taskForm.get('selectedChild')?.value; - - if (!selectedChildId) { - this.selectedClients = []; - return; - } - - const url = `${this.baseUrl}${selectedChildId}`.replace(/([^:]\/)\/+/g, '$1'); - - this.http.get(url).subscribe( - (data) => { - if (Array.isArray(data.clients) && data.clients.length > 0) { - this.selectedClients = data.clients; - } else { - this.selectedClients = []; - this.toastr.warning('El aula seleccionada no tiene clientes.'); + loadOrganizationalUnits(): Promise { + return new Promise((resolve, reject) => { + this.http.get(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=100`).subscribe( + (data) => { + this.allOrganizationalUnits = data['hydra:member']; + this.availableOrganizationalUnits = [...this.allOrganizationalUnits]; + resolve(); + }, + (error) => { + this.toastr.error('Error al cargar las unidades organizacionales'); + reject(error); } - - this.taskForm.patchValue({ selectedClients: [] }); - this.selectedClientIds.clear(); - }, - (error) => { - this.toastr.error('Error al cargar los detalles del aula seleccionada'); - } - ); - } - - toggleSelectAll() { - const allSelected = this.areAllSelected(); - if (allSelected) { - this.selectedClientIds.clear(); - } else { - this.selectedClients.forEach(client => this.selectedClientIds.add(client.uuid)); - } - this.taskForm.get('selectedClients')!.setValue(Array.from(this.selectedClientIds)); - } - - areAllSelected(): boolean { - return this.selectedClients.length > 0 && this.selectedClients.every(client => this.selectedClientIds.has(client.uuid)); - } - - onCommandGroupChange(): void { - const selectedGroupId = this.taskForm.get('commandGroup')?.value; - this.http.get(`${this.baseUrl}/command-groups/${selectedGroupId}`).subscribe( - (data) => { - this.selectedGroupCommands = data.commands; - }, - (error) => { - this.toastr.error('Error al cargar los comandos del grupo seleccionado'); - } - ); + ); + }); } saveTask(): void { @@ -192,22 +168,14 @@ export class CreateTaskComponent implements OnInit { } const formData = this.taskForm.value; - const dateTime = this.combineDateAndTime(formData.date, formData.time); - const selectedCommands = formData.extraCommands && formData.extraCommands.length > 0 - ? formData.extraCommands.map((id: any) => `/commands/${id}`) - : null; const payload: any = { - commandGroups: formData.commandGroup ? [`/command-groups/${formData.commandGroup}`] : null, - dateTime: dateTime, + name: formData.name, + scope: formData.scope, + organizationalUnit: formData.organizationalUnit, notes: formData.notes || '', - clients: Array.from(this.selectedClientIds).map((uuid: string) => `/clients/${uuid}`), }; - if (selectedCommands) { - payload.commands = selectedCommands; - } - if (this.editing) { const taskId = this.data.task.uuid; this.http.patch(`${this.apiUrl}/${taskId}`, payload).subscribe({ @@ -221,9 +189,21 @@ export class CreateTaskComponent implements OnInit { }); } else { this.http.post(this.apiUrl, payload).subscribe({ - next: () => { + next: response => { this.toastr.success('Tarea creada con éxito'); - this.dialogRef.close(true); + this.dialogRef.close(response); + if (formData.scheduleAfterCreate) { + const dialogRef = this.dialog.open(CreateTaskScheduleComponent, { + width: '800px', + data: { task: response } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.toastr.success('Tarea programada correctamente'); + } + }); + } }, error: () => { this.toastr.error('Error al crear la tarea'); @@ -232,14 +212,7 @@ export class CreateTaskComponent implements OnInit { } } - combineDateAndTime(date: string, time: string): string { - const dateObj = new Date(date); - const [hours, minutes] = time.split(':').map(Number); - dateObj.setHours(hours, minutes, 0); - return dateObj.toISOString(); - } - close(): void { - this.dialogRef.close(); + this.dialogRef.close(false); } } diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.css b/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.css new file mode 100644 index 0000000..d092e27 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.css @@ -0,0 +1,89 @@ +.full-width { + width: 100%; +} + +form { + padding: 20px; +} + +.spacing-container { + margin-top: 20px; + margin-bottom: 16px; +} + +.list-item-content { + display: flex; + align-items: flex-start; + justify-content: space-between; + width: 100%; +} + +.text-content { + flex-grow: 1; + margin-right: 16px; + margin-left: 10px; +} + +.icon-container { + display: flex; + align-items: center; +} + +.right-icon { + margin-left: 8px; + cursor: pointer; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} + +.lists-container { + padding: 16px; +} + +.search-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + margin: 1.5rem 0rem 1.5rem 0rem; + box-sizing: border-box; +} + +.search-string { + flex: 1; + padding: 5px; +} + +.search-select { + flex: 1; + padding: 5px; +} + +table { + width: 100%; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); +} + +.paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} + +mat-spinner { + margin: 0 auto; + align-self: center; +} + +.subnets-button-row { + display: flex; + gap: 15px; +} diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.html b/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.html new file mode 100644 index 0000000..0df04e2 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.html @@ -0,0 +1,100 @@ + + +

Gestionar programaciones de tareas en {{ data.commandTask?.name }}

+ + +
+ + Buscar nombre del cliente + + search + + Pulsar 'enter' para buscar + + + Buscar por tipo + + search + + Pulsar 'enter' para buscar + +
+ + + + + + + + + + + + + + + +
{{ column.header }} + + + + + No programado +
+ {{ schedule.executionDate | date }} +
+
+ + Programado +
+ {{ schedule.recurrenceDetails.initDate | date }} → {{ schedule.recurrenceDetails.endDate | date}} +
+
+
+
+ + {{ schedule.executionTime | date: 'HH:mm' }} + + + {{ column.cell(schedule) }} + + + + + Activo + + + Inactivo + + + +
Acciones + + +
+ +
+ + +
+
+ + + + diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.spec.ts b/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.spec.ts new file mode 100644 index 0000000..bc6ff23 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.spec.ts @@ -0,0 +1,86 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ShowTaskScheduleComponent } from './show-task-schedule.component'; +import {LoadingComponent} from "../../../../shared/loading/loading.component"; +import {HttpClientTestingModule} from "@angular/common/http/testing"; +import {ToastrModule} from "ngx-toastr"; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {MatDividerModule} from "@angular/material/divider"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatInputModule} from "@angular/material/input"; +import {MatIconModule} from "@angular/material/icon"; +import {MatButtonModule} from "@angular/material/button"; +import {MatTableModule} from "@angular/material/table"; +import {MatPaginatorModule} from "@angular/material/paginator"; +import {MatTooltipModule} from "@angular/material/tooltip"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog"; +import {MatSelectModule} from "@angular/material/select"; +import {MatTabsModule} from "@angular/material/tabs"; +import {MatAutocompleteModule} from "@angular/material/autocomplete"; +import {MatListModule} from "@angular/material/list"; +import {MatCardModule} from "@angular/material/card"; +import {MatMenuModule} from "@angular/material/menu"; +import {MatTreeModule} from "@angular/material/tree"; +import {TranslateModule} from "@ngx-translate/core"; +import {JoyrideModule} from "ngx-joyride"; +import {ConfigService} from "@services/config.service"; +import {ActivatedRoute} from "@angular/router"; +import {CreateTaskScheduleComponent} from "../create-task-schedule/create-task-schedule.component"; + +describe('ShowTaskScheduleComponent', () => { + let component: ShowTaskScheduleComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url', + mercureUrl: 'http://mock-mercure-url' + }; + + await TestBed.configureTestingModule({ + declarations: [ShowTaskScheduleComponent, LoadingComponent], + imports: [ + HttpClientTestingModule, + ToastrModule.forRoot(), + BrowserAnimationsModule, + MatDividerModule, + MatFormFieldModule, + MatInputModule, + MatIconModule, + MatButtonModule, + MatTableModule, + MatPaginatorModule, + MatTooltipModule, + FormsModule, + ReactiveFormsModule, + MatProgressSpinnerModule, + MatDialogModule, + MatSelectModule, + MatTabsModule, + MatAutocompleteModule, + MatListModule, + MatCardModule, + MatMenuModule, + MatTreeModule, + TranslateModule.forRoot(), + JoyrideModule.forRoot(), + ], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } }, + { provide: ConfigService, useValue: mockConfigService }, + { provide: ActivatedRoute, useValue: { queryParams: { subscribe: () => {} } } }, + ] + }).compileComponents(); + + fixture = TestBed.createComponent(ShowTaskScheduleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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 new file mode 100644 index 0000000..108197a --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-schedule/show-task-schedule.component.ts @@ -0,0 +1,107 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MatTableDataSource} from "@angular/material/table"; +import {Client} from "../../../groups/model/model"; +import {ToastrService} from "ngx-toastr"; +import {HttpClient} from "@angular/common/http"; +import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog"; +import {ConfigService} from "@services/config.service"; +import {DeleteModalComponent} from "../../../../shared/delete_modal/delete-modal/delete-modal.component"; +import {CreateTaskScheduleComponent} from "../create-task-schedule/create-task-schedule.component"; +import {DatePipe} from "@angular/common"; + +@Component({ + selector: 'app-show-task-schedule', + templateUrl: './show-task-schedule.component.html', + styleUrl: './show-task-schedule.component.css' +}) +export class ShowTaskScheduleComponent implements OnInit{ + baseUrl: string; + dataSource = new MatTableDataSource([]); + length = 0; + itemsPerPage: number = 10; + pageSizeOptions: number[] = [5, 10, 20]; + page = 0; + loading: boolean = false; + filters: { [key: string]: string } = {}; + datePipe: DatePipe = new DatePipe('es-ES'); + + 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') }, + { 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 } + ]; + + displayedColumns: string[] = ['id', 'recurrenceType', 'time', 'daysOfWeek', 'months', 'enabled', 'actions']; + + constructor( + private toastService: ToastrService, + private http: HttpClient, + public dialogRef: MatDialogRef, + public dialog: MatDialog, + private configService: ConfigService, + @Inject(MAT_DIALOG_DATA) public data: any + ) { + this.baseUrl = this.configService.apiUrl; + } + + ngOnInit(): void { + if (this.data) { + this.loadData(); + } + } + + loadData() { + this.loading = true; + this.http.get(`${this.baseUrl}/command-task-schedules?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}&commandTask.id=${this.data.commandTask?.id}`, { params: this.filters }).subscribe( + (data) => { + this.dataSource.data = data['hydra:member']; + this.length = data['hydra:totalItems']; + this.loading = false; + }, + (error) => { + this.loading = false; + } + ); + } + + editSchedule(schedule: any): void { + this.dialog.open(CreateTaskScheduleComponent, { + width: '800px', + data: { schedule: schedule, task: this.data.commandTask } + }).afterClosed().subscribe(() => this.loadData()); + } + + deleteSchedule(schedule: any): void { + const dialogRef = this.dialog.open(DeleteModalComponent, { + width: '300px', + data: { name: 'tarea programada' } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.http.delete(`${this.baseUrl}${schedule['@id']}`).subscribe( + () => { + this.toastService.success('Programación eliminada correctamente'); + this.loadData(); + }, + (error) => { + this.toastService.error(error.error['hydra:description']); + } + ); + } + }) + } + + onNoClick(): void { + this.dialogRef.close(false); + } + + onPageChange(event: any) { + this.page = event.pageIndex; + this.itemsPerPage = event.pageSize; + this.loadData() + } +} diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.css b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.css new file mode 100644 index 0000000..d092e27 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.css @@ -0,0 +1,89 @@ +.full-width { + width: 100%; +} + +form { + padding: 20px; +} + +.spacing-container { + margin-top: 20px; + margin-bottom: 16px; +} + +.list-item-content { + display: flex; + align-items: flex-start; + justify-content: space-between; + width: 100%; +} + +.text-content { + flex-grow: 1; + margin-right: 16px; + margin-left: 10px; +} + +.icon-container { + display: flex; + align-items: center; +} + +.right-icon { + margin-left: 8px; + cursor: pointer; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} + +.lists-container { + padding: 16px; +} + +.search-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + margin: 1.5rem 0rem 1.5rem 0rem; + box-sizing: border-box; +} + +.search-string { + flex: 1; + padding: 5px; +} + +.search-select { + flex: 1; + padding: 5px; +} + +table { + width: 100%; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); +} + +.paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} + +mat-spinner { + margin: 0 auto; + align-self: center; +} + +.subnets-button-row { + display: flex; + gap: 15px; +} diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.html b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.html new file mode 100644 index 0000000..e8090db --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.html @@ -0,0 +1,70 @@ + + +

Gestionar scripts de tareas en {{ data.commandTask?.name }}

+ + +
+ + Buscar contenido de script + + search + + Pulsar 'enter' para buscar + +
+ + + + + + + + + + + + + + + +
{{ column.header }} + +
+ {{ column.cell(schedule) }} +
+
+ + + + + + + + + {{ column.cell(schedule) }} + +
Acciones + +
+ +
+ + +
+
+ + + + diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.spec.ts b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.spec.ts new file mode 100644 index 0000000..504883b --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.spec.ts @@ -0,0 +1,86 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ShowTaskScriptComponent } from './show-task-script.component'; +import {LoadingComponent} from "../../../../shared/loading/loading.component"; +import {HttpClientTestingModule} from "@angular/common/http/testing"; +import {ToastrModule} from "ngx-toastr"; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {MatDividerModule} from "@angular/material/divider"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatInputModule} from "@angular/material/input"; +import {MatIconModule} from "@angular/material/icon"; +import {MatButtonModule} from "@angular/material/button"; +import {MatTableModule} from "@angular/material/table"; +import {MatPaginatorModule} from "@angular/material/paginator"; +import {MatTooltipModule} from "@angular/material/tooltip"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog"; +import {MatSelectModule} from "@angular/material/select"; +import {MatTabsModule} from "@angular/material/tabs"; +import {MatAutocompleteModule} from "@angular/material/autocomplete"; +import {MatListModule} from "@angular/material/list"; +import {MatCardModule} from "@angular/material/card"; +import {MatMenuModule} from "@angular/material/menu"; +import {MatTreeModule} from "@angular/material/tree"; +import {TranslateModule} from "@ngx-translate/core"; +import {JoyrideModule} from "ngx-joyride"; +import {ConfigService} from "@services/config.service"; +import {ActivatedRoute} from "@angular/router"; +import {ShowTaskScheduleComponent} from "../show-task-schedule/show-task-schedule.component"; + +describe('ShowTaskScriptComponent', () => { + let component: ShowTaskScriptComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url', + mercureUrl: 'http://mock-mercure-url' + }; + + await TestBed.configureTestingModule({ + declarations: [ShowTaskScriptComponent, LoadingComponent], + imports: [ + HttpClientTestingModule, + ToastrModule.forRoot(), + BrowserAnimationsModule, + MatDividerModule, + MatFormFieldModule, + MatInputModule, + MatIconModule, + MatButtonModule, + MatTableModule, + MatPaginatorModule, + MatTooltipModule, + FormsModule, + ReactiveFormsModule, + MatProgressSpinnerModule, + MatDialogModule, + MatSelectModule, + MatTabsModule, + MatAutocompleteModule, + MatListModule, + MatCardModule, + MatMenuModule, + MatTreeModule, + TranslateModule.forRoot(), + JoyrideModule.forRoot(), + ], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } }, + { provide: ConfigService, useValue: mockConfigService }, + { provide: ActivatedRoute, useValue: { queryParams: { subscribe: () => {} } } }, + ] + }).compileComponents(); + + fixture = TestBed.createComponent(ShowTaskScriptComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.ts b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.ts new file mode 100644 index 0000000..45f4899 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/show-task-script.component.ts @@ -0,0 +1,104 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MatTableDataSource} from "@angular/material/table"; +import {ToastrService} from "ngx-toastr"; +import {HttpClient} from "@angular/common/http"; +import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog"; +import {ConfigService} from "@services/config.service"; +import {CreateTaskScheduleComponent} from "../create-task-schedule/create-task-schedule.component"; +import {DeleteModalComponent} from "../../../../shared/delete_modal/delete-modal/delete-modal.component"; +import {ViewParametersModalComponent} from "./view-parameters-modal/view-parameters-modal.component"; + +@Component({ + selector: 'app-show-task-script', + templateUrl: './show-task-script.component.html', + styleUrl: './show-task-script.component.css' +}) +export class ShowTaskScriptComponent implements OnInit{ + baseUrl: string; + dataSource = new MatTableDataSource([]); + length = 0; + itemsPerPage: number = 10; + pageSizeOptions: number[] = [5, 10, 20]; + page = 0; + loading: boolean = false; + filters: { [key: string]: string } = {}; + + columns = [ + { columnDef: 'id', header: 'ID', cell: (client: any) => client.id }, + { columnDef: 'order', header: 'Orden', cell: (client: any) => client.order }, + { columnDef: 'content', header: 'Script', cell: (client: any) => client.content }, + { columnDef: 'type', header: 'Type', cell: (client: any) => client.type }, + { columnDef: 'parameters', header: 'Parameters', cell: (client: any) => client.parameters }, + ]; + + displayedColumns: string[] = ['id', 'order', 'type', 'parameters', 'content', 'actions']; + + constructor( + private toastService: ToastrService, + private http: HttpClient, + public dialogRef: MatDialogRef, + public dialog: MatDialog, + private configService: ConfigService, + @Inject(MAT_DIALOG_DATA) public data: any + ) { + this.baseUrl = this.configService.apiUrl; + } + + ngOnInit(): void { + if (this.data) { + this.loadData(); + } + } + + loadData() { + this.loading = true; + this.http.get(`${this.baseUrl}/command-task-scripts?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}&commandTask.id=${this.data.commandTask?.id}`, { params: this.filters }).subscribe( + (data) => { + this.dataSource.data = data['hydra:member']; + this.length = data['hydra:totalItems']; + this.loading = false; + }, + (error) => { + this.loading = false; + } + ); + } + + deleteTaskScript(schedule: any): void { + const dialogRef = this.dialog.open(DeleteModalComponent, { + width: '300px', + data: { name: 'script de una tarea' } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.http.delete(`${this.baseUrl}${schedule['@id']}`).subscribe( + () => { + this.toastService.success('Eliminado correctamente'); + this.loadData(); + }, + (error) => { + this.toastService.error(error.error['hydra:description']); + } + ); + } + }) + } + + onNoClick(): void { + this.dialogRef.close(false); + } + + openParametersModal(parameters: any): void { + this.dialog.open(ViewParametersModalComponent, { + width: '900px', + data: parameters + }); + } + + onPageChange(event: any) { + this.page = event.pageIndex; + this.itemsPerPage = event.pageSize; + this.loadData() + } +} diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.css b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.css new file mode 100644 index 0000000..e69de29 diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.html b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.html new file mode 100644 index 0000000..8bcddab --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.html @@ -0,0 +1,7 @@ +

Parámetros

+ +
{{ data | json }}
+
+ + + diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.spec.ts b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.spec.ts new file mode 100644 index 0000000..6104c77 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.spec.ts @@ -0,0 +1,69 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ViewParametersModalComponent } from './view-parameters-modal.component'; +import {LoadingComponent} from "../../../../../shared/loading/loading.component"; +import {HttpClientTestingModule, provideHttpClientTesting} from "@angular/common/http/testing"; +import {ToastrModule, ToastrService} from "ngx-toastr"; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {MatDividerModule} from "@angular/material/divider"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatInputModule} from "@angular/material/input"; +import {MatIconModule} from "@angular/material/icon"; +import {MatButtonModule} from "@angular/material/button"; +import {MatTableModule} from "@angular/material/table"; +import {MatPaginatorModule} from "@angular/material/paginator"; +import {MatTooltipModule} from "@angular/material/tooltip"; +import {FormBuilder, FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog"; +import {MatSelectModule} from "@angular/material/select"; +import {MatTabsModule} from "@angular/material/tabs"; +import {MatAutocompleteModule} from "@angular/material/autocomplete"; +import {MatListModule} from "@angular/material/list"; +import {MatCardModule} from "@angular/material/card"; +import {MatMenuModule} from "@angular/material/menu"; +import {MatTreeModule} from "@angular/material/tree"; +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 {provideHttpClient} from "@angular/common/http"; + +describe('ViewParametersModalComponent', () => { + let component: ViewParametersModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ViewParametersModalComponent], + imports: [ + MatDialogModule, + TranslateModule.forRoot(), + ], + providers: [ + FormBuilder, + ToastrService, + provideHttpClient(), + provideHttpClientTesting(), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: {} + } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ViewParametersModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.ts b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.ts new file mode 100644 index 0000000..a19e159 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.ts @@ -0,0 +1,18 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; + +@Component({ + selector: 'app-view-parameters-modal', + templateUrl: './view-parameters-modal.component.html', + styleUrl: './view-parameters-modal.component.css' +}) +export class ViewParametersModalComponent { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any + ) {} + + close(): void { + this.dialogRef.close(); + } +} 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 2e64e69..8bff5aa 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 @@ -189,9 +189,10 @@ export class ExecuteCommandComponent implements OnInit { })); this.router.navigate(['/clients/partition-assistant'], { - queryParams: { clientData: JSON.stringify(clientDataToSend) } - }).then(r => { - console.log('Navigated to partition assistant with data:', this.clientData); + queryParams: { + clientData: JSON.stringify(clientDataToSend), + runScriptContext: JSON.stringify(this.runScriptContext) + } }); } @@ -212,9 +213,10 @@ export class ExecuteCommandComponent implements OnInit { })); this.router.navigate(['/clients/deploy-image'], { - queryParams: { clientData: JSON.stringify(clientDataToSend) } - }).then(r => { - console.log('Navigated to deploy image with data:', this.clientData); + queryParams: { + clientData: JSON.stringify(clientDataToSend), + runScriptContext: JSON.stringify(this.runScriptContext) + } }); } @@ -229,7 +231,7 @@ export class ExecuteCommandComponent implements OnInit { })); this.router.navigate(['/clients/run-script'], { - queryParams: { + queryParams: { clientData: JSON.stringify(clientDataToSend) , runScriptContext: JSON.stringify(this.runScriptContext) } diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css new file mode 100644 index 0000000..fcb5bc8 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css @@ -0,0 +1,358 @@ +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; +} + +.table-header-container { + display: flex; + align-items: center; + padding: 10px; + gap: 20px; +} + +.client-container { + flex-grow: 1; + box-sizing: border-box; + overflow: hidden; + display: flex; + flex-direction: column; + padding: 0rem 1rem 0rem 0.5rem; +} + +.client-icon { + flex-shrink: 0; + margin-right: 20px; + display: flex; + align-items: center; + justify-content: center; + min-width: 120px; + min-height: 120px; +} + +.row-container { + justify-content: space-between; + width: 100%; +} + +.table-container { + padding-right: 10px; +} + +.charts-wrapper { + width: 100%; + margin-top: 20px; +} + +.charts-row { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 20px; +} + +.disk-usage { + text-align: center; + flex: 1; + min-width: 200px; +} + +.circular-chart { + max-width: 150px; + max-height: 150px; + margin: 0 auto; +} + +.chart { + display: flex; + justify-content: center; +} + +.icon-pc { + font-size: 25px; + color: #3b82f6; +} + +.client-title h1 { + font-size: 2rem; + margin-bottom: 10px; +} + +.client-title p { + margin: 2px 0; + font-size: 1rem; + color: #666; +} + +.client-info { + margin: 20px 0; + border-radius: 12px; + background-color: #f5f7fa; + padding: 20px; + border: 2px solid #d1d9e6; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); +} + +.info-section { + background-color: #fff; + border-radius: 12px; +} + +.two-column-table { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-top: 15px; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); +} + +.table-row { + display: flex; + justify-content: space-between; + padding: 10px; + border-bottom: 1px solid #e0e0e0; +} + +.column.property { + font-weight: bold; + text-align: left; + width: 45%; +} + +.column.value { + text-align: right; + width: 45%; +} + +.mat-tab-group { + min-height: 400px; +} + +.mat-tab-body-wrapper { + min-height: inherit; +} + +.info-section h2 { + font-size: 1.4rem; + margin-bottom: 10px; + color: #0056b3; +} + +.info-section p { + font-size: 1rem; + margin: 5px 0; +} + +.second-section { + display: grid; + gap: 20px; +} + +.client-button-row { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 20px; +} + +.buttons-row { + display: flex; + flex-direction: column; + background-color: #fff; + padding: 20px; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + justify-content: flex-start; +} + +.buttons-row button { + margin-bottom: 10px; + width: 100%; +} + +.circular-chart { + display: block; + margin: 0 auto; + max-width: 100%; + max-height: 150px; +} + +.circle-bg { + fill: none; + stroke: #f0f0f0; + stroke-width: 3.8; +} + +.circle { + fill: none; + stroke-width: 3.8; + stroke: #00bfa5; + stroke-linecap: round; + animation: progress 1s ease-out forwards; +} + +.percentage { + fill: #333; + font-size: 0.7rem; + text-anchor: middle; +} + +.disk-usage h3 { + margin: 0 0 10px 0; + font-size: 1.2rem; + color: #333; +} + +@keyframes progress { + 0% { + stroke-dasharray: 0, 100; + } +} + +.assistants-container { + background-color: #fff; + margin-top: 10px; + padding: 20px; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.circular-chart { + display: block; + margin: 0 auto; + max-width: 100%; + max-height: 150px; +} + +.circle-bg { + fill: none; + stroke: #f0f0f0; + stroke-width: 3.8; +} + +.circle { + fill: none; + stroke-width: 3.8; + stroke-linecap: round; + animation: progress 1s ease-out forwards; +} + +.partition-0 { + stroke: #00bfa5; +} + +.partition-1 { + stroke: #ff6f61; +} + +.partition-2 { + stroke: #ffb400; +} + +.partition-3 { + stroke: #3498db; +} + +.percentage { + fill: #333; + font-size: 0.7rem; + text-anchor: middle; +} + +.disk-container { + display: flex; + flex-direction: row; + gap: 20px; + background-color: #f5f7fa; + border: 2px solid #d1d9e6; + border-radius: 10px; + padding: 20px; + margin-top: 20px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); + flex-wrap: wrap; + justify-content: center; + align-items: stretch; + margin-bottom: 20px; +} + +.table-container { + flex: 5; + display: flex; + justify-content: center; + align-items: center; +} + +table.mat-elevation-z8 { + width: 100%; + max-width: 800px; + background-color: white; + border-radius: 8px; + overflow: hidden; +} + +.mat-header-cell { + background-color: #d1d9e6 !important; + color: #333; + font-weight: bold; + text-align: center; +} + +.mat-cell { + text-align: center; +} + +.mat-chip { + font-weight: bold; + color: white; +} + +.charts-container { + flex: 2; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.disk-usage { + background-color: white; + padding: 15px; + border-radius: 8px; + justify-self: center; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); + text-align: center; +} + +.chart { + display: flex; + justify-content: center; +} + +.back-button { + display: flex; + align-items: center; + gap: 5px; + margin-left: 0.5em; + background-color: #3f51b5; + color: white; + padding: 8px 18px 8px 8px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition: transform 0.3s ease; + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +.back-button:hover:not(:disabled) { + background-color: #485ac0d7; +} + +.back-button:disabled { + background-color: #ced0df; + cursor: not-allowed; +} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html new file mode 100644 index 0000000..31e967e --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html @@ -0,0 +1,72 @@ + + +
+
+

{{ 'clientDetailsTitle' | translate }} {{ clientData.name }}

+
+ +
+
+ + + +
+
+
+
+
{{ clientData?.property }}
+
{{ clientData?.value }}
+
+
+
+
+
{{ clientData?.property }}
+
{{ clientData?.value }}
+
+
+
+
+
+

Discos/Particiones

+ {{ clientData.firmwareType }} +
+ +
+
+ + + + + + + +
{{ column.header }} + + {{ column.cell(image) }} + + + + {{ (image.size / 1024).toFixed(2) }} GB + + +
+
+ +
+ +
+ + + +

Disco {{ disk.diskNumber }}

+

Usado: {{ (disk.used).toFixed(2) }} GB ({{ disk.percentage }}%)

+

Total: {{ disk.total }} GB

+
+
+
+
+
+ diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html index a967fef..53946f1 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html @@ -5,11 +5,22 @@

{{ 'deployImage' | translate }}

+

+ {{ runScriptTitle }} +

+ +
+ +
+ +
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts index 9f1620f..fc747c7 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts @@ -1,17 +1,19 @@ -import { Component, EventEmitter, Output } from '@angular/core'; +import {Component, EventEmitter, OnInit, Output} from '@angular/core'; import { MatTableDataSource } from "@angular/material/table"; import { SelectionModel } from "@angular/cdk/collections"; import { HttpClient } from "@angular/common/http"; import { ToastrService } from "ngx-toastr"; import { ActivatedRoute, Router } from "@angular/router"; import { ConfigService } from '@services/config.service'; +import {CreateTaskComponent} from "../../../../commands/commands-task/create-task/create-task.component"; +import {MatDialog} from "@angular/material/dialog"; @Component({ selector: 'app-deploy-image', templateUrl: './deploy-image.component.html', styleUrl: './deploy-image.component.css' }) -export class DeployImageComponent { +export class DeployImageComponent implements OnInit{ baseUrl: string; @Output() dataChange = new EventEmitter(); @@ -34,6 +36,7 @@ export class DeployImageComponent { clientData: any = []; loading: boolean = false; allSelected = true; + runScriptContext: any = null; protected p2pModeOptions = [ { name: 'Leecher', value: 'leecher' }, @@ -95,13 +98,17 @@ export class DeployImageComponent { private toastService: ToastrService, private configService: ConfigService, private router: Router, - private route: ActivatedRoute + private route: ActivatedRoute, + private dialog: MatDialog, ) { this.baseUrl = this.configService.apiUrl; this.route.queryParams.subscribe(params => { if (params['clientData']) { this.clientData = JSON.parse(params['clientData']); } + if (params['runScriptContext']) { + this.runScriptContext = params['runScriptContext']; + } }); this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null; this.clientData.forEach((client: { selected: boolean; status: string}) => { @@ -122,6 +129,27 @@ export class DeployImageComponent { } } + ngOnInit(): void { + this.route.queryParams.subscribe(params => { + this.runScriptContext = params['runScriptContext'] ? JSON.parse(params['runScriptContext']) : null; + }); + } + + get runScriptTitle(): string { + const ctx = this.runScriptContext; + if (!ctx) { + return ''; + } + if (Array.isArray(ctx)) { + return ctx.map(c => c.name).join(', '); + } + if (typeof ctx === 'object' && 'name' in ctx) { + return ctx.name; + } + return String(ctx); + } + + isMethod(method: string): boolean { return this.selectedMethod === method; } @@ -291,4 +319,47 @@ export class DeployImageComponent { } }); } + + + openScheduleModal(): void { + const dialogRef = this.dialog.open(CreateTaskComponent, { + width: '800px', + data: { + scope: this.runScriptContext.type, + organizationalUnit: this.runScriptContext['@id'] + } + }); + + dialogRef.afterClosed().subscribe((result: { [x: string]: any; }) => { + if (result) { + const payload = { + method: this.selectedMethod, + diskNumber: this.selectedPartition.diskNumber, + partitionNumber: this.selectedPartition.partitionNumber, + p2pMode: this.selectedMethod === 'torrent' ? this.p2pMode : null, + p2pTime: this.selectedMethod === 'torrent' ? this.p2pTime : null, + mcastIp: this.selectedMethod === 'multicast' ? this.mcastIp : null, + mcastPort: this.selectedMethod === 'multicast' ? this.mcastPort : null, + mcastMode: this.selectedMethod === 'multicast' ? this.mcastMode : null, + mcastSpeed: this.selectedMethod === 'multicast' ? this.mcastSpeed : null, + maxTime: this.selectedMethod === 'multicast' ? this.mcastMaxTime : null, + maxClients: this.selectedMethod === 'multicast' ? this.mcastMaxClients : null, + }; + + this.http.post(`${this.baseUrl}/command-task-scripts`, { + commandTask: result['@id'], + parameters: payload, + order: 1, + type: 'deploy-image', + }).subscribe({ + next: () => { + this.toastService.success('Script añadido con éxito a la tarea'); + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }) + } + }); + } } diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css index 1a1ea81..ced0932 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css @@ -50,16 +50,6 @@ padding-top: 10px; } -button { - border: none; - padding: 10px 20px; - border-radius: 4px; - font-size: 1rem; - font-weight: 500; - cursor: pointer; - transition: background-color 0.3s ease; -} - button.mat-button { background-color: #007bff; color: white; @@ -82,6 +72,7 @@ button.remove-btn { background-color: #dc3545; color: white; border-radius: 4px; + padding: 7px; } button.remove-btn:hover { @@ -186,6 +177,7 @@ button.remove-btn:hover { width: 100%; box-sizing: border-box; padding-left: 1em; + padding-right: 1em; } .client-card { diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html index 100ace3..3d59ac2 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html @@ -2,12 +2,21 @@
-

- Asistente de particionado +

+ {{ 'partitionTitle' | translate }}

+

+ {{ runScriptTitle }} +

-
- +
+ +
+ +
+
@@ -80,7 +89,7 @@
- Tabla de particiones: {{ selectedModelClient.firmwareType }} + Tabla de firmware: {{ selectedModelClient.firmwareType }}
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts index 14273fb..3bf2684 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts @@ -5,6 +5,8 @@ import {ActivatedRoute, Router} from "@angular/router"; import { PARTITION_TYPES } from '../../../../../shared/constants/partition-types'; import { FILESYSTEM_TYPES } from '../../../../../shared/constants/filesystem-types'; import { ConfigService } from '@services/config.service'; +import {CreateTaskComponent} from "../../../../commands/commands-task/create-task/create-task.component"; +import {MatDialog} from "@angular/material/dialog"; interface Partition { uuid?: string; @@ -25,7 +27,7 @@ interface Partition { templateUrl: './partition-assistant.component.html', styleUrls: ['./partition-assistant.component.css'] }) -export class PartitionAssistantComponent { +export class PartitionAssistantComponent implements OnInit{ baseUrl: string; private apiUrl: string; @Output() dataChange = new EventEmitter(); @@ -41,6 +43,7 @@ export class PartitionAssistantComponent { disks: { diskNumber: number; totalDiskSize: number; partitions: Partition[]; chartData: any[]; used: number; percentage: number }[] = []; clientData: any = []; loading: boolean = false; + runScriptContext: any = null; view: [number, number] = [400, 300]; showLegend = true; @@ -55,6 +58,7 @@ export class PartitionAssistantComponent { private route: ActivatedRoute, private router: Router, private configService: ConfigService, + private dialog: MatDialog, ) { this.baseUrl = this.configService.apiUrl; this.apiUrl = this.baseUrl + '/partitions'; @@ -62,8 +66,11 @@ export class PartitionAssistantComponent { if (params['clientData']) { this.clientData = JSON.parse(params['clientData']); } + if (params['runScriptContext']) { + this.runScriptContext = params['runScriptContext']; + } }); - this.clientId = this.clientData?.[0]['@id']; + this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null; this.clientData.forEach((client: { selected: boolean; status: string}) => { if (client.status === 'og-live') { client.selected = true; @@ -83,6 +90,12 @@ export class PartitionAssistantComponent { } } + ngOnInit(): void { + this.route.queryParams.subscribe(params => { + this.runScriptContext = params['runScriptContext'] ? JSON.parse(params['runScriptContext']) : null; + }); + } + get selectedDisk():any { return this.disks.find(disk => disk.diskNumber === this.selectedDiskNumber) || null; } @@ -103,6 +116,20 @@ export class PartitionAssistantComponent { ); } + get runScriptTitle(): string { + const ctx = this.runScriptContext; + if (!ctx) { + return ''; + } + if (Array.isArray(ctx)) { + return ctx.map(c => c.name).join(', '); + } + if (typeof ctx === 'object' && 'name' in ctx) { + return ctx.name; + } + return String(ctx); + } + toggleSelectAll() { this.allSelected = !this.allSelected; this.clientData.forEach((client: { selected: boolean; status: string }) => { @@ -380,4 +407,57 @@ export class PartitionAssistantComponent { disk.used = this.calculateUsedSpace(disk.partitions); disk.percentage = (disk.used / disk.totalDiskSize) * 100; } + + openScheduleModal(): void { + const dialogRef = this.dialog.open(CreateTaskComponent, { + width: '800px', + data: { + scope: this.runScriptContext.type, + organizationalUnit: this.runScriptContext['@id'] + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + const modifiedPartitions = this.selectedDisk.partitions.filter((partition: { removed: any; format: any; }) => !partition.removed || partition.format); + + if (modifiedPartitions.length === 0) { + this.loading = false; + this.toastService.info('No hay cambios para guardar en el disco seleccionado.'); + return; + } + + const newPartitions = modifiedPartitions.map((partition: { partitionNumber: any; memoryUsage: any; size: any; partitionCode: any; filesystem: any; uuid: any; removed: any; format: any; }) => ({ + diskNumber: this.selectedDisk.diskNumber, + partitionNumber: partition.partitionNumber, + memoryUsage: partition.memoryUsage, + size: partition.size, + partitionCode: partition.partitionCode, + filesystem: partition.filesystem, + uuid: partition.uuid, + removed: partition.removed || false, + format: partition.format || false, + })); + + const bulkPayload = { + partitions: newPartitions, + clients: this.selectedClients.map((client: any) => client.uuid), + }; + + this.http.post(`${this.baseUrl}/command-task-scripts`, { + commandTask: result['@id'], + parameters: bulkPayload.partitions, + order: 1, + type: 'partition-assistant', + }).subscribe({ + next: () => { + this.toastService.success('Script añadido con éxito a la tarea'); + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }) + } + }); + } } diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.css index 1f6be0a..7c31624 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.css +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.css @@ -261,4 +261,15 @@ table { width: 100%; } +.script-selector-card { + margin: 20px 20px; + padding: 16px; +} + +.toggle-options { + display: flex; + justify-content: start; + margin: 16px 0; +} + diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html index a581a6a..74465cf 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html @@ -3,12 +3,21 @@

- {{ 'runScript' | translate }} {{runScriptTitle}} + {{ 'runScript' | translate }}

+

+ {{ runScriptTitle }} +

+ +
+ +
@@ -55,12 +64,18 @@ -
-
- - Comando nuevo - Comando existente - + + Seleccione el tipo de comando + +
+ + + edit Nuevo Script + + + storage Script Guardado + +
@@ -68,10 +83,10 @@ Ingrese el script - +
-
+
Seleccione script a ejecutar @@ -82,22 +97,20 @@
-

Script:

+

Script:

-

Ingrese los valores de los parámetros detectados:

+

Ingrese los parámetros:

{{ paramName }} - +
-
+ + diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.spec.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.spec.ts index 8b6edc1..d4531e6 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.spec.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.spec.ts @@ -26,6 +26,8 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import {MatIconModule} from "@angular/material/icon"; +import {MatCardModule} from "@angular/material/card"; +import {MatButtonToggleModule} from "@angular/material/button-toggle"; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http); @@ -59,6 +61,8 @@ describe('RunScriptAssistantComponent', () => { MatSelectModule, BrowserAnimationsModule, MatIconModule, + MatCardModule, + MatButtonToggleModule, ToastrModule.forRoot(), HttpClientTestingModule, TranslateModule.forRoot({ diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts index bd93e49..56dd99e 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Output } from '@angular/core'; +import {Component, EventEmitter, OnInit, Output} from '@angular/core'; import { SelectionModel } from "@angular/cdk/collections"; import { HttpClient } from "@angular/common/http"; import { ToastrService } from "ngx-toastr"; @@ -6,13 +6,14 @@ import { ConfigService } from "@services/config.service"; import { ActivatedRoute, Router } from "@angular/router"; import { SaveScriptComponent } from "./save-script/save-script.component"; import { MatDialog } from "@angular/material/dialog"; +import {CreateTaskComponent} from "../../../../commands/commands-task/create-task/create-task.component"; @Component({ selector: 'app-run-script-assistant', templateUrl: './run-script-assistant.component.html', styleUrl: './run-script-assistant.component.css' }) -export class RunScriptAssistantComponent { +export class RunScriptAssistantComponent implements OnInit{ baseUrl: string; @Output() dataChange = new EventEmitter(); @@ -52,24 +53,25 @@ export class RunScriptAssistantComponent { } }); this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null; - this.clientData.forEach((client: { selected: boolean; status: string }) => { + this.clientData.forEach((client: { selected: boolean; status: string}) => { if (client.status === 'og-live') { client.selected = true; } }); + this.selectedClients = this.clientData.filter( (client: { status: string }) => client.status === 'og-live' ); + this.loadScripts() } ngOnInit(): void { this.route.queryParams.subscribe(params => { this.runScriptContext = params['runScriptContext'] ? JSON.parse(params['runScriptContext']) : null; - this.clientData = params['clientData'] ? JSON.parse(params['clientData']) : []; }); } - + get runScriptTitle(): string { const ctx = this.runScriptContext; if (!ctx) { @@ -179,11 +181,6 @@ export class RunScriptAssistantComponent { this.scriptContent = updatedScript; } - trackByIndex(index: number): number { - return index; - } - - save(): void { this.loading = true; @@ -203,4 +200,32 @@ export class RunScriptAssistantComponent { this.loading = false; } + + openScheduleModal(): void { + const dialogRef = this.dialog.open(CreateTaskComponent, { + width: '800px', + data: { + scope: this.runScriptContext.type, + organizationalUnit: this.runScriptContext['@id'] + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.http.post(`${this.baseUrl}/command-task-scripts`, { + commandTask: result['@id'], + content: this.commandType === 'existing' ? this.scriptContent : this.newScript, + order: 1, + type: 'run-script', + }).subscribe({ + next: () => { + this.toastService.success('Script añadido con éxito a la tarea'); + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }) + } + }); + } } diff --git a/ogWebconsole/src/app/layout/main-layout/main-layout.component.css b/ogWebconsole/src/app/layout/main-layout/main-layout.component.css index f8efe00..918a88a 100644 --- a/ogWebconsole/src/app/layout/main-layout/main-layout.component.css +++ b/ogWebconsole/src/app/layout/main-layout/main-layout.component.css @@ -1,5 +1,4 @@ .container { - width: 100vw; height: calc(100vh - 7vh); min-width: 375px; } @@ -7,4 +6,4 @@ .sidebar { width: 15vw; min-width: 250px; -} \ No newline at end of file +} diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index 95a731c..aeab939 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -18,11 +18,14 @@ "addUserlabelUsername": "Username", "addUserlabelPassword": "Password", "labelRole": "Role", + "partitionTitle": "Partitions assistant", "labelOrganizationalUnit": "Organizational Unit", "buttonCancel": "Cancel", "buttonAdd": "Add", "firmwareType": "Firmware", "addRule": "Add rule", + "remotePcStatusUnavailable": "Remote PC status unavailable", + "remotePcStatusAvailable": "Remote PC status available", "rulesHeader": "Rules", "statusUnavailable": "Unavailable", "statusAvailable": "Available", @@ -58,7 +61,7 @@ "tableStepText": "Here are the existing calendars with their characteristics and settings.", "actionsStepText": "Access the available actions for each calendar here.", "editCalendar": "Edit calendar", - "remoteAvailability": "Remote availability?", + "remoteAvailability": "Check availability to RemotePC", "selectWeekDays": "Select the days of the week", "startTime": "Start time", "startTimePlaceholder": "Select start time", @@ -245,8 +248,9 @@ "selectCommandGroupLabel": "Select Command Group", "noClientsMessage": "No clients available", "editClientDialogTitle": "Edit Client", - "organizationalUnitLabel": "Parent", + "organizationalUnitLabel": "Organizational Unit", "ogLiveLabel": "OgLive", + "notesLabel": "Notes", "serialNumberLabel": "Serial Number", "netifaceLabel": "Network interface", "netDriverLabel": "Network driver", diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 28a8640..74bf1d2 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -38,7 +38,9 @@ "addRule": "Añadir regla", "rulesHeader": "Reglas", "statusUnavailable": "No disponible", + "remotePcStatusUnavailable": "No disponible para Remote PC", "statusAvailable": "Disponible", + "remotePcStatusAvailable": "Disponible para Remote PC", "parameters": "Parámetros", "labelRoleName": "Nombre", "runScript": "Ejecutar comando", @@ -59,7 +61,7 @@ "tableStepText": "Aquí se muestran los calendarios existentes con sus características y configuraciones.", "actionsStepText": "Accede a las acciones disponibles para cada calendario aquí.", "editCalendar": "Editar calendario", - "remoteAvailability": "¿Disponibilidad remota?", + "remoteAvailability": "Marcar como disponible para Remote PC", "selectWeekDays": "Selecciona los días de la semana", "startTime": "Hora de inicio", "startTimePlaceholder": "Selecciona la hora de inicio", @@ -244,8 +246,9 @@ "selectCommandGroupLabel": "Seleccione Grupo de Comandos", "noClientsMessage": "No hay clientes disponibles", "editClientDialogTitle": "Editar Cliente", - "organizationalUnitLabel": "Padre", + "organizationalUnitLabel": "Unidad organizativa", "ogLiveLabel": "OgLive", + "notesLabel": "Notas", "imageNameLabel": "Nombre de la imagen", "importImageButton": "Importar imagen", "convertImageButton": "Convertir imagen virtual", @@ -281,6 +284,7 @@ "excludeParentChanges": "Excluir cambios de las unidades organizativas superiores", "networkPropertiesTab": "Propiedades de red", "disksPartitionsTitle": "Discos/Particiones", + "partitionTitle": "Asistente de particionado", "diskTitle": "Disco", "diskUsedLabel": "Usado", "diskTotalLabel": "Total", From 83ab784c48b58981bc018ebcec1ee8f9bab57dcf Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 30 Apr 2025 13:16:18 +0200 Subject: [PATCH 17/47] refs #1922. Show new fields in clients table. Fixed wrong details --- .../classroom-view.component.css | 13 +++--- .../classroom-view.component.html | 4 +- .../client-details.component.css | 28 ++++++++++-- .../client-details.component.html | 11 +++-- .../client-details.component.ts | 45 ++++--------------- .../manage-client.component.html | 2 +- .../manage-client/manage-client.component.ts | 8 ++-- .../manage-organizational-unit.component.html | 26 +++++++---- .../manage-organizational-unit.component.ts | 45 ++++++++++++------- 9 files changed, 103 insertions(+), 79 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.css b/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.css index 4413607..46fede3 100644 --- a/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.css +++ b/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.css @@ -21,21 +21,24 @@ mat-card { } .client-image { - width: 100%; + display: flex; + justify-content: center; + width: 70%; height: auto; + margin: 0 auto; } .proyector-image { width: auto; - height: 100px; + height: 80px; } .client-info { + display: grid; text-align: center; - margin-top: 5px; - font-size: medium; color: gray; align-items: center; + font-size: small; } .client-name { @@ -131,4 +134,4 @@ mat-dialog-content { .submit-button { margin: 1rem; -} \ No newline at end of file +} diff --git a/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.html b/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.html index c5b1bbd..fbe9a77 100644 --- a/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.html +++ b/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.html @@ -12,7 +12,9 @@ {{ 'clientAlt' | translate }}
- {{ client.name }} + {{ client.name }} + {{ client.ip }} + {{ client.mac }}
diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css index 902cb8f..0aa7a6d 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css @@ -62,7 +62,8 @@ } .table-container { - flex: 1 1 500px; + flex: 5; + display: flex; overflow-x: auto; } @@ -70,7 +71,7 @@ display: flex; flex-wrap: wrap; gap: 24px; - flex: 1 1 300px; + flex: 2; justify-content: flex-start; } @@ -85,6 +86,13 @@ table { box-shadow: none; } +.table-header-container { + display: flex; + align-items: center; + padding: 10px; + gap: 20px; +} + .charts-container.single-disk { justify-content: center; } @@ -107,4 +115,18 @@ table { width: 100%; height: 160px; margin-bottom: 12px; -} \ No newline at end of file +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} + +.client-container { + display: flex; + flex-direction: column; + gap: 1em; + margin-bottom: 40px; +} diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html index 9e8edc0..8beb74e 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html @@ -23,10 +23,12 @@
-
-

Discos/Particiones

+
+

Discos/Particiones

+ {{ clientData.firmwareType }}
+
@@ -63,4 +65,7 @@
- \ No newline at end of file + + + + diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts index 919e84e..788cb42 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts @@ -64,6 +64,11 @@ export class ClientDetailsComponent { header: 'Particion', cell: (partition: any) => `${partition.partitionNumber}` }, + { + columnDef: 'partitionCode', + header: 'Tipo de partición', + cell: (partition: any) => `${partition.partitionCode}` + }, { columnDef: 'description', header: 'Sistema de ficheros', @@ -115,7 +120,7 @@ export class ClientDetailsComponent { console.error('No se recibieron datos del cliente.'); } } - + loadClient = (uuid: string) => { this.http.get(`${this.baseUrl}${uuid}`).subscribe({ next: data => { @@ -225,42 +230,8 @@ export class ClientDetailsComponent { }); } - - loadCommands(): void { - this.http.get(`${this.baseUrl}/commands?`).subscribe({ - next: data => { - this.commands = data['hydra:member']; - }, - error: error => { - console.error('Error al obtener las particiones:', error); - } - }); - } - - onCommandSelect(action: any): void { - if (action === 'partition') { - this.openPartitionAssistant(); - } - - if (action === 'create-image') { - this.openCreateImageAssistant(); - } - - if (action === 'deploy-image') { - this.openDeployImageAssistant(); - } - - if (action === 'reboot') { - this.rebootClient(); - } - - if (action === 'power-off') { - this.powerOffClient(); - } - - if (action === 'power-on') { - this.powerOnClient(); - } + onNoClick(): void { + this.dialog.closeAll(); } rebootClient(): void { diff --git a/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.html b/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.html index 1a4d246..69f2a51 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.html +++ b/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.html @@ -67,7 +67,7 @@ {{ 'templateLabel' | translate }} - + {{ template.name }} diff --git a/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.ts b/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.ts index 163fbcb..d38075c 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.ts @@ -86,7 +86,7 @@ export class ManageClientComponent implements OnInit { netDriver: null, mac: ['', Validators.pattern(/^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/)], ip: ['', Validators.required], - template: [null], + pxeTemplate: [null], hardwareProfile: [null], ogLive: [null], repository: [null], @@ -110,7 +110,8 @@ export class ManageClientComponent implements OnInit { repository: unit.networkSettings?.repository?.['@id'], hardwareProfile: unit.networkSettings?.hardwareProfile?.['@id'], ogLive: unit.networkSettings?.ogLive?.['@id'], - menu: unit.networkSettings?.menu?.['@id'] + menu: unit.networkSettings?.menu?.['@id'], + pxeTemplate: unit.networkSettings?.pxeTemplate?.['@id'], })); const initialUnitId = this.clientForm.get('organizationalUnit')?.value; @@ -229,6 +230,7 @@ export class ManageClientComponent implements OnInit { ogLive: selectedUnit.ogLive || null, menu: selectedUnit.menu || null, netiface: selectedUnit.netiface || null, + pxeTemplate: selectedUnit.pxeTemplate || null }); } } @@ -250,7 +252,7 @@ export class ManageClientComponent implements OnInit { organizationalUnit: data.organizationalUnit ? data.organizationalUnit['@id'] : null, repository: data.repository ? data.repository['@id'] : null, ogLive: data.ogLive ? data.ogLive['@id'] : null, - template: data.template ? data.template['@id'] : null, + pxeTemplate: data.pxeTemplate ? data.pxeTemplate['@id'] : null, menu: data.menu ? data.menu['@id'] : null, maintenance: data.maintenance }); diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html index dc980dd..a72d549 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html @@ -18,7 +18,7 @@ - Padre + Unidad organizativa superior {{ getSelectedParentName() }} @@ -72,14 +72,22 @@ Configuración de Red
- - OgLive - - - {{ oglive.name }} - - - + + OgLive + + + {{ oglive.name }} + + + + + Plantilla PXE + + + {{ template.name }} + + + Repositorio diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.ts b/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.ts index 87c49df..1ae3a86 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.ts @@ -30,6 +30,7 @@ export class ManageOrganizationalUnitComponent implements OnInit { isEditMode: boolean; currentCalendar: any = []; ogLives: any[] = []; + pxeTemplates: any[] = []; menus: any[] = []; repositories: any[] = []; parentUnitsWithPaths: { id: string, name: string, path: string }[] = []; @@ -77,6 +78,7 @@ export class ManageOrganizationalUnitComponent implements OnInit { this.networkSettingsFormGroup = this._formBuilder.group({ ogLive: [null], repository: [null], + pxeTemplate: [null], proxy: [null], dns: [null], netmask: [null], @@ -111,6 +113,7 @@ export class ManageOrganizationalUnitComponent implements OnInit { this.loadOgLives(), this.loadRepositories(), this.loadMenus(), + this.loadPxeTemplates() ]; Promise.all(observables).then(() => { @@ -144,6 +147,7 @@ export class ManageOrganizationalUnitComponent implements OnInit { repository: unit.networkSettings?.repository?.['@id'], hardwareProfile: unit.networkSettings?.hardwareProfile?.['@id'], ogLive: unit.networkSettings?.ogLive?.['@id'], + pxeTemplate: unit.networkSettings?.pxeTemplate?.['@id'], menu: unit.networkSettings?.menu?.['@id'], mcastIp: unit.networkSettings?.mcastIp, mcastSpeed: unit.networkSettings?.mcastSpeed, @@ -183,6 +187,7 @@ export class ManageOrganizationalUnitComponent implements OnInit { repository: selectedUnit.repository || null, hardwareProfile: selectedUnit.hardwareProfile || null, ogLive: selectedUnit.ogLive || null, + pxeTemplate: selectedUnit.pxeTemplate || null, menu: selectedUnit.menu || null, mcastIp: selectedUnit.mcastIp || null, mcastSpeed: selectedUnit.mcastSpeed || null, @@ -219,6 +224,23 @@ export class ManageOrganizationalUnitComponent implements OnInit { }); } + loadPxeTemplates(): Promise { + return new Promise((resolve, reject) => { + const url = `${this.baseUrl}/pxe-templates?page=1&itemsPerPage=10000`; + + this.http.get(url).subscribe( + response => { + this.pxeTemplates = response['hydra:member']; + resolve(); + }, + error => { + console.error('Error fetching pxe templates:', error); + reject(error); + } + ); + }); + } + loadOgLives(): Promise { return new Promise((resolve, reject) => { const url = `${this.baseUrl}/og-lives?page=1&itemsPerPage=30`; @@ -287,22 +309,6 @@ export class ManageOrganizationalUnitComponent implements OnInit { }); } - loadCurrentCalendar(uuid: string): void { - this.loading = true; - const apiUrl = `${this.baseUrl}/remote-calendars/${uuid}`; - this.http.get(apiUrl).subscribe( - response => { - this.currentCalendar = response; - this.loading = false; - }, - error => { - console.error('Error loading current calendar', error); - this.toastService.error('Error loading current calendar'); - this.loading = false; - } - ); - } - onCalendarChange(event: any) { this.generalFormGroup.value.remoteCalendar = event.value; } @@ -315,6 +321,10 @@ export class ManageOrganizationalUnitComponent implements OnInit { this.networkSettingsFormGroup.value.repository = event.value; } + onPxeTemplateChange(event: any) { + this.networkSettingsFormGroup.value.pxeTemplate = event.value; + } + loadData(uuid: string): Promise { return new Promise((resolve, reject) => { const url = `${this.baseUrl}/organizational-units/${uuid}`; @@ -347,7 +357,8 @@ export class ManageOrganizationalUnitComponent implements OnInit { menu: data.networkSettings?.menu ? data.networkSettings.menu['@id'] : null, hardwareProfile: data.networkSettings?.hardwareProfile ? data.networkSettings.hardwareProfile['@id'] : null, ogLive: data.networkSettings?.ogLive ? data.networkSettings.ogLive['@id'] : null, - repository: data.networkSettings?.repository ? data.networkSettings.repository['@id'] : null + repository: data.networkSettings?.repository ? data.networkSettings.repository['@id'] : null, + pxeTemplate: data.networkSettings?.pxeTemplate ? data.networkSettings.pxeTemplate['@id'] : null }); this.classroomInfoFormGroup.patchValue({ location: data.location, From 1e051767587f978c46f7f632ae2ec1c016ff117c Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 30 Apr 2025 13:17:16 +0200 Subject: [PATCH 18/47] refs #1827. UX changes. New calendar styles --- .../create-calendar-rule.component.css | 79 ++++++++++++++++++- .../create-calendar-rule.component.html | 66 +++++++++++++--- .../create-calendar-rule.component.ts | 8 +- .../create-calendar.component.css | 30 ++++--- .../create-calendar.component.html | 18 +++-- 5 files changed, 167 insertions(+), 34 deletions(-) diff --git a/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.css b/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.css index 2f3d78a..f0c9645 100644 --- a/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.css +++ b/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.css @@ -22,7 +22,12 @@ .time-fields { display: flex; - gap: 15px; /* Espacio entre los campos */ + gap: 15px; +} + +.hour-fields { + display: flex; + gap: 15px; } .time-field { @@ -34,4 +39,74 @@ justify-content: flex-end; gap: 1em; padding: 1.5em; -} \ No newline at end of file +} + +.custom-text { + font-style: italic; + font-size: 0.875rem; + color: #666; + margin: 4px 0 12px; +} + +.weekday-toggle-group { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin: 40px 0 40px 0; + width: 100%; + justify-content: space-between; +} + +.weekday-toggle { + flex: 1 1 calc(14.28% - 10px); + padding: 10px 0; + border-radius: 999px; + border: 1px solid #ccc; + background-color: #f5f5f5; + cursor: pointer; + font-size: 14px; + text-align: center; + transition: all 0.2s ease; + min-width: 40px; +} + +.weekday-toggle.selected { + background-color: #1976d2; + color: white; + border-color: #1976d2; +} + + +.availability-summary { + background-color: #e3f2fd; + border-left: 4px solid #2196f3; + padding: 12px 16px; + margin-top: 16px; + border-radius: 6px; + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; +} + +.summary-text { + color: #0d47a1; + line-height: 1.4; +} + +.unavailability-summary { + background-color: #ffebee; + border-left: 4px solid #d32f2f; + padding: 12px 16px; + margin-top: 16px; + border-radius: 6px; + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; +} + +.summary-text { + color: #b71c1c; + line-height: 1.4; +} diff --git a/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.html b/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.html index b1de852..8e24c5c 100644 --- a/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.html +++ b/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.html @@ -1,24 +1,26 @@

{{ isEditMode ? ('editCalendar' | translate) : ('addCalendar' | translate) }}

- + {{ 'remoteAvailability' | translate }} - + + +
{{ 'selectWeekDays' | translate }} -
-
- - {{ day }} - -
-
- - {{ day }} - -
+

(Los dias y horas seleccionados se marcarán como aula no disponible para remote pc.)

+
+
+
{{ 'startTime' | translate }} @@ -30,12 +32,24 @@
+ + + +
+ block + + El aula estará no disponible para Remote PC los días: + {{ getSelectedDays().join(', ') }} de + {{ busyFromHour }} a {{ busyToHour }}. + +
{{ 'reasonLabel' | translate }} + Razón por la cual el aula SI está disponible para su uso en Remote PC
@@ -53,6 +67,32 @@
+
+ + {{ 'startTime' | translate }} + + + + + {{ 'endTime' | translate }} + + +
+ + + +
+ info + + El aula estará disponible para reserva desde el + {{ availableFromDate | date:'fullDate' }} hasta el + {{ availableToDate | date:'fullDate' }} + + en el horario de {{ busyFromHour }} a {{ busyToHour }}. + + +
+
diff --git a/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.ts b/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.ts index 716818f..c9839cb 100644 --- a/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.ts +++ b/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.ts @@ -64,8 +64,8 @@ export class CreateCalendarRuleComponent { this.dialogRef.close(); } - toggleAdditionalForm(): void { - this.showAdditionalForm = !this.showAdditionalForm; + getSelectedDays(): string[] { + return Object.keys(this.busyWeekDays || {}).filter(day => this.busyWeekDays[day]); } getSelectedDaysIndices() { @@ -93,7 +93,7 @@ export class CreateCalendarRuleComponent { this.http.put(`${this.baseUrl}${this.ruleId}`, formData) .subscribe({ next: (response) => { - this.toastService.success('Calendar updated successfully'); + this.toastService.success('Calendar rule updated successfully'); this.dialogRef.close(true); }, error: (error) => { @@ -105,7 +105,7 @@ export class CreateCalendarRuleComponent { this.http.post(`${this.baseUrl}/remote-calendar-rules`, formData) .subscribe({ next: (response) => { - this.toastService.success('Calendar created successfully'); + this.toastService.success('Calendar rule created successfully'); this.dialogRef.close(true); }, error: (error) => { diff --git a/ogWebconsole/src/app/components/calendar/create-calendar/create-calendar.component.css b/ogWebconsole/src/app/components/calendar/create-calendar/create-calendar.component.css index fa815f9..cbad9e3 100644 --- a/ogWebconsole/src/app/components/calendar/create-calendar/create-calendar.component.css +++ b/ogWebconsole/src/app/components/calendar/create-calendar/create-calendar.component.css @@ -25,7 +25,7 @@ .time-fields { display: flex; - gap: 15px; /* Espacio entre los campos */ + gap: 15px; } .time-field { @@ -34,24 +34,25 @@ .list-item-content { display: flex; - align-items: flex-start; /* Alinea el contenido al inicio */ - justify-content: space-between; /* Espacio entre los textos y los íconos */ - width: 100%; /* Asegúrate de que el contenido ocupe todo el ancho */ + align-items: flex-start; + justify-content: space-between; + width: 100%; } .text-content { - flex-grow: 1; /* Permite que este contenedor ocupe el espacio disponible */ - margin-right: 16px; /* Espaciado a la derecha para separar de los íconos */ + flex-grow: 1; + margin-right: 16px; margin-left: 10px; + margin-bottom: 16px; } .icon-container { display: flex; - align-items: center; /* Alinea los íconos verticalmente */ + align-items: center; } .right-icon { - margin-left: 8px; /* Espaciado entre los íconos */ + margin-left: 8px; cursor: pointer; } @@ -60,4 +61,15 @@ justify-content: flex-end; gap: 1em; padding: 1.5em; -} \ No newline at end of file +} + +.rule-available { + background-color: #e8f5e9; + border-left: 4px solid #4caf50; +} + +.rule-unavailable { + background-color: #ffebee; + border-left: 4px solid #f44336; +} + diff --git a/ogWebconsole/src/app/components/calendar/create-calendar/create-calendar.component.html b/ogWebconsole/src/app/components/calendar/create-calendar/create-calendar.component.html index fd76357..52dc365 100644 --- a/ogWebconsole/src/app/components/calendar/create-calendar/create-calendar.component.html +++ b/ogWebconsole/src/app/components/calendar/create-calendar/create-calendar.component.html @@ -6,7 +6,7 @@ mode_edit - +
{{ 'rulesHeader' | translate }}
{{ 'pxeTemplate' | translate }} {{ client.template?.name }} {{ client.pxeTemplate?.name }}
+
+ + + + - - + + + - - - - - - - + + - - +
Disco {{ element.diskNumber }} Partición {{ element.partitionNumber }} Tipo {{ element.partitionCode }} Tamaño{{ element.size }}Tamaño (MB){{ element.size | number }} Fyle System{{ element.filesystem }}Memoria{{ element.memoryUsage }}File System + {{ element.filesystem || '-' }} + Resumen +
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts index b9eb8f5..99337c6 100644 --- a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts @@ -1,28 +1,97 @@ import { Component, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +interface Partition { + diskNumber: number; + partitionNumber: number; + partitionCode: string; + size: number; + filesystem: string | null; + isSummary?: boolean; +} + +interface SimplifiedClient { + name: string; + partitions: Partition[]; +} + +interface GroupedClientPartitions { + clientNames: string[]; + partitions: Partition[]; +} + @Component({ selector: 'app-partition-type-organizator', templateUrl: './partition-type-organizator.component.html', styleUrl: './partition-type-organizator.component.css' }) export class PartitionTypeOrganizatorComponent implements OnInit { - constructor(@Inject(MAT_DIALOG_DATA) public data: any) { } + displayedColumns: string[] = ['diskNumber', 'partitionNumber', 'partitionCode', 'size', 'filesystem']; + groupedPartitions: GroupedClientPartitions[] = []; - public simplifiedData: any[] = []; - displayedColumns: string[] = ['diskNumber', 'partitionNumber', 'partitionCode', 'size', 'filesystem', 'memoryUsage']; + constructor(@Inject(MAT_DIALOG_DATA) public data: SimplifiedClient[]) { } ngOnInit(): void { - this.simplifiedData = this.data.map((client: any) => ({ - name: client.name, - partitions: client.partitions.map((p: any) => ({ + const simplifiedClients = this.simplifyClients(this.data); + this.groupedPartitions = this.groupClientsByPartitions(simplifiedClients); + } + + private simplifyClients(clients: SimplifiedClient[]): SimplifiedClient[] { + return clients.map(client => { + const partitionZero = client.partitions.find(p => p.partitionNumber === 0); + const otherPartitions = client.partitions.filter(p => p.partitionNumber !== 0); + + const simplifiedPartitions: Partition[] = otherPartitions.map(p => ({ diskNumber: p.diskNumber, partitionNumber: p.partitionNumber, partitionCode: p.partitionCode, size: p.size, filesystem: p.filesystem, - memoryUsage: p.memoryUsage - })) - })); + isSummary: false + })); + + if (partitionZero) { + simplifiedPartitions.push({ + diskNumber: partitionZero.diskNumber, + partitionNumber: partitionZero.partitionNumber, + partitionCode: partitionZero.partitionCode, + size: partitionZero.size, + filesystem: null, + isSummary: true + }); + } + + return { + name: client.name, + partitions: simplifiedPartitions + }; + }); + } + + private groupClientsByPartitions(clients: SimplifiedClient[]): GroupedClientPartitions[] { + const groups: GroupedClientPartitions[] = []; + + clients.forEach(client => { + const normalizedPartitions = this.normalizePartitions(client.partitions); + + const existingGroup = groups.find(group => + JSON.stringify(this.normalizePartitions(group.partitions)) === JSON.stringify(normalizedPartitions) + ); + + if (existingGroup) { + existingGroup.clientNames.push(client.name); + } else { + groups.push({ + clientNames: [client.name], + partitions: client.partitions + }); + } + }); + + return groups; + } + + private normalizePartitions(partitions: Partition[]): Partition[] { + return [...partitions].sort((a, b) => a.partitionNumber - b.partitionNumber); } } From 8cc1854d09916e312ab9e15c92ce43bbc08e6eb4 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 2 May 2025 13:34:02 +0200 Subject: [PATCH 21/47] Refactor partition type display: simplify client name presentation and improve table structure --- .../partition-type-organizator.component.html | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html index 8c66c0a..25c1fe7 100644 --- a/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html @@ -1,47 +1,47 @@

- {{ group.clientNames.length === 1 ? group.clientNames[0] : 'Clientes (' + group.clientNames.length + '): ' + - group.clientNames.join(', ') }} + {{ group.clientNames.length === 1 ? group.clientNames[0] : group.clientNames.join(', ') }}

- + +
+ + + + + - - - - - + + + + - - - - - + + + + - - - - - + + + + - - - - - + + + + - - - - - + + +
Disco{{ element.diskNumber }}Disco{{ element.diskNumber }}Partición{{ element.partitionNumber }}Partición{{ element.partitionNumber }}Tipo{{ element.partitionCode }}Tipo{{ element.partitionCode }}Tamaño (MB){{ element.size | number }}Tamaño (MB){{ element.size | number }}File System + {{ element.filesystem || '-' }} + Resumen + File System - {{ element.filesystem || '-' }} - Resumen -
+ - - - + +

Sin particiones disponibles.

+
\ No newline at end of file From a3f99958a39b6d994b6bd62f53136d59964694f0 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Tue, 6 May 2025 14:09:12 +0200 Subject: [PATCH 22/47] Refactor groups component: update tour steps, improve button layout, and enhance translation keys --- .../components/groups/groups.component.html | 51 ++++++++++--------- .../app/components/groups/groups.component.ts | 4 +- ogWebconsole/src/locale/en.json | 17 +++++-- ogWebconsole/src/locale/es.json | 15 ++++-- 4 files changed, 53 insertions(+), 34 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index 32755af..bdddf21 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -1,25 +1,27 @@
-
-
-

+

{{ 'adminGroupsTitle' | translate }}

-
- - - - - - - +
+
+ + + + + + +
- {{ option.name }} @@ -116,7 +118,7 @@
-
+
@@ -222,16 +224,18 @@ {{ selectedNode?.name }}
- +
+ +
+ (change)="toggleView($event.value)" joyrideStep="tabsStep" text="{{ 'tabsStepText' | translate }}"> - list {{ 'Vista Lista' | translate }} + list {{ 'vistalista' | translate }} - grid_view {{ 'Vista Tarjeta' | translate }} + grid_view {{ 'vistatarjeta' | translate }}
@@ -240,7 +244,8 @@ -
+
diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 3e07050..7a699b6 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -635,9 +635,9 @@ export class GroupsComponent implements OnInit, OnDestroy { } - iniciarTour(): void { + initTour(): void { this.joyrideService.startTour({ - steps: ['groupsTitleStepText', 'filtersPanelStep', 'addStep', 'keyStep', 'tabsStep'], + steps: ['groupsTitleStep', 'filtersPanelStep', 'treePanelStep', 'addStep', 'keyStep', 'executeCommandStep', 'tabsStep', 'clientsViewStep'], showPrevButton: true, themeColor: '#3f51b5', }); diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index aeab939..12b3cd2 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -52,7 +52,7 @@ "checkboxUserRole": "User", "groupsTitleStepText": "On this screen, you can manage the main organizational units (Faculties, Classrooms, Classroom Groups, and clients).", "titleStepText": "On this screen, you can manage the calendars of remote teams connected to the UDS service", - "groupsAddStepText": "Click to add a new organizational unit or client.", + "groupsAddStepText": "Click to add a new organizational unit or clients.", "adminCalendarsTitle": "Manage calendars", "addButtonStepText": "Click here to add a new calendar.", "addCalendar": "Add calendar", @@ -163,6 +163,7 @@ "cancelButton": "Cancel", "saveButton": "Save", "generalTabLabel": "General", + "executeCommandStepText": "Set of actions to be performed on a selection of clients or an organizational unit.", "tabsStepText": "Use the tabs to access different viewing and search options for organizational units and clients.", "adminGroupsTitle": "Manage groups", "newOrganizationalUnitTooltip": "Open modal to create organizational units of any type (Center, Classroom, Classroom Group, or Client Group)", @@ -428,7 +429,7 @@ "ogLive": "ogLive", "TOOLTIP_PXE_IMAGES": "View available PXE boot images", "pxeTemplates": "PXE Templates", - "pxeTemplate": "Plantilla", + "pxeTemplate": "Template", "TOOLTIP_PXE_TEMPLATES": "Manage PXE boot templates", "pxeBootFiles": "PXE Boot Files", "TOOLTIP_PXE_BOOT_FILES": "Configure PXE boot files", @@ -476,7 +477,8 @@ "subnet": "Subnet", "parent": "Parent", "adminUsersTitle": "Manage users", - "filtersPanelStepText": "Use these filters to search or load configurations.", + "filtersPanelStepText": "Use these filters to search by clients, organizational units or states.", + "treePanelStepText": "In this organizational units tree you will be able to see the different hierarchical structures as well as to perform actions on each of them.", "organizationalUnitsStepText": "List of Organizational Units. Click on one to view details.", "defaultMenuLabel": "Main menu", "noClients": "No clients", @@ -487,5 +489,10 @@ "usedPercentageLabel": "Used", "errorLoadingData": "Error fetching data. Service not available", "repositoryTitleStep": "On this screen you can manage image repositories.", - "partitions": "Particiones" -} + "partitions": "Particiones", + "clientsViewStepText": "Display of the selected organizational unit's clients", + "vistalista": "List View", + "vistatarjeta": "Card View", + "ejecutarComandos": "Execute Commands", + "searchState": "Search state" +} \ No newline at end of file diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 74bf1d2..84e3fc3 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -164,7 +164,8 @@ "cancelButton": "Cancelar", "saveButton": "Guardar", "generalTabLabel": "General", - "tabsStepText": "Utiliza las pestañás para acceder a las diferentes opciones de visualización y busqueda de unidades organizativas y clientes.", + "executeCommandStepText": "Conjunto de acciones a ejecutar sobre una selección de clientes o unidad organizativa", + "tabsStepText": "Utiliza las pestañas para acceder a las diferentes opciones de visualización y busqueda de unidades organizativas y clientes.", "adminGroupsTitle": "Administrar grupos", "newOrganizationalUnitTooltip": "Abrir modal para crear unidades organizativas de cualquier tipo (Centro, Aula, Grupo de aulas o Grupo de clientes)", "newOrganizationalUnitButton": "Nueva Unidad Organizativa", @@ -478,7 +479,8 @@ "subnet": "Subred", "parent": "Padre", "adminUsersTitle": "Administrar usuarios", - "filtersPanelStepText": "Utiliza estos filtros para buscar o cargar configuraciones.", + "filtersPanelStepText": "Utiliza estos filtros para buscar por clientes, unidades organizativas o estados.", + "treePanelStepText": "En este árbol de unidades organizativas podrás ver las diferentes estructuras jerárquicas además de poder realizar acciones sobre cada una de ellas", "organizationalUnitsStepText": "Lista de Unidades Organizacionales. Haz clic en una para ver detalles.", "defaultMenuLabel": "Menú por defecto", "noClients": "No hay clientes", @@ -489,5 +491,10 @@ "usedPercentageLabel": "Usado", "errorLoadingData": "Error al cargar los datos. Servicio inactivo", "repositoryTitleStep": "En esta pantalla se pueden gestionar los repositorios de imágenes.", - "partitions": "Particiones" -} + "partitions": "Particiones", + "clientsViewStepText": "Visualización de los clientes de la unidad organizativa seleccionada", + "vistalista": "Vista Lista", + "vistatarjeta": "Vista Tarjeta", + "ejecutarComandos": "Ejecutar Comandos", + "searchState": "Buscar por estado" +} \ No newline at end of file From bdd6206e18b0d20f402620848a08a68e885ee7ff Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 7 May 2025 11:58:51 +0200 Subject: [PATCH 23/47] Refactor execute command component: replace command names with translation keys and update localization files for execute commands --- .../execute-command.component.html | 2 +- .../execute-command.component.ts | 24 +++++++++---------- .../components/groups/groups.component.html | 2 +- ogWebconsole/src/locale/en.json | 17 +++++++++++-- ogWebconsole/src/locale/es.json | 15 +++++++++++- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.html b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.html index 0d66031..400dc52 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.html +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.html @@ -19,6 +19,6 @@ \ No newline at end of file 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 8bff5aa..1b871a4 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 @@ -21,17 +21,17 @@ export class ExecuteCommandComponent implements OnInit { loading: boolean = true; arrayCommands: any[] = [ - { name: 'Enceder', slug: 'power-on', disabled: false }, - { name: 'Apagar', slug: 'power-off', disabled: false }, - { name: 'Reiniciar', slug: 'reboot', disabled: false }, - { name: 'Iniciar Sesión', slug: 'login', disabled: true }, - { name: 'Crear imagen', slug: 'create-image', disabled: false }, - { name: 'Clonar/desplegar imagen', slug: 'deploy-image', disabled: false }, - { name: 'Eliminar Imagen Cache', slug: 'delete-image-cache', disabled: true }, - { name: 'Particionar y Formatear', slug: 'partition', disabled: false }, - { name: 'Inventario Software', slug: 'software-inventory', disabled: true }, - { name: 'Inventario Hardware', slug: 'hardware-inventory', disabled: true }, - { name: 'Ejecutar comando', slug: 'run-script', disabled: false }, + { translationKey: 'executeCommands.powerOn', slug: 'power-on', disabled: false }, + { translationKey: 'executeCommands.powerOff', slug: 'power-off', disabled: false }, + { translationKey: 'executeCommands.reboot', slug: 'reboot', disabled: false }, + { translationKey: 'executeCommands.login', slug: 'login', disabled: true }, + { translationKey: 'executeCommands.createImage', slug: 'create-image', disabled: false }, + { translationKey: 'executeCommands.deployImage', slug: 'deploy-image', disabled: false }, + { translationKey: 'executeCommands.deleteImageCache', slug: 'delete-image-cache', disabled: true }, + { translationKey: 'executeCommands.partition', slug: 'partition', disabled: false }, + { translationKey: 'executeCommands.softwareInventory', slug: 'software-inventory', disabled: true }, + { translationKey: 'executeCommands.hardwareInventory', slug: 'hardware-inventory', disabled: true }, + { translationKey: 'executeCommands.runScript', slug: 'run-script', disabled: false }, ]; client: any = {}; @@ -232,7 +232,7 @@ export class ExecuteCommandComponent implements OnInit { this.router.navigate(['/clients/run-script'], { queryParams: { - clientData: JSON.stringify(clientDataToSend) , + clientData: JSON.stringify(clientDataToSend), runScriptContext: JSON.stringify(this.runScriptContext) } }) diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index bdddf21..a338c2d 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -205,7 +205,7 @@ {{ 'partitions' | translate }} diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index 12b3cd2..136bb66 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -489,10 +489,23 @@ "usedPercentageLabel": "Used", "errorLoadingData": "Error fetching data. Service not available", "repositoryTitleStep": "On this screen you can manage image repositories.", - "partitions": "Particiones", + "partitions": "Partitions", "clientsViewStepText": "Display of the selected organizational unit's clients", "vistalista": "List View", "vistatarjeta": "Card View", "ejecutarComandos": "Execute Commands", - "searchState": "Search state" + "searchState": "Search state", + "executeCommands": { + "powerOn": "Power On", + "powerOff": "Shut Down", + "reboot": "Reboot", + "login": "Login", + "createImage": "Create Image", + "deployImage": "Deploy Image", + "deleteImageCache": "Delete Image Cache", + "partition": "Partition and Format", + "softwareInventory": "Software Inventory", + "hardwareInventory": "Hardware Inventory", + "runScript": "Run Script" + } } \ No newline at end of file diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 84e3fc3..26a05f3 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -496,5 +496,18 @@ "vistalista": "Vista Lista", "vistatarjeta": "Vista Tarjeta", "ejecutarComandos": "Ejecutar Comandos", - "searchState": "Buscar por estado" + "searchState": "Buscar por estado", + "executeCommands": { + "powerOn": "Encender", + "powerOff": "Apagar", + "reboot": "Reiniciar", + "login": "Iniciar Sesión", + "createImage": "Crear imagen", + "deployImage": "Clonar/desplegar imagen", + "deleteImageCache": "Eliminar Imagen Cache", + "partition": "Particionar y Formatear", + "softwareInventory": "Inventario Software", + "hardwareInventory": "Inventario Hardware", + "runScript": "Ejecutar script" + } } \ No newline at end of file From 2dce55ce1d1c87115e313bee8b71d121c8757b3d Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 7 May 2025 12:19:00 +0200 Subject: [PATCH 24/47] Refactor legend component: update remote access titles to use translation keys and enhance localization files --- .../components/groups/shared/legend/legend.component.html | 6 +++--- ogWebconsole/src/locale/en.json | 4 +++- ogWebconsole/src/locale/es.json | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/shared/legend/legend.component.html b/ogWebconsole/src/app/components/groups/shared/legend/legend.component.html index 6cd66de..3d02ad5 100644 --- a/ogWebconsole/src/app/components/groups/shared/legend/legend.component.html +++ b/ogWebconsole/src/app/components/groups/shared/legend/legend.component.html @@ -22,11 +22,11 @@ school -
Disponible acceso remoto
+
{{ 'remoteAccess' | translate }}
school -
No disponible acceso remoto
+
{{ 'noRemoteAccess' | translate }}
- + \ No newline at end of file diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index 136bb66..7b4239a 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -507,5 +507,7 @@ "softwareInventory": "Software Inventory", "hardwareInventory": "Hardware Inventory", "runScript": "Run Script" - } + }, + "remoteAccess": "Remote access available", + "noRemoteAccess": "Remote access not available" } \ No newline at end of file diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 26a05f3..9f9bedf 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -509,5 +509,7 @@ "softwareInventory": "Inventario Software", "hardwareInventory": "Inventario Hardware", "runScript": "Ejecutar script" - } + }, + "remoteAccess": "Disponible acceso remoto", + "noRemoteAccess": "No disponible acceso remoto" } \ No newline at end of file From f35ba106bab5a7c561a01f5ffe1ae79b75bdbdac Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 7 May 2025 13:45:40 +0200 Subject: [PATCH 25/47] refs #1953 Refactor manage organizational unit component: replace hardcoded strings with translation keys and update localization files --- .../manage-organizational-unit.component.html | 114 +++++++++--------- ogWebconsole/src/locale/en.json | 3 +- ogWebconsole/src/locale/es.json | 3 +- 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html index a72d549..739256b 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html @@ -1,12 +1,13 @@
-

{{ isEditMode ? 'Editar' : 'Crear' }} Unidad Organizativa

-
+

{{ isEditMode ? ('edit' | translate) : ('createButton' | translate) }} {{ + 'labelOrganizationalUnit' | translate }}

+
- - General + + {{ 'generalTabLabel' | translate }} - Tipo + {{ 'typeLabel' | translate }} {{ typeTranslations[type] }} @@ -14,11 +15,11 @@ - Nombre + {{ 'nameColumnHeader' | translate }} - Unidad organizativa superior + {{ 'createOrgUnitparentLabel' | translate }} {{ getSelectedParentName() }} @@ -31,7 +32,7 @@ - Descripción + {{ 'descriptionLabel' |translate }} @@ -41,22 +42,23 @@ - Información del aula + {{'classroomInfoStepLabel' | translate}}
- Localización + {{ 'locationLabel' | translate }} - Aforo + {{ 'capacityLabel' | translate }} - El aforo no puede ser negativo + {{ 'capacityWarning' | translate }} - Calendario Asociado + {{ 'associatedCalendarLabel' | translate }} {{ calendar.name }} @@ -64,32 +66,32 @@
- Proyector - Pizarra + {{ 'projectorAlt' | translate }} + {{ 'boardToggle' | translate }}
- Configuración de Red + {{ 'networkSettingsStepLabel' | translate }}
- - OgLive - - - {{ oglive.name }} - - - - - Plantilla PXE - - - {{ template.name }} - - - - Repositorio + OgLive + + + {{ oglive.name }} + + + + + {{ 'templateLabel' | translate }} + + + {{ template.name }} + + + + + {{ 'repositoryLabel' | translate }} {{ repository.name }} @@ -105,17 +107,17 @@ - Máscara de Red + {{ 'netmaskLabel' | translate }} - - Interfaz de red - - - {{ type.name }} - - - + + {{ 'netifaceLabel' | translate }} + + + {{ type.name }} + + + Router @@ -125,7 +127,7 @@ - Modo P2P + {{ 'p2pModeLabel' | translate }} {{ option.name }} @@ -133,23 +135,23 @@ - Tiempo P2P + {{ 'p2pTimeLabel' | translate }} - Mcast IP + {{ 'mcastIpLabel' | translate }} - Mcast Speed + {{ 'mcastSpeedLabel' | translate }} - Mcast Port + {{ 'mcastPortLabel' | translate }} - Mcast Mode + {{ 'mcastModeLabel' | translate }} {{ option.name }} @@ -157,7 +159,7 @@ - Menu + {{ 'menuLabel' | translate }} {{ menu.name }} @@ -165,28 +167,28 @@ - Perfil de Hardware + {{ 'hardwareProfileLabel' | translate }} {{ unit.description }} - Formato de URL incorrecto + {{ 'urlFormatError' | translate }}
- Información Adicional + {{ 'additionalInfoStepLabel' | translate }}
- Comentarios + {{ 'commentsLabel' | translate }}
- + + isEditMode ? ('edit' | translate) : ('createButton' | translate) }}
-
+
\ No newline at end of file diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index 7b4239a..e829ced 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -509,5 +509,6 @@ "runScript": "Run Script" }, "remoteAccess": "Remote access available", - "noRemoteAccess": "Remote access not available" + "noRemoteAccess": "Remote access not available", + "capacityWarning": "The capacity cannot be negative" } \ No newline at end of file diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 9f9bedf..92d37a3 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -511,5 +511,6 @@ "runScript": "Ejecutar script" }, "remoteAccess": "Disponible acceso remoto", - "noRemoteAccess": "No disponible acceso remoto" + "noRemoteAccess": "No disponible acceso remoto", + "capacityWarning": "El aforo no puede ser" } \ No newline at end of file From fc51481977ada854e897ecfe7bc244d0263e87b9 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 8 May 2025 08:01:14 +0200 Subject: [PATCH 26/47] refs #1968. Pxe some UX improvements --- .../pxe-boot-files.component.css | 18 ---- .../pxe-boot-files.component.html | 8 +- .../pxe-boot-files.component.ts | 13 +-- .../pxe-images/pxe-images.component.html | 19 ++-- .../ogboot/pxe-images/pxe-images.component.ts | 44 +++------ .../create-pxe-template.component.html | 43 --------- .../create-pxe-template.component.css | 58 ++++-------- .../create-pxe-template.component.html | 44 +++++++++ .../create-pxe-template.component.ts | 93 ++++++------------- .../components/ogboot/pxe/pxe.component.html | 39 +++++--- .../components/ogboot/pxe/pxe.component.ts | 56 +++++------ .../show-template-content.component.css | 9 +- .../show-template-content.component.html | 12 ++- .../show-template-content.component.ts | 9 +- 14 files changed, 198 insertions(+), 267 deletions(-) delete mode 100644 ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.html rename ogWebconsole/src/app/components/ogboot/pxe/{create-pxeTemplate => manage-pxeTemplate}/create-pxe-template.component.css (52%) create mode 100644 ogWebconsole/src/app/components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component.html rename ogWebconsole/src/app/components/ogboot/pxe/{create-pxeTemplate => manage-pxeTemplate}/create-pxe-template.component.ts (61%) diff --git a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.css b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.css index d2ea8a8..f4ae6a5 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.css +++ b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.css @@ -51,21 +51,3 @@ .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; -} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html index f8caba7..fb12d46 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html +++ b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html @@ -37,7 +37,7 @@
- {{ 'idColumnHeader' | translate }} @@ -50,12 +50,12 @@ - {{ 'nameColumnHeader' | translate }} + {{ 'ipLabel' | translate }} {{ element.ip }} - {{ 'nameColumnHeader' | translate }} + {{ 'macLabel' | translate }} {{ element.mac }} @@ -77,4 +77,4 @@ - \ No newline at end of file + diff --git a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts index 95be23e..b2413ce 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts +++ b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts @@ -4,6 +4,7 @@ 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', @@ -15,7 +16,7 @@ export class PxeBootFilesComponent implements OnInit { availableOrganizationalUnits: any[] = []; selectedUnitChildren: any[] = []; - dataSource: any[] = []; + dataSource = new MatTableDataSource(); taskForm: FormGroup; units: any[] = []; ogLiveOptions: any[] = []; @@ -72,14 +73,14 @@ export class PxeBootFilesComponent implements OnInit { loadChildUnits(event: any) { this.http.get(`${this.baseUrl}/clients?organizationalUnit.id=${event.value.id}`).subscribe( response => { - this.dataSource = response['hydra:member']; + this.dataSource.data = response['hydra:member']; }, error => console.error('Error fetching child units:', error) ); } applyToAll(): void { - this.dataSource = this.dataSource.map(client => ({ + this.dataSource.data = this.dataSource.data.map(client => ({ ...client, ogLive: this.globalOgLive || client.ogLive })); @@ -88,7 +89,7 @@ export class PxeBootFilesComponent implements OnInit { saveOgLiveTemplates(): void { const groupedByTemplate: { [key: string]: string[] } = {}; - this.dataSource.forEach(client => { + this.dataSource.data.forEach(client => { if (client.ogLive) { if (!groupedByTemplate[client.ogLive]) { groupedByTemplate[client.ogLive] = []; @@ -107,10 +108,10 @@ export class PxeBootFilesComponent implements OnInit { this.http.post(url, payload).subscribe({ next: () => { - this.toastService.success(`Clientes guardados correctamente para la plantilla ${templateId}`); + this.toastService.success(`Clientes guardados correctamente para la plantilla`); }, error: () => { - this.toastService.error(`Error al guardar clientes para la plantilla ${templateId}`); + this.toastService.error(`Error al guardar clientes para la plantilla`); } }); }); diff --git a/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.html b/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.html index bf13061..6f86ccf 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.html +++ b/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.html @@ -8,7 +8,7 @@
- + - diff --git a/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.ts b/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.ts index f627fba..6d8dfb5 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.ts +++ b/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.ts @@ -25,8 +25,8 @@ export class PXEimagesComponent implements OnInit { images: { downloadUrl: string; name: string; uuid: string }[] = []; dataSource = new MatTableDataSource(); length: number = 0; - itemsPerPage: number = 10; - page: number = 1; + itemsPerPage: number = 20; + page: number = 0; pageSizeOptions: number[] = [5, 10, 20, 40, 100]; selectedElements: string[] = []; loading: boolean = false; @@ -94,12 +94,15 @@ export class PXEimagesComponent implements OnInit { } search(): void { - this.dataService.getImages(this.filters).subscribe( + this.loading = true; + this.http.get(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe( data => { - this.dataSource.data = data; + this.dataSource.data = data['hydra:member']; + this.length = data['hydra:totalItems']; + this.loading = false; }, error => { - console.error('Error fetching og lives', error); + this.loading = false; } ); } @@ -160,19 +163,6 @@ 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', @@ -203,22 +193,11 @@ export class PXEimagesComponent implements OnInit { const dialogRef = this.dialog.open(InfoImageComponent, { data: { data }, width: '700px' }); } - applyFilter() { - this.http.get(`${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) { + onPageChange(event: any): void { this.page = event.pageIndex; this.itemsPerPage = event.pageSize; - this.applyFilter(); + this.length = event.length; + this.search(); } loadAlert(): Observable { @@ -248,6 +227,7 @@ export class PXEimagesComponent implements OnInit { this.joyrideService.startTour({ steps: [ 'titleStep', + 'viewInfoStep', 'addImageStep', 'searchNameStep', 'searchDefaultImageStep', diff --git a/ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.html b/ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.html deleted file mode 100644 index 75a76cc..0000000 --- a/ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.html +++ /dev/null @@ -1,43 +0,0 @@ -

{{ isEditMode ? ('editTemplateTitle' | translate) : ('addTemplateTitle' | translate) }}

- - -
-
- - {{ 'templateNameLabel' | translate }} - - - {{ 'templateNameError' | translate }} - - - - - {{ 'templateContentLabel' | translate }} - - - {{ 'templateContentError' | translate }} - - -
-
-
- - -
- - - - - - -
- - -
-
-
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.css b/ogWebconsole/src/app/components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component.css similarity index 52% rename from ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.css rename to ogWebconsole/src/app/components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component.css index 65a0a2c..af4ebe4 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.css +++ b/ogWebconsole/src/app/components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component.css @@ -1,6 +1,3 @@ -mat-form-field { - width: 100%; -} pre { background-color: #eceff1; @@ -12,41 +9,17 @@ pre { color: #333; } -mat-dialog-actions { - margin-top: 20px; + +.dialog-content { display: flex; - justify-content: flex-end; + flex-direction: column; + padding: 40px; } -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; +.pxe-form { + width: 100%; + display: flex; + flex-direction: column; } .list-item-content { @@ -56,6 +29,11 @@ h3 { width: 100%; } +.form-field { + width: 100%; + margin-top: 16px; +} + .text-content { flex-grow: 1; margin-right: 16px; @@ -72,13 +50,11 @@ h3 { cursor: pointer; } -.actions-container { +.action-container { display: flex; - justify-content: space-between; - width: 100%; - align-items: center; - margin-bottom: 1rem; - padding-right: 1rem; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; } .action-buttons { diff --git a/ogWebconsole/src/app/components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component.html b/ogWebconsole/src/app/components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component.html new file mode 100644 index 0000000..f9b4836 --- /dev/null +++ b/ogWebconsole/src/app/components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component.html @@ -0,0 +1,44 @@ +

{{ isEditMode ? ('editTemplateTitle' | translate) : ('addTemplateTitle' | translate) }}

+ + +
+ + {{ 'templateNameLabel' | translate }} + + + {{ 'templateNameError' | translate }} + + + + + {{ 'templateContent' | translate }} + + + {{ 'templateContentError' | translate }} + + + + + {{ 'isDefaultLabel' | translate }} + + +
+
+ +
+ + + + + + +
+ + +
+
diff --git a/ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.ts b/ogWebconsole/src/app/components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component.ts similarity index 61% rename from ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.ts rename to ogWebconsole/src/app/components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component.ts index e2f7ee6..ae7c9d1 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.ts +++ b/ogWebconsole/src/app/components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component.ts @@ -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,7 +72,8 @@ exit` this.templateForm = this.fb.group({ name: [this.data?.name || '', Validators.required], - templateContent: [this.data?.templateContent || '', Validators.required] + templateContent: [this.data?.templateContent || '', Validators.required], + isDefault: [this.data?.isDefault || false] }); } @@ -99,7 +100,8 @@ exit` const formValues = this.templateForm.value; const payload = { name: formValues.name, - templateContent: formValues.templateContent + templateContent: formValues.templateContent, + isDefault: formValues.isDefault, }; this.http.post(`${this.baseUrl}/pxe-templates`, payload).subscribe({ @@ -117,7 +119,8 @@ exit` const formValues = this.templateForm.value; const payload = { name: formValues.name, - templateContent: formValues.templateContent + templateContent: formValues.templateContent, + isDefault: formValues.isDefault, }; this.http.patch(`${this.baseUrl}/pxe-templates/${this.data.uuid}`, payload).subscribe({ @@ -138,42 +141,6 @@ exit` 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); } diff --git a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.html b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.html index a6cb871..02fd939 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.html +++ b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.html @@ -1,5 +1,5 @@
-
@@ -7,7 +7,7 @@ translate }}
- +
@@ -22,10 +22,10 @@ search {{ 'searchHint' | translate }} - - {{ 'createdInOgbootLabel' | translate }} - + {{ 'isDefaultLabel' | translate }} + {{ 'allOption' | translate }} {{ 'yesOption' | translate }} @@ -36,16 +36,31 @@ + text="{{ 'tableDatePxeTemplateText' | translate }}"> @@ -60,7 +75,7 @@ - diff --git a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.ts b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.ts index 55cb6ad..9c35f3a 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.ts +++ b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.ts @@ -1,9 +1,8 @@ import { HttpClient } from '@angular/common/http'; import {Component, OnInit} from '@angular/core'; -import { CreatePxeTemplateComponent } from './create-pxeTemplate/create-pxe-template.component'; +import { CreatePxeTemplateComponent } from './manage-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'; @@ -26,8 +25,8 @@ export class PxeComponent implements OnInit{ currentPage: number = 1; dataSource = new MatTableDataSource(); length: number = 0; - itemsPerPage: number = 10; - page: number = 1; + itemsPerPage: number = 20; + page: number = 0; pageSizeOptions: number[] = [5, 10, 20, 40, 100]; selectedElements: string[] = []; loading: boolean = false; @@ -45,17 +44,22 @@ 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}` + cell: (user: any) => user.synchronized + }, + { + columnDef: 'isDefault', + header: 'Plantilla por defecto', + cell: (user: any) => user.isDefault }, { 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']; @@ -80,12 +84,15 @@ export class PxeComponent implements OnInit{ } search(): void { - this.dataService.getPxeTemplates(this.filters).subscribe( + this.loading = true; + this.http.get(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe( data => { - this.dataSource.data = data; + this.dataSource.data = data['hydra:member']; + this.length = data['hydra:totalItems']; + this.loading = false; }, error => { - console.error('Error fetching pxe templates', error); + this.loading = false; } ); } @@ -100,10 +107,9 @@ export class PxeComponent implements OnInit{ }); } - editPxeTemplate(template: any) { const dialogRef = this.dialog.open(CreatePxeTemplateComponent, { - data: template, // Pasa los datos del template para edición + data: template, width: '800px' }); @@ -128,7 +134,6 @@ export class PxeComponent implements OnInit{ this.search(); }, error: (error) => { - console.error('Error al eliminar la subred', error); this.toastService.error(error.error['hydra:description']); } }); @@ -143,19 +148,7 @@ export class PxeComponent implements OnInit{ showTemplate(event: MouseEvent, data: any): void { event.stopPropagation(); - const dialogRef = this.dialog.open(ShowTemplateContentComponent, { data: { data }, width: '700px' }); - } - - applyFilter() { - this.http.get(`${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); - } - }); + const dialogRef = this.dialog.open(ShowTemplateContentComponent, { data: { data }, width: '800px' }); } loadAlert(): Observable { @@ -181,20 +174,21 @@ export class PxeComponent implements OnInit{ ); } - onPageChange(event: PageEvent) { + onPageChange(event: any): void { this.page = event.pageIndex; this.itemsPerPage = event.pageSize; - this.applyFilter(); + this.length = event.length; + this.search(); } - iniciarTour(): void { + initTour(): void { this.joyrideService.startTour({ steps: [ - 'serverInfoStep', 'titleStep', + 'viewInfoStep', 'addTemplateStep', 'searchNameStep', - 'searchSyncStep', + 'searchIsDefaultStep', 'tableStep', 'actionsStep', 'paginationStep' diff --git a/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.css b/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.css index d01dcc9..b314670 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.css +++ b/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.css @@ -26,8 +26,15 @@ background-color: #f5f5f5; padding: 16px; border-radius: 4px; - font-size: 12px; + font-size: 14px; overflow-x: auto; white-space: pre; border: 1px solid #dcdcdc; } + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} diff --git a/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.html b/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.html index 52d8e53..2724d18 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.html +++ b/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.html @@ -1,4 +1,10 @@ -
-

{{ 'detailsTitle' | translate: { name: data.data.name } }}

-
{{ data.data.templateContent }}
+ +
+

{{ 'detailsTitle' | translate: { name: data.data.name } }}

+
{{ data.data.templateContent }}
+
+
+ +
+
diff --git a/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.ts b/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.ts index 5ae59ac..ed2fb3b 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.ts +++ b/ogWebconsole/src/app/components/ogboot/pxe/show-template-content/show-template-content.component.ts @@ -1,5 +1,5 @@ import { Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA } from "@angular/material/dialog"; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import { HttpClient } from "@angular/common/http"; import { ConfigService } from "@services/config.service"; @@ -15,8 +15,13 @@ export class ShowTemplateContentComponent { constructor( @Inject(MAT_DIALOG_DATA) public data: any, private http: HttpClient, - private configService: ConfigService + private configService: ConfigService, + public dialogRef: MatDialogRef ) { this.baseUrl = this.configService.apiUrl; } + + close(): void { + this.dialogRef.close(); + } } From 9ba8c1771d1d2a57ba9a9b6f51821320c63f0bde Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 8 May 2025 08:03:45 +0200 Subject: [PATCH 27/47] refs #1906. Updated UX to CommandTask --- .../commands-task/commands-task.component.ts | 2 +- .../create-task-schedule.component.ts | 16 +- .../create-task-script.component.ts | 1 + .../show-task-schedule.component.ts | 2 +- .../view-parameters-modal.component.spec.ts | 2 +- .../input-dialog/input-dialog.component.css | 0 .../input-dialog/input-dialog.component.html | 7 - .../input-dialog.component.spec.ts | 47 --- .../input-dialog/input-dialog.component.ts | 18 - .../task-logs/task-logs.component.css | 118 ------- .../task-logs/task-logs.component.html | 128 -------- .../task-logs/task-logs.component.ts | 309 ------------------ 12 files changed, 15 insertions(+), 635 deletions(-) delete mode 100644 ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.css delete mode 100644 ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.html delete mode 100644 ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.spec.ts delete mode 100644 ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.ts delete mode 100644 ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.css delete mode 100644 ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html delete mode 100644 ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts 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 229b4e5..c8e27e5 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 @@ -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') }, + { columnDef: 'nextExecution', header: 'Próxima ejecución', cell: (task: any) => this.datePipe.transform(task.nextExecution, 'dd/MM/yyyy HH:mm:ss', 'UTC') }, { 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.ts b/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.ts index f47d7ca..9cf584f 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.ts +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task-schedule/create-task-schedule.component.ts @@ -114,20 +114,26 @@ 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' ? formData.executionDate : null, + executionDate: formData.recurrenceType === 'none' ? this.convertDateToLocalISO(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 } diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.ts b/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.ts index 4b3497f..f46cc88 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.ts +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task-script/create-task-script.component.ts @@ -120,6 +120,7 @@ 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'); 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 108197a..88ea5bf 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') }, + { columnDef: 'time', header: 'Hora de ejecución', cell: (schedule: any) => this.datePipe.transform(schedule.executionTime, 'HH:mm', 'UTC') }, { 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/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.spec.ts b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.spec.ts index 6104c77..c814fa3 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.spec.ts +++ b/ogWebconsole/src/app/components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component.spec.ts @@ -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', () => { diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.css b/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.html b/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.html deleted file mode 100644 index eed69e9..0000000 --- a/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.html +++ /dev/null @@ -1,7 +0,0 @@ -

{{ 'inputDetails' | translate }}

-
-
{{ data.input | json }}
-
-
- -
diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.spec.ts b/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.spec.ts deleted file mode 100644 index 7168136..0000000 --- a/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -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; - - 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(); - }); -}); diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.ts b/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.ts deleted file mode 100644 index 744b415..0000000 --- a/ogWebconsole/src/app/components/commands/commands-task/task-logs/input-dialog/input-dialog.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -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, - @Inject(MAT_DIALOG_DATA) public data: { input: any } - ) {} - - close(): void { - this.dialogRef.close(); - } -} diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.css b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.css deleted file mode 100644 index 195127a..0000000 --- a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.css +++ /dev/null @@ -1,118 +0,0 @@ -.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; -} diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html deleted file mode 100644 index f7ceea4..0000000 --- a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html +++ /dev/null @@ -1,128 +0,0 @@ -
- - -
-

{{ 'adminCommandsTitle' | - translate }}

-
- -
- -
-
- -
- - - - - {{ client.name }} - - - - - - - - - {{ command.name }} - - - - - - Estado - - Todos - Fallido - Pendiente de ejecutar - Ejecutando - Completado con éxito - Cancelado - - -
- - - -
-
{{ column.header }} - - {{ image[column.columnDef] ? 'check_circle' : 'cancel' }} - + + + {{ 'yesOption' | translate }} + + + {{ 'noOption' | translate }} + + - + + + + {{ 'yesOption' | translate }} + + + {{ 'noOption' | translate }} + + + + {{ column.cell(image) }}
- - - - - - - - -
{{ column.header }} - - - - -
- - - {{trace.progress}}% -
-
- -
- - {{ - 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 - }} - - -
-
-
- - - - - - - {{ column.cell(trace) }} - -
- -
-
- -
- - -
diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts deleted file mode 100644 index 60a233f..0000000 --- a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts +++ /dev/null @@ -1,309 +0,0 @@ -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; - clientControl = new FormControl(); - filteredCommands!: Observable; - 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(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(`${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(`${this.baseUrl}/clients?&page=1&itemsPerPage=10000`).subscribe( - response => { - const clientIds = response['hydra:member'].map((client: any) => client['@id']); - const clientDetailsRequests: Observable[] = clientIds.map((id: string) => this.http.get(`${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' - }); - } - -} From ef3158a045e843acfa3af03513bead2aef3c5617 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 8 May 2025 08:06:57 +0200 Subject: [PATCH 28/47] refs #1922. Show new fields in clients table. --- ogWebconsole/src/app/app-routing.module.ts | 2 +- ogWebconsole/src/app/app.module.ts | 6 +- .../create-image/create-image.component.html | 10 ++ .../partition-assistant.component.ts | 18 ++- .../components/groups/groups.component.html | 12 +- .../client-details.component.html | 7 +- .../client-details.component.ts | 107 ++---------------- 7 files changed, 51 insertions(+), 111 deletions(-) diff --git a/ogWebconsole/src/app/app-routing.module.ts b/ogWebconsole/src/app/app-routing.module.ts index 3a0ca83..f901d43 100644 --- a/ogWebconsole/src/app/app-routing.module.ts +++ b/ogWebconsole/src/app/app-routing.module.ts @@ -17,7 +17,7 @@ import { CalendarComponent } from "./components/calendar/calendar.component"; import { CommandsComponent } from './components/commands/main-commands/commands.component'; import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component'; import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component'; -import { TaskLogsComponent } from './components/commands/commands-task/task-logs/task-logs.component'; +import { TaskLogsComponent } from './components/task-logs/task-logs.component'; import {SoftwareComponent} from "./components/software/software.component"; import {SoftwareProfileComponent} from "./components/software-profile/software-profile.component"; import {OperativeSystemComponent} from "./components/operative-system/operative-system.component"; diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 13cef17..9338131 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -67,7 +67,7 @@ import { PXEimagesComponent } from './components/ogboot/pxe-images/pxe-images.co import { CreatePXEImageComponent } from './components/ogboot/pxe-images/create-image/create-image/create-image.component'; import { InfoImageComponent } from './components/ogboot/pxe-images/info-image/info-image/info-image.component'; import { PxeComponent } from './components/ogboot/pxe/pxe.component'; -import { CreatePxeTemplateComponent } from './components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component'; +import { CreatePxeTemplateComponent } from './components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component'; import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component'; import { MatExpansionPanel, MatExpansionPanelDescription, MatExpansionPanelTitle } from "@angular/material/expansion"; import { OgbootStatusComponent } from './components/ogboot/ogboot-status/ogboot-status.component'; @@ -87,7 +87,7 @@ import { CreateCommandGroupComponent } from './components/commands/commands-grou import { DetailCommandGroupComponent } from './components/commands/commands-groups/detail-command-group/detail-command-group.component'; import { CreateTaskComponent } from './components/commands/commands-task/create-task/create-task.component'; import { DetailTaskComponent } from './components/commands/commands-task/detail-task/detail-task.component'; -import { TaskLogsComponent } from './components/commands/commands-task/task-logs/task-logs.component'; +import { TaskLogsComponent } from './components/task-logs/task-logs.component'; import { MatSliderModule } from '@angular/material/slider'; import { ImagesComponent } from './components/images/images.component'; import { CreateImageComponent } from './components/images/create-image/create-image.component'; @@ -117,7 +117,7 @@ import { CreateMultipleClientComponent } from './components/groups/shared/client import { ExportImageComponent } from './components/images/export-image/export-image.component'; import { ImportImageComponent } from "./components/repositories/import-image/import-image.component"; import { LoadingComponent } from './shared/loading/loading.component'; -import { InputDialogComponent } from './components/commands/commands-task/task-logs/input-dialog/input-dialog.component'; +import { InputDialogComponent } from './components/task-logs/input-dialog/input-dialog.component'; import { ManageOrganizationalUnitComponent } from './components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component'; import { BackupImageComponent } from './components/repositories/backup-image/backup-image.component'; import { ServerInfoDialogComponent } from "./components/ogdhcp/server-info-dialog/server-info-dialog.component"; diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html index 3369556..6c3be8f 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html @@ -13,6 +13,16 @@
+
+ + Tipo de imagen + + Monolítica + Git + + +
+
Nombre canónico diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts index 3bf2684..d5fbe0d 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts @@ -252,6 +252,8 @@ export class PartitionAssistantComponent implements OnInit{ if (disk) { const remainingGB = this.getRemainingGB(disk.partitions, disk.totalDiskSize); + console.log('Remaining GB:', remainingGB); + console.log('Total Disk Size:', disk); if (remainingGB > 0) { const removedPartitions = disk.partitions.filter((p) => !p.removed); @@ -259,7 +261,6 @@ export class PartitionAssistantComponent implements OnInit{ removedPartitions.length > 0 ? Math.max(...removedPartitions.map((p) => p.partitionNumber)) : 0; const newPartitionNumber = maxPartitionNumber + 1; - disk.partitions.push({ partitionNumber: newPartitionNumber, size: 0, @@ -303,10 +304,14 @@ export class PartitionAssistantComponent implements OnInit{ } getRemainingGB(partitions: Partition[], totalDiskSize: number): number { - const totalUsedGB = partitions.reduce((acc, partition) => acc + partition.size, 0); + const totalUsedGB = partitions + .filter(partition => !partition.removed) + .reduce((acc, partition) => acc + partition.size, 0); + return Math.max(0, totalDiskSize - totalUsedGB); } + save() { if (!this.selectedDisk) { this.toastService.error('No se ha seleccionado un disco.'); @@ -373,6 +378,10 @@ export class PartitionAssistantComponent implements OnInit{ if (partitionToRemove) { partitionToRemove.removed = true; } + + disk.used = this.calculateUsedSpace(disk.partitions); + disk.percentage = (disk.used / disk.totalDiskSize) * 100; + this.updateDiskChart(disk); this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); } @@ -391,9 +400,12 @@ export class PartitionAssistantComponent implements OnInit{ } calculateUsedSpace(partitions: Partition[]): number { - return partitions.reduce((acc, partition) => acc + partition.size, 0); + return partitions + .filter(partition => !partition.removed) + .reduce((acc, partition) => acc + partition.size, 0); } + generateChartData(partitions: Partition[]): any[] { return partitions.map((partition) => ({ name: `Partición ${partition.partitionNumber}`, diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index a338c2d..4fc74d1 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -371,15 +371,19 @@ {{ 'firmwareType' | translate }} - - {{ client.firmwareType ? client.firmwareType : 'N/A' }} + + {{ client.firmwareType }} OG Live - {{ client.ogLive?.date | date }} + +
+ {{ client.ogLive?.kernel }} + {{ client.ogLive?.date | date }} +
@@ -457,4 +461,4 @@
-
\ No newline at end of file +
diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html index 8beb74e..e7f3c9a 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html @@ -46,9 +46,14 @@ + - + + + + +
diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts index 788cb42..ddf03be 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts @@ -38,41 +38,27 @@ export class ClientDetailsComponent { view: [number, number] = [260, 160]; showLegend: boolean = true; - arrayCommands: any[] = [ - { name: 'Enceder', slug: 'power-on' }, - { name: 'Apagar', slug: 'power-off' }, - { name: 'Reiniciar', slug: 'reboot' }, - { name: 'Iniciar Sesión', slug: 'login' }, - { name: 'Crear imagen', slug: 'create-image' }, - { name: 'Clonar/desplegar imagen', slug: 'deploy-image' }, - { name: 'Eliminar Imagen Cache', slug: 'delete-image-cache' }, - { name: 'Particionar y Formatear', slug: 'partition' }, - { name: 'Inventario Software', slug: 'software-inventory' }, - { name: 'Inventario Hardware', slug: 'hardware-inventory' }, - { name: 'Ejecutar comando', slug: 'run-script' }, - ]; - datePipe: DatePipe = new DatePipe('es-ES'); columns = [ { columnDef: 'diskNumber', header: 'Disco', - cell: (partition: any) => `${partition.diskNumber}`, + cell: (partition: any) => partition.diskNumber, }, { columnDef: 'partitionNumber', header: 'Particion', - cell: (partition: any) => `${partition.partitionNumber}` + cell: (partition: any) => partition.partitionNumber }, { columnDef: 'partitionCode', header: 'Tipo de partición', - cell: (partition: any) => `${partition.partitionCode}` + cell: (partition: any) => partition.partitionCode }, { columnDef: 'description', header: 'Sistema de ficheros', - cell: (partition: any) => `${partition.filesystem}` + cell: (partition: any) => partition.filesystem }, { columnDef: 'size', @@ -82,12 +68,12 @@ export class ClientDetailsComponent { { columnDef: 'memoryUsage', header: 'Uso', - cell: (partition: any) => `${partition.memoryUsage} %` + cell: (partition: any) => `${partition.memoryUsage}%` }, { columnDef: 'operativeSystem', header: 'SO', - cell: (partition: any) => `${partition.operativeSystem?.name}` + cell: (partition: any) => partition.operativeSystem?.name }, ]; displayedColumns = [...this.columns.map(column => column.columnDef)]; @@ -121,21 +107,6 @@ export class ClientDetailsComponent { } } - loadClient = (uuid: string) => { - this.http.get(`${this.baseUrl}${uuid}`).subscribe({ - next: data => { - this.clientData = data; - this.updateGeneralData(); - this.updateNetworkData(); - this.loadPartitions() - this.loading = false; - }, - error: error => { - console.error('Error al obtener el cliente:', error); - } - }); - } - updateGeneralData() { this.generalData = [ { property: 'Nombre', value: this.clientData?.name || '' }, @@ -150,7 +121,7 @@ export class ClientDetailsComponent { updateNetworkData() { this.networkData = [ { property: 'Padre', value: this.clientData?.organizationalUnit?.name || '' }, - { property: 'Pxe', value: this.clientData?.template?.name || '' }, + { property: 'Pxe', value: this.clientData?.pxeTemplate?.name || '' }, { property: 'Remote Pc', value: this.clientData.remotePc || '' }, { property: 'Subred', value: this.clientData?.subnet || '' }, { property: 'OGlive', value: this.clientData?.ogLive?.name || '' }, @@ -205,21 +176,14 @@ export class ClientDetailsComponent { this.isDiskUsageEmpty = this.diskUsageData.length === 0; } - onEditClick(event: MouseEvent, uuid: string): void { - event.stopPropagation(); - const dialogRef = this.dialog.open(ManageClientComponent, { data: { uuid }, width: '900px' }); - dialogRef.afterClosed().subscribe(); - } - loadPartitions(): void { if (!this.clientData?.id) { - console.error('El ID del cliente no está disponible.'); return; } this.http.get(`${this.baseUrl}/partitions?client.id=${this.clientData.id}&order[diskNumber, partitionNumber]=ASC`).subscribe({ next: data => { - const filteredPartitions = data['hydra:member'].filter((partition: any) => partition.partitionNumber !== 0); + const filteredPartitions = data['hydra:member']; this.dataSource = filteredPartitions; this.partitions = filteredPartitions; this.calculateDiskUsage(); @@ -233,59 +197,4 @@ export class ClientDetailsComponent { onNoClick(): void { this.dialog.closeAll(); } - - rebootClient(): void { - this.http.post(`${this.baseUrl}/clients/server/${this.clientData.uuid}/reboot`, {}).subscribe( - response => { - this.toastService.success('Cliente actualizado correctamente'); - }, - error => { - this.toastService.error('Error de conexión con el cliente'); - } - ); - } - - powerOnClient(): void { - const payload = { - client: this.clientData['@id'] - } - - this.http.post(`${this.baseUrl}${this.clientData.repository['@id']}/wol`, payload).subscribe( - response => { - this.toastService.success('Cliente actualizado correctamente'); - }, - error => { - this.toastService.error('Error de conexión con el cliente'); - } - ); - } - - powerOffClient(): void { - this.http.post(`${this.baseUrl}/clients/server/${this.clientData.uuid}/power-off`, {}).subscribe( - response => { - this.toastService.success('Cliente actualizado correctamente'); - }, - error => { - this.toastService.error('Error de conexión con el cliente'); - } - ); - } - - openPartitionAssistant(): void { - this.router.navigate([`/clients/${this.clientData.uuid}/partition-assistant`]).then(r => { - console.log('navigated', r); - }); - } - - openCreateImageAssistant(): void { - this.router.navigate([`/clients/${this.clientData.uuid}/create-image`]).then(r => { - console.log('navigated', r); - }); - } - - openDeployImageAssistant(): void { - this.router.navigate([`/clients/deploy-image`]).then(r => { - console.log('navigated', r); - }); - } } From 9a84e45cb8a783748b7db35db68ec824cf2703b5 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 8 May 2025 09:05:56 +0200 Subject: [PATCH 29/47] Pushed app.module --- ogWebconsole/src/app/app.module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 9338131..7b63f13 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -129,7 +129,7 @@ import { ShowClientsComponent } from './components/ogdhcp/show-clients/show-clie import { OperationResultDialogComponent } from './components/ogdhcp/operation-result-dialog/operation-result-dialog.component'; import { ManageClientComponent } from './components/groups/shared/clients/manage-client/manage-client.component'; import { ConvertImageComponent } from './components/repositories/convert-image/convert-image.component'; -import { registerLocaleData } from '@angular/common'; +import {NgOptimizedImage, registerLocaleData} from '@angular/common'; import localeEs from '@angular/common/locales/es'; import { GlobalStatusComponent } from './components/global-status/global-status.component'; import { ShowMonoliticImagesComponent } from './components/repositories/show-monolitic-images/show-monolitic-images.component'; @@ -304,7 +304,7 @@ registerLocaleData(localeEs, 'es-ES'); progressAnimation: 'increasing', closeButton: true } - ), MatGridList, MatTree, MatTreeNode, MatNestedTreeNode, MatTreeNodeToggle, MatTreeNodeDef, MatTreeNodePadding, MatTreeNodeOutlet, MatPaginator, MatGridTile, MatExpansionPanel, MatExpansionPanelTitle, MatExpansionPanelDescription, MatRadioGroup, MatRadioButton, MatAutocompleteTrigger + ), MatGridList, MatTree, MatTreeNode, MatNestedTreeNode, MatTreeNodeToggle, MatTreeNodeDef, MatTreeNodePadding, MatTreeNodeOutlet, MatPaginator, MatGridTile, MatExpansionPanel, MatExpansionPanelTitle, MatExpansionPanelDescription, MatRadioGroup, MatRadioButton, MatAutocompleteTrigger, NgOptimizedImage ], schemas: [ CUSTOM_ELEMENTS_SCHEMA, From 81766471eebd8c9fa894bb579fb5355e2efb5c73 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 8 May 2025 09:06:20 +0200 Subject: [PATCH 30/47] Fixed wrong dir --- .../input-dialog/input-dialog.component.css | 0 .../input-dialog/input-dialog.component.html | 7 + .../input-dialog.component.spec.ts | 47 +++ .../input-dialog/input-dialog.component.ts | 18 + .../task-logs/task-logs.component.css | 118 +++++++ .../task-logs/task-logs.component.html | 128 ++++++++ .../task-logs/task-logs.component.ts | 309 ++++++++++++++++++ 7 files changed, 627 insertions(+) create mode 100644 ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.css create mode 100644 ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.html create mode 100644 ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.spec.ts create mode 100644 ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.ts create mode 100644 ogWebconsole/src/app/components/task-logs/task-logs.component.css create mode 100644 ogWebconsole/src/app/components/task-logs/task-logs.component.html create mode 100644 ogWebconsole/src/app/components/task-logs/task-logs.component.ts diff --git a/ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.css b/ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.html b/ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.html new file mode 100644 index 0000000..eed69e9 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.html @@ -0,0 +1,7 @@ +

{{ 'inputDetails' | translate }}

+
+
{{ data.input | json }}
+
+
+ +
diff --git a/ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.spec.ts b/ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.spec.ts new file mode 100644 index 0000000..7168136 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.spec.ts @@ -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; + + 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(); + }); +}); diff --git a/ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.ts b/ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.ts new file mode 100644 index 0000000..744b415 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/input-dialog/input-dialog.component.ts @@ -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, + @Inject(MAT_DIALOG_DATA) public data: { input: any } + ) {} + + close(): void { + this.dialogRef.close(); + } +} diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.css b/ogWebconsole/src/app/components/task-logs/task-logs.component.css new file mode 100644 index 0000000..195127a --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.css @@ -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; +} diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.html b/ogWebconsole/src/app/components/task-logs/task-logs.component.html new file mode 100644 index 0000000..f7ceea4 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.html @@ -0,0 +1,128 @@ +
+ + +
+

{{ 'adminCommandsTitle' | + translate }}

+
+ +
+ +
+
+ +
+ + + + + {{ client.name }} + + + + + + + + + {{ command.name }} + + + + + + Estado + + Todos + Fallido + Pendiente de ejecutar + Ejecutando + Completado con éxito + Cancelado + + +
+ + + +
+ + + + + + + + + +
{{ column.header }} + + + + +
+ + + {{trace.progress}}% +
+
+ +
+ + {{ + 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 + }} + + +
+
+
+ + + + + + + {{ column.cell(trace) }} + +
+ +
+
+ +
+ + +
diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts new file mode 100644 index 0000000..7da0b89 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts @@ -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; + clientControl = new FormControl(); + filteredCommands!: Observable; + 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(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(`${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(`${this.baseUrl}/clients?&page=1&itemsPerPage=10000`).subscribe( + response => { + const clientIds = response['hydra:member'].map((client: any) => client['@id']); + const clientDetailsRequests: Observable[] = clientIds.map((id: string) => this.http.get(`${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' + }); + } + +} From a5730a1de41549bd6a7b551c25c9ebe89fde2684 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 8 May 2025 12:54:58 +0200 Subject: [PATCH 31/47] refs #1971. Trace UX changes. Some improvements and fixed filters --- ogWebconsole/src/app/app.module.ts | 4 +- .../output-dialog/output-dialog.component.css | 0 .../output-dialog.component.html | 7 ++ .../output-dialog.component.spec.ts | 23 ++++ .../output-dialog/output-dialog.component.ts | 18 +++ .../task-logs/task-logs.component.html | 97 +++++++++++---- .../task-logs/task-logs.component.ts | 110 +++++++++++------- .../src/app/services/translation.service.ts | 37 ++++++ .../src/app/shared/constants/command-types.ts | 46 ++++++++ ogWebconsole/src/locale/en.json | 27 ++++- ogWebconsole/src/locale/es.json | 32 +++-- 11 files changed, 325 insertions(+), 76 deletions(-) create mode 100644 ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.css create mode 100644 ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.html create mode 100644 ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.spec.ts create mode 100644 ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.ts create mode 100644 ogWebconsole/src/app/services/translation.service.ts create mode 100644 ogWebconsole/src/app/shared/constants/command-types.ts diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 7b63f13..07f2dcb 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -149,6 +149,7 @@ import { ShowTaskScheduleComponent } from './components/commands/commands-task/s import { ShowTaskScriptComponent } from './components/commands/commands-task/show-task-script/show-task-script.component'; import { CreateTaskScriptComponent } from './components/commands/commands-task/create-task-script/create-task-script.component'; import { ViewParametersModalComponent } from './components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component'; +import { OutputDialogComponent } from './components/task-logs/output-dialog/output-dialog.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); @@ -255,7 +256,8 @@ registerLocaleData(localeEs, 'es-ES'); ShowTaskScheduleComponent, ShowTaskScriptComponent, CreateTaskScriptComponent, - ViewParametersModalComponent + ViewParametersModalComponent, + OutputDialogComponent ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.css b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.html b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.html new file mode 100644 index 0000000..eed69e9 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.html @@ -0,0 +1,7 @@ +

{{ 'inputDetails' | translate }}

+
+
{{ data.input | json }}
+
+
+ +
diff --git a/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.spec.ts b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.spec.ts new file mode 100644 index 0000000..d690f31 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OutputDialogComponent } from './output-dialog.component'; + +describe('OutputDialogComponent', () => { + let component: OutputDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [OutputDialogComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(OutputDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.ts b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.ts new file mode 100644 index 0000000..fe47c50 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.ts @@ -0,0 +1,18 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; + +@Component({ + selector: 'app-output-dialog', + templateUrl: './output-dialog.component.html', + styleUrl: './output-dialog.component.css' +}) +export class OutputDialogComponent { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { input: any } + ) {} + + close(): void { + this.dialogRef.close(); + } +} diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.html b/ogWebconsole/src/app/components/task-logs/task-logs.component.html index f7ceea4..37276fa 100644 --- a/ogWebconsole/src/app/components/task-logs/task-logs.component.html +++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.html @@ -19,38 +19,54 @@
- - {{ client.name }} +
+ {{ client.name }} + + {{ client.ip }} — {{ client.mac }} + +
+
+ + Por favor, ingrese el nombre del cliente
- - - - - {{ command.name }} + + {{ 'commandSelectStepText' | translate }} + + + {{ translateCommand(command.name) }} - + + + Estado - - Todos + Fallido Pendiente de ejecutar Ejecutando Completado con éxito Cancelado +
@@ -82,10 +98,10 @@ 'chip-cancelled': trace.status === 'cancelled' }"> {{ - trace.status === 'failed' ? 'Fallido' : + trace.status === 'failed' ? 'Error' : trace.status === 'in-progress' ? 'En ejecución' : - trace.status === 'success' ? 'Finalizado con éxito' : - trace.status === 'pending' ? 'Pendiente de ejecutar' : + trace.status === 'success' ? 'Completado' : + trace.status === 'pending' ? 'Pendiente' : trace.status === 'cancelled' ? 'Cancelado' : trace.status }} @@ -101,20 +117,59 @@ - - + +
+ {{ translateCommand(trace.command) }} + {{ trace.jobId }} +
+ +
+ {{ trace.client?.name }} + {{ trace.client?.ip }} +
+
+ + + +
+ {{ trace.executedAt |date: 'dd/MM/yyyy hh:mm:ss'}} +
+
+ + +
+ {{ trace.finishedAt |date: 'dd/MM/yyyy hh:mm:ss'}} +
+
{{ column.cell(trace) }} -
+ + + {{ 'columnActions' | translate }} + + + + + diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts index 7da0b89..d2f1353 100644 --- a/ogWebconsole/src/app/components/task-logs/task-logs.component.ts +++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts @@ -11,6 +11,9 @@ 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'; +import {OutputDialogComponent} from "./output-dialog/output-dialog.component"; +import {TranslationService} from "@services/translation.service"; +import { COMMAND_TYPES } from '../../shared/constants/command-types'; @Component({ selector: 'app-task-logs', @@ -34,6 +37,12 @@ export class TaskLogsComponent implements OnInit { progress = 0; bufferValue = 0; + filteredCommands2 = Object.keys(COMMAND_TYPES).map(key => ({ + name: key, + value: key, + label: COMMAND_TYPES[key] + })); + columns = [ { columnDef: 'id', @@ -47,7 +56,7 @@ export class TaskLogsComponent implements OnInit { }, { columnDef: 'client', - header: 'Client', + header: 'Cliente', cell: (trace: any) => trace.client?.name }, { @@ -55,24 +64,9 @@ export class TaskLogsComponent implements OnInit { 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', + header: 'Ejecución', cell: (trace: any) => this.datePipe.transform(trace.executedAt, 'dd/MM/yyyy hh:mm:ss'), }, { @@ -81,7 +75,7 @@ export class TaskLogsComponent implements OnInit { cell: (trace: any) => this.datePipe.transform(trace.finishedAt, 'dd/MM/yyyy hh:mm:ss'), }, ]; - displayedColumns = [...this.columns.map(column => column.columnDef)]; + displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; filters: { [key: string]: string } = {}; filteredClients!: Observable; @@ -94,7 +88,8 @@ export class TaskLogsComponent implements OnInit { private dialog: MatDialog, private cdr: ChangeDetectorRef, private configService: ConfigService, - private toastService: ToastrService + private toastService: ToastrService, + private translationService: TranslationService ) { this.baseUrl = this.configService.apiUrl; this.mercureUrl = this.configService.mercureUrl; @@ -103,7 +98,7 @@ export class TaskLogsComponent implements OnInit { ngOnInit(): void { this.loadTraces(); this.loadCommands(); - //this.loadClients(); + this.loadClients(); this.filteredCommands = this.commandControl.valueChanges.pipe( startWith(''), map(value => (typeof value === 'string' ? value : value?.name)), @@ -147,11 +142,17 @@ export class TaskLogsComponent implements OnInit { } - private _filterClients(name: string): any[] { - const filterValue = name.toLowerCase(); - return this.clients.filter(client => client.name.toLowerCase().includes(filterValue)); + private _filterClients(value: string): any[] { + const filterValue = value.toLowerCase(); + + return this.clients.filter(client => + client.name?.toLowerCase().includes(filterValue) || + client.ip?.toLowerCase().includes(filterValue) || + client.mac?.toLowerCase().includes(filterValue) + ); } + private _filterCommands(name: string): any[] { const filterValue = name.toLowerCase(); return this.commands.filter(command => command.name.toLowerCase().includes(filterValue)); @@ -161,12 +162,13 @@ export class TaskLogsComponent implements OnInit { return client && client.name ? client.name : ''; } - displayFnCommand(command: any): string { - return command && command.name ? command.name : ''; + onOptionCommandSelected(selectedCommand: any): void { + this.filters['command'] = selectedCommand.name; + this.loadTraces(); } - onOptionCommandSelected(selectedCommand: any): void { - this.filters['command.id'] = selectedCommand.id; + onOptionStatusSelected(selectedStatus: any): void { + this.filters['status'] = selectedStatus; this.loadTraces(); } @@ -177,11 +179,19 @@ export class TaskLogsComponent implements OnInit { openInputModal(inputData: any): void { this.dialog.open(InputDialogComponent, { - width: '700px', + width: '70vw', + height: '60vh', data: { input: inputData } }); } + openOutputModal(outputData: any): void { + this.dialog.open(OutputDialogComponent, { + width: '500px', + data: { input: outputData } + }); + } + cancelTrace(trace: any): void { this.dialog.open(DeleteModalComponent, { width: '300px', @@ -239,28 +249,19 @@ export class TaskLogsComponent implements OnInit { loadClients() { this.loading = true; - this.http.get(`${this.baseUrl}/clients?&page=1&itemsPerPage=10000`).subscribe( + this.http.get(`${this.baseUrl}/clients?page=1&itemsPerPage=10000`).subscribe( response => { - const clientIds = response['hydra:member'].map((client: any) => client['@id']); - const clientDetailsRequests: Observable[] = clientIds.map((id: string) => this.http.get(`${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; - } - ); + this.clients = response['hydra:member']; + this.loading = false; }, - (error: any) => { + error => { console.error('Error fetching clients:', error); this.loading = false; } ); } + resetFilters() { this.loading = true; this.filters = {}; @@ -291,6 +292,31 @@ export class TaskLogsComponent implements OnInit { this.loadTraces(); } + translateCommand(command: string): string { + return this.translationService.getCommandTranslation(command); + } + + clearCommandFilter(event: Event, clientSearchCommandInput: any): void { + event.stopPropagation(); + delete this.filters['command']; + clientSearchCommandInput.value = null; + this.loadTraces() + } + + clearStatusFilter(event: Event, clientSearchStatusInput: any): void { + event.stopPropagation(); + delete this.filters['status']; + clientSearchStatusInput.value = null; + this.loadTraces() + } + + clearClientFilter(event: Event, clientSearchClientInput: any): void { + event.stopPropagation(); + delete this.filters['client.id']; + clientSearchClientInput.value = null; + this.loadTraces() + } + iniciarTour(): void { this.joyrideService.startTour({ steps: [ diff --git a/ogWebconsole/src/app/services/translation.service.ts b/ogWebconsole/src/app/services/translation.service.ts new file mode 100644 index 0000000..b7144a9 --- /dev/null +++ b/ogWebconsole/src/app/services/translation.service.ts @@ -0,0 +1,37 @@ + +import { Injectable } from '@angular/core'; +import { COMMAND_TYPES } from '../shared/constants/command-types'; + +@Injectable({ + providedIn: 'root' +}) +export class TranslationService { + private currentLang: string = 'es'; + + constructor() {} + + getCommandTranslation(command: string): string { + switch(command) { + case 'install-oglive': + return COMMAND_TYPES['install-oglive'][this.currentLang]; + case 'create-aux-file': + return COMMAND_TYPES['create-aux-file'][this.currentLang]; + case 'convert-image-to-virtual': + return COMMAND_TYPES['convert-image-to-virtual'][this.currentLang]; + case 'deploy-image': + return COMMAND_TYPES['deploy-image'][this.currentLang]; + case 'create-image': + return COMMAND_TYPES['create-image'][this.currentLang]; + case 'reboot': + return COMMAND_TYPES.reboot[this.currentLang]; + case 'shutdown': + return COMMAND_TYPES.shutdown[this.currentLang]; + case 'partition-and-format': + return COMMAND_TYPES['partition-and-format'][this.currentLang]; + case 'run-script': + return COMMAND_TYPES['run-script'][this.currentLang]; + default: + return command; + } + } +} diff --git a/ogWebconsole/src/app/shared/constants/command-types.ts b/ogWebconsole/src/app/shared/constants/command-types.ts new file mode 100644 index 0000000..ef81cb5 --- /dev/null +++ b/ogWebconsole/src/app/shared/constants/command-types.ts @@ -0,0 +1,46 @@ +export const COMMAND_TYPES: any = { + 'create-image': { + en: 'Create Image', + es: 'Crear Imagen' + }, + + 'deploy-image': { + en: 'Deploy Image', + es: 'Desplegar Imagen' + }, + + 'install-oglive': { + en: 'Install OGLive', + es: 'Instalar OGLive' + }, + + 'create-aux-file': { + en: 'Create Auxiliary File', + es: 'Crear archivos auxiliares' + }, + + 'convert-image-to-virtual': { + en: 'Convert Image to Virtual', + es: 'Convertir imagen a virtual' + }, + + reboot: { + en: 'Reboot', + es: 'Reiniciar' + }, + + shutdown: { + en: 'Shutdown', + es: 'Apagar' + }, + + 'partition-and-format': { + en: 'Partition and Format', + es: 'Particionar y Formatear' + }, + + 'run-script': { + en: 'Run Script', + es: 'Ejecutar Script' + } +}; diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index e829ced..b1de3d8 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -51,12 +51,14 @@ "checkboxOrgMinimal": "Minimal Organizational Unit", "checkboxUserRole": "User", "groupsTitleStepText": "On this screen, you can manage the main organizational units (Faculties, Classrooms, Classroom Groups, and clients).", + "repositoryTitleStepText": "On this screen, you can manage the repositories.", + "tableDateRepositoryText": "Use the table to view the details of the repositories.", "titleStepText": "On this screen, you can manage the calendars of remote teams connected to the UDS service", "groupsAddStepText": "Click to add a new organizational unit or clients.", "adminCalendarsTitle": "Manage calendars", "addButtonStepText": "Click here to add a new calendar.", "addCalendar": "Add calendar", - "searchStepText": "Use this search bar to filter existing calendars.", + "searchStepText": "Use this search bar to filter existing entities.", "searchCalendarLabel": "Search calendar name", "tableStepText": "Here are the existing calendars with their characteristics and settings.", "actionsStepText": "Access the available actions for each calendar here.", @@ -107,7 +109,7 @@ "statusColumn": "Status", "enabled": "Enabled", "disabled": "Disabled", - "adminCommandsTitle": "Command and procedure traces", + "adminCommandsTitle": "Trace Commands", "resetFiltersStepText": "Click to reset the applied filters and see all traces.", "resetFilters": "Reset filters", "clientSelectStepText": "Select a client to see the associated traces.", @@ -183,6 +185,7 @@ "editUnitTooltip": "Edit this organizational unit", "viewUnitTooltip": "View organizational unit details", "viewUnitMenu": "View data", + "tableDateStep": "Use the table to view the details", "addInternalUnitTooltip": "Create a new internal organizational unit", "addClientTooltip": "Register a client in this organizational unit", "deleteElementTooltip": "Delete this element", @@ -359,10 +362,12 @@ "nameColumnHeader": "Name", "templateColumnHeader": "Template", "pxeImageTitle": "Information on ogBoot server", + "tableDatePxeTemplateText": "Use the table to view the details of the PXE templates.", + "searchIsDefaultText": "Search for default images", "serverInfoDescription": "Access information and synchronization options on the OgBoot server.", "syncDatabaseButton": "Sync database", "viewInfoButton": "View Information", - "adminImagesDescription": "From here you can manage the images configured on the OgBoot server.", + "adminImagesDescription": "From here you can manage the ogLive images configured on the OgBoot server.", "actionsDescription": "Manage each image with options to view, edit, delete, and more.", "addClientButton": "Add client", "searchClientNameLabel": "Search client name", @@ -427,6 +432,7 @@ "boot": "Boot", "TOOLTIP_BOOT": "Configure and manage boot options", "ogLive": "ogLive", + "viewInfoStepText": "View information about the OgLive server", "TOOLTIP_PXE_IMAGES": "View available PXE boot images", "pxeTemplates": "PXE Templates", "pxeTemplate": "Template", @@ -488,6 +494,9 @@ "processes": "Processes", "usedPercentageLabel": "Used", "errorLoadingData": "Error fetching data. Service not available", + "repository": "Repository", + "addRepository": "Add repository", + "addRepositoryStepText": "Click to add a new repository.", "repositoryTitleStep": "On this screen you can manage image repositories.", "partitions": "Partitions", "clientsViewStepText": "Display of the selected organizational unit's clients", @@ -510,5 +519,13 @@ }, "remoteAccess": "Remote access available", "noRemoteAccess": "Remote access not available", - "capacityWarning": "The capacity cannot be negative" -} \ No newline at end of file + "capacityWarning": "The capacity cannot be negative", + "monolithicImageStep": "Monolithic image", + "monolithicImageStepText": "On this screen you can manage monolithic images.", + "monolithicImage": "Monolithic image", + "gitImage": "Git images", + "gitImageStep": "Git images", + "gitImageStepText": "On this screen you can manage git images.", + "partitions": "Particiones", + "isDefaultLabel": "Default" +} diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 92d37a3..0ebb6e3 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -1,7 +1,7 @@ { "Administration": "Administración", "changePassword": "Cambiar contraseña", - "logout": "Salir", + "logout": "Cerrar sesión", "loginlabelUsername": "Introduce tu usuario", "loginlabelPassword": "Introduce tu contraseña", "buttonLogin": "Login", @@ -56,7 +56,7 @@ "adminCalendarsTitle": "Administrar calendarios", "addButtonStepText": "Haz clic aquí para añadir un nuevo calendario.", "addCalendar": "Añadir calendario", - "searchStepText": "Utiliza esta barra de búsqueda para filtrar los calendarios existentes.", + "searchStepText": "Utiliza esta barra de búsqueda para filtrar las entidades existentes.", "searchCalendarLabel": "Buscar nombre de calendario", "tableStepText": "Aquí se muestran los calendarios existentes con sus características y configuraciones.", "actionsStepText": "Accede a las acciones disponibles para cada calendario aquí.", @@ -71,6 +71,7 @@ "reasonPlaceholder": "Razón para la excepción", "startDate": "Fecha de inicio", "endDate": "Fecha de fin", + "tableDateStep": "Esta tabla muestra los datos asociados al modulo en concreto", "buttonSave": "Guardar", "adminCommandGroupsTitle": "Administrar Grupos de Comandos", "addCommandGroupStepText": "Haz clic para añadir un nuevo grupo de comandos.", @@ -107,13 +108,13 @@ "statusColumn": "Estado", "enabled": "Habilitado", "disabled": "Deshabilitado", - "adminCommandsTitle": "Trazas de comandos y procedimientos", + "adminCommandsTitle": "Traza de ejecución", "CommandsTitle": "Administrar Comandos", "resetFiltersStepText": "Haz clic para reiniciar los filtros aplicados y ver todas las trazas.", "resetFilters": "Reiniciar filtros", "clientSelectStepText": "Selecciona un cliente para ver las trazas asociadas.", "filterClientPlaceholder": "Filtrar por cliente", - "commandSelectStepText": "Selecciona un comando para ver las trazas específicas de ese comando.", + "commandSelectStepText": "Selecciona un comando para ver las trazas.", "filterCommandPlaceholder": "Filtrar por comando", "taskDetailsTitle": "Detalles de la Tarea", "taskId": "ID de la Tarea", @@ -263,6 +264,7 @@ "ipLabel": "Dirección IP", "ipError": "Formato de dirección IP inválido. Ejemplo válido: 127.0.0.1", "templateLabel": "Plantilla PXE", + "templateContent": "Contenido de la plantilla PXE", "digitalBoard": "Pizarra digital", "projectorAlt": "Proyector", "clientAlt": "Cliente", @@ -360,10 +362,12 @@ "nameColumnHeader": "Nombre", "templateColumnHeader": "Plantilla", "pxeImageTitle": "Información en servidor ogBoot", + "tableDatePxeTemplateText": "Esta tabla muestra los datos asociados a las plantillas PXE existentes.", + "searchIsDefaultText": "Filtra para mostrar solo las imágenes por defecto o no por defecto.", "serverInfoDescription": "Accede a información y opciones de sincronización en el servidor OgBoot.", "syncDatabaseButton": "Sincronizar base de datos", "viewInfoButton": "Ver Información", - "adminImagesDescription": "Desde aquí puedes gestionar las imágenes configuradas en el servidor OgBoot.", + "adminImagesDescription": "Desde aquí puedes gestionar las imágenes ogLive.", "actionsDescription": "Administra cada imagen con opciones para ver, editar, eliminar y más.", "addClientButton": "Añadir cliente", "searchClientNameLabel": "Buscar nombre de cliente", @@ -429,6 +433,7 @@ "boot": "Boot", "TOOLTIP_BOOT": "Configurar y administrar opciones de arranque", "ogLive": "ogLive", + "viewInfoStepText": "Accede a información y opciones en la API de OgBoot.", "TOOLTIP_PXE_IMAGES": "Ver imágenes disponibles para arranque PXE", "pxeTemplates": "Plantillas PXE", "pxeTemplate": "Plantilla", @@ -490,6 +495,9 @@ "processes": "Procesos", "usedPercentageLabel": "Usado", "errorLoadingData": "Error al cargar los datos. Servicio inactivo", + "repository": "Repositorio", + "addRepository": "Añadir repositorio", + "addRepositoryStepText": "Haz clic para añadir un nuevo repositorio.", "repositoryTitleStep": "En esta pantalla se pueden gestionar los repositorios de imágenes.", "partitions": "Particiones", "clientsViewStepText": "Visualización de los clientes de la unidad organizativa seleccionada", @@ -512,5 +520,15 @@ }, "remoteAccess": "Disponible acceso remoto", "noRemoteAccess": "No disponible acceso remoto", - "capacityWarning": "El aforo no puede ser" -} \ No newline at end of file + "capacityWarning": "El aforo no puede ser", + "tableDateRepositoryText": "Esta tabla muestra los datos asociados a los repositorios existentes.", + "repositoryTitleStepText": "En esta pantalla se pueden gestionar los repositorios de imágenes.", + "monolithicImageStep": "Imagen monolítica", + "monolithicImageStepText": "Esta opción permite visualizar las imágenes monolíticas disponibles en el servidor.", + "gitImageStep": "Imágenes Git", + "monolithicImage": "Imagenes monolíticas", + "gitImage": "Imágenes Git", + "gitImageStepText": "Esta opción permite visualizar las imágenes Git disponibles en el servidor.", + "partitions": "Particiones", + "isDefaultLabel": "Por defecto" +} From 242f7a374cd5f8e036eb6e33795db911fbb66128 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 8 May 2025 12:56:14 +0200 Subject: [PATCH 32/47] refs #1972. General styles changed. New 2025 --- .../app/layout/header/header.component.css | 14 ++-- .../app/layout/header/header.component.html | 7 +- .../main-layout/main-layout.component.css | 29 ++++++-- .../main-layout/main-layout.component.html | 7 +- .../app/layout/sidebar/sidebar.component.css | 68 +++++++++++++++++-- .../app/layout/sidebar/sidebar.component.html | 20 +++--- 6 files changed, 110 insertions(+), 35 deletions(-) diff --git a/ogWebconsole/src/app/layout/header/header.component.css b/ogWebconsole/src/app/layout/header/header.component.css index ec5f5c3..72b60ae 100644 --- a/ogWebconsole/src/app/layout/header/header.component.css +++ b/ogWebconsole/src/app/layout/header/header.component.css @@ -2,12 +2,8 @@ mat-toolbar { /*height: 7vh;*/ min-height: 65px; min-width: 375px; - background-color: #3f51b5; - color: white; -} - -.trace-button .mat-icon { - color: #ffffff; + background-color: #e2e8f0; + color: black; } .navbar-actions-row { @@ -51,6 +47,10 @@ mat-toolbar { display: none; } +.trace-button { + color: #3f51b5; +} + @media (min-width: 1500px) { .hide-on-small { display: inline; @@ -80,4 +80,4 @@ mat-toolbar { .smallScreenMenubutton { margin-right: 2vh; } -} \ No newline at end of file +} diff --git a/ogWebconsole/src/app/layout/header/header.component.html b/ogWebconsole/src/app/layout/header/header.component.html index 3413417..e9c1a50 100644 --- a/ogWebconsole/src/app/layout/header/header.component.html +++ b/ogWebconsole/src/app/layout/header/header.component.html @@ -1,7 +1,6 @@ - Opengnsys webconsole -
+
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 7a699b6..dbd45c6 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -28,6 +28,7 @@ import { BreakpointObserver } from '@angular/cdk/layout'; import { MatMenuTrigger } from '@angular/material/menu'; import { ClientDetailsComponent } from './shared/client-details/client-details.component'; import { PartitionTypeOrganizatorComponent } from './shared/partition-type-organizator/partition-type-organizator.component'; +import { ClientTaskLogsComponent } from '../task-logs/client-task-logs/client-task-logs.component'; enum NodeType { OrganizationalUnit = 'organizational-unit', @@ -861,4 +862,13 @@ export class GroupsComponent implements OnInit, OnDestroy { data: simplifiedClientsData }); } + + openClientTaskLogs(event: MouseEvent, client: Client): void { + event.stopPropagation(); + + this.dialog.open(ClientTaskLogsComponent, { + width: '1200px', + data: {client} + }) + } } diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css new file mode 100644 index 0000000..e69de29 diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html new file mode 100644 index 0000000..651b9f4 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html @@ -0,0 +1 @@ +

client-task-logs works!

diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.spec.ts b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.spec.ts new file mode 100644 index 0000000..ee38857 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ClientTaskLogsComponent } from './client-task-logs.component'; + +describe('ClientTaskLogsComponent', () => { + let component: ClientTaskLogsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ClientTaskLogsComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ClientTaskLogsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts new file mode 100644 index 0000000..3502328 --- /dev/null +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts @@ -0,0 +1,58 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { ConfigService } from '@services/config.service'; + +@Component({ + selector: 'app-client-task-logs', + templateUrl: './client-task-logs.component.html', + styleUrls: ['./client-task-logs.component.css'] +}) +export class ClientTaskLogsComponent implements OnInit { + + traces: any[] = []; + loading = false; + itemsPerPage: number = 20; + page: number = 0; + length: number = 0; + baseUrl: string; + + constructor( + @Inject(MAT_DIALOG_DATA) public data: { client: any }, + private http: HttpClient, + private configService: ConfigService, + ) { + this.baseUrl = this.configService.apiUrl; + } + + ngOnInit(): void { + this.loadTraces(); + } + + loadTraces(): void { + const clientId = this.data.client?.id; + if (!clientId) return; + + this.loading = true; + + const params = new HttpParams() + .set('client.id', clientId) + .set('page', (this.page + 1).toString()) + .set('itemsPerPage', this.itemsPerPage.toString()); + + const url = `${this.baseUrl}/traces`; + + this.http.get(url, { params }).subscribe( + (data) => { + this.traces = data['hydra:member']; + this.length = data['hydra:totalItems']; + this.loading = false; + }, + (error) => { + console.error('Error fetching client traces', error); + this.loading = false; + } + ); + } + +} diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index b1de3d8..e968708 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -520,6 +520,7 @@ "remoteAccess": "Remote access available", "noRemoteAccess": "Remote access not available", "capacityWarning": "The capacity cannot be negative", + "procedimientosCliente": "Procedures", "monolithicImageStep": "Monolithic image", "monolithicImageStepText": "On this screen you can manage monolithic images.", "monolithicImage": "Monolithic image", diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 0ebb6e3..ec3f35b 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -521,6 +521,7 @@ "remoteAccess": "Disponible acceso remoto", "noRemoteAccess": "No disponible acceso remoto", "capacityWarning": "El aforo no puede ser", + "procedimientosCliente": "Procedimientos", "tableDateRepositoryText": "Esta tabla muestra los datos asociados a los repositorios existentes.", "repositoryTitleStepText": "En esta pantalla se pueden gestionar los repositorios de imágenes.", "monolithicImageStep": "Imagen monolítica", From 60684d2c5018f4740dc505850e13f885dee92a69 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Thu, 8 May 2025 13:59:24 +0200 Subject: [PATCH 34/47] Implement ClientTaskLogs component with enhanced UI and functionality --- .../client-task-logs.component.css | 124 +++++++++ .../client-task-logs.component.html | 162 +++++++++++- .../client-task-logs.component.ts | 241 +++++++++++++++++- 3 files changed, 518 insertions(+), 9 deletions(-) diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css index e69de29..fa2c0ba 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css @@ -0,0 +1,124 @@ +.modal-content { + max-height: 85vh; + overflow-y: auto; + padding: 1rem; +} + +.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; +} diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html index 651b9f4..b21a2c3 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html @@ -1 +1,161 @@ -

client-task-logs works!

+ \ No newline at end of file diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts index 3502328..397247c 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts @@ -1,7 +1,19 @@ -import { Component, Inject, OnInit } from '@angular/core'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; import { HttpClient, HttpParams } from '@angular/common/http'; import { ConfigService } from '@services/config.service'; +import { DatePipe } from '@angular/common'; +import { DeleteModalComponent } from 'src/app/shared/delete_modal/delete-modal/delete-modal.component'; +import { ToastrService } from 'ngx-toastr'; +import { TranslationService } from '@services/translation.service'; +import { Observable } from 'rxjs'; +import { FormControl } from '@angular/forms'; +import { OutputDialogComponent } from '../output-dialog/output-dialog.component'; +import { InputDialogComponent } from '../input-dialog/input-dialog.component'; +import { ProgressBarMode } from '@angular/material/progress-bar'; +import { JoyrideService } from 'ngx-joyride'; +import { map, startWith } from 'rxjs/operators'; +import { COMMAND_TYPES } from 'src/app/shared/constants/command-types'; @Component({ selector: 'app-client-task-logs', @@ -9,24 +21,161 @@ import { ConfigService } from '@services/config.service'; styleUrls: ['./client-task-logs.component.css'] }) export class ClientTaskLogsComponent implements OnInit { - + baseUrl: string; + mercureUrl: string; traces: any[] = []; - loading = false; + groupedTraces: any[] = []; + commands: any[] = []; + length: number = 0; itemsPerPage: number = 20; page: number = 0; - length: number = 0; - baseUrl: string; + loading: boolean = true; + pageSizeOptions: number[] = [10, 20, 30, 50]; + datePipe: DatePipe = new DatePipe('es-ES'); + mode: ProgressBarMode = 'buffer'; + progress = 0; + bufferValue = 0; - constructor( + filteredCommands2 = Object.keys(COMMAND_TYPES).map(key => ({ + name: key, + value: key, + label: COMMAND_TYPES[key] + })); + + columns = [ + { + columnDef: 'id', + header: 'ID', + cell: (trace: any) => `${trace.id}`, + }, + { + columnDef: 'command', + header: 'Comando', + cell: (trace: any) => trace.command + }, + { + columnDef: 'status', + header: 'Estado', + cell: (trace: any) => trace.status + }, + { + columnDef: 'executedAt', + header: '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), 'actions']; + + filters: { [key: string]: string } = {}; + filteredCommands!: Observable; + commandControl = new FormControl(); + + constructor(private http: HttpClient, @Inject(MAT_DIALOG_DATA) public data: { client: any }, - private http: HttpClient, + private joyrideService: JoyrideService, + private dialog: MatDialog, + private cdr: ChangeDetectorRef, private configService: ConfigService, + private toastService: ToastrService, + private translationService: TranslationService ) { this.baseUrl = this.configService.apiUrl; + this.mercureUrl = this.configService.mercureUrl; } ngOnInit(): void { this.loadTraces(); + this.loadCommands(); + this.filteredCommands = this.commandControl.valueChanges.pipe( + startWith(''), + map(value => (typeof value === 'string' ? value : value?.name)), + map(name => (name ? this._filterCommands(name) : this.commands.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 _filterCommands(name: string): any[] { + const filterValue = name.toLowerCase(); + return this.commands.filter(command => command.name.toLowerCase().includes(filterValue)); + } + + onOptionCommandSelected(selectedCommand: any): void { + this.filters['command'] = selectedCommand.name; + this.loadTraces(); + } + + onOptionStatusSelected(selectedStatus: any): void { + this.filters['status'] = selectedStatus; + this.loadTraces(); + } + + openInputModal(inputData: any): void { + this.dialog.open(InputDialogComponent, { + width: '70vw', + height: '60vh', + data: { input: inputData } + }); + } + + openOutputModal(outputData: any): void { + this.dialog.open(OutputDialogComponent, { + width: '500px', + data: { input: outputData } + }); + } + + 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 { @@ -55,4 +204,80 @@ export class ClientTaskLogsComponent implements OnInit { ); } + loadCommands() { + this.loading = true; + this.http.get(`${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; + } + ); + } + + 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(); + } + + translateCommand(command: string): string { + return this.translationService.getCommandTranslation(command); + } + + clearCommandFilter(event: Event, clientSearchCommandInput: any): void { + event.stopPropagation(); + delete this.filters['command']; + clientSearchCommandInput.value = null; + this.loadTraces() + } + + clearStatusFilter(event: Event, clientSearchStatusInput: any): void { + event.stopPropagation(); + delete this.filters['status']; + clientSearchStatusInput.value = null; + this.loadTraces() + } + + iniciarTour(): void { + this.joyrideService.startTour({ + steps: [ + 'titleStep', + 'resetFiltersStep', + 'clientSelectStep', + 'commandSelectStep', + 'tableStep', + 'paginationStep' + ], + showPrevButton: true, + themeColor: '#3f51b5' + }); + } } From 28336603ad759c888ffaeb36f813a8f784807526 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Thu, 8 May 2025 14:11:45 +0200 Subject: [PATCH 35/47] Refactor resetFilters method to accept input parameters for command and status filters --- .../client-task-logs.component.html | 2 +- .../client-task-logs.component.ts | 4 +- .../task-logs/task-logs.component.html | 39 +++++++++---------- .../task-logs/task-logs.component.ts | 4 +- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html index b21a2c3..0ede3ba 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html @@ -10,7 +10,7 @@
- diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts index 397247c..0d3e97c 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts @@ -218,8 +218,10 @@ export class ClientTaskLogsComponent implements OnInit { ); } - resetFilters() { + resetFilters(clientSearchCommandInput: any, clientSearchStatusInput: any) { this.loading = true; + clientSearchCommandInput.value = null; + clientSearchStatusInput.value = null; this.filters = {}; this.loadTraces(); } diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.html b/ogWebconsole/src/app/components/task-logs/task-logs.component.html index 37276fa..a396e5d 100644 --- a/ogWebconsole/src/app/components/task-logs/task-logs.component.html +++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.html @@ -9,8 +9,8 @@
-
@@ -34,7 +34,7 @@ Por favor, ingrese el nombre del cliente @@ -48,7 +48,7 @@ @@ -56,7 +56,8 @@ Estado - + Fallido Pendiente de ejecutar Ejecutando @@ -64,7 +65,7 @@ Cancelado @@ -83,7 +84,8 @@
- + {{trace.progress}}%
@@ -98,19 +100,16 @@ 'chip-cancelled': trace.status === 'cancelled' }"> {{ - trace.status === 'failed' ? 'Error' : - trace.status === 'in-progress' ? 'En ejecución' : - trace.status === 'success' ? 'Completado' : - trace.status === 'pending' ? 'Pendiente' : - trace.status === 'cancelled' ? 'Cancelado' : - trace.status + trace.status === 'failed' ? 'Error' : + trace.status === 'in-progress' ? 'En ejecución' : + trace.status === 'success' ? 'Completado' : + trace.status === 'pending' ? 'Pendiente' : + trace.status === 'cancelled' ? 'Cancelado' : + trace.status }} -
@@ -161,7 +160,7 @@ -
+
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts index d2f1353..8b6ff08 100644 --- a/ogWebconsole/src/app/components/task-logs/task-logs.component.ts +++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts @@ -262,8 +262,10 @@ export class TaskLogsComponent implements OnInit { } - resetFilters() { + resetFilters(clientSearchCommandInput: any, clientSearchStatusInput: any) { this.loading = true; + clientSearchCommandInput.value = null; + clientSearchStatusInput.value = null; this.filters = {}; this.loadTraces(); } From 5777be94173b60cd73066f3a9817d2e17fde51d3 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Thu, 8 May 2025 14:13:11 +0200 Subject: [PATCH 36/47] Enhance resetFilters method to include client input parameter and update template button accordingly --- .../src/app/components/task-logs/task-logs.component.html | 2 +- .../src/app/components/task-logs/task-logs.component.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.html b/ogWebconsole/src/app/components/task-logs/task-logs.component.html index a396e5d..06d54ee 100644 --- a/ogWebconsole/src/app/components/task-logs/task-logs.component.html +++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.html @@ -9,7 +9,7 @@
- diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts index 8b6ff08..79425f4 100644 --- a/ogWebconsole/src/app/components/task-logs/task-logs.component.ts +++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts @@ -262,10 +262,11 @@ export class TaskLogsComponent implements OnInit { } - resetFilters(clientSearchCommandInput: any, clientSearchStatusInput: any) { + resetFilters(clientSearchCommandInput: any, clientSearchStatusInput: any, clientSearchClientInput: any) { this.loading = true; clientSearchCommandInput.value = null; clientSearchStatusInput.value = null; + clientSearchClientInput.value = null; this.filters = {}; this.loadTraces(); } From 7ea5013cf430d69c5f3020e4bfa4c9b474d5aa00 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 8 May 2025 16:33:22 +0200 Subject: [PATCH 37/47] refs #1972. General styles changed. Show image details --- .../src/app/components/groups/groups.component.ts | 3 ++- .../client-details/client-details.component.html | 11 +++++++++-- .../shared/client-details/client-details.component.ts | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index dbd45c6..c75f2fd 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -613,7 +613,8 @@ export class GroupsComponent implements OnInit, OnDestroy { onShowClientDetail(event: MouseEvent, client: Client): void { event.stopPropagation(); this.dialog.open(ClientDetailsComponent, { - width: '1200px', + width: '70vw', + height: '90vh', data: { clientData: client }, }) } diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html index e7f3c9a..1a8e3ae 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html @@ -25,7 +25,7 @@

Discos/Particiones

- {{ clientData.firmwareType }} + {{ clientData.firmwareType }}
@@ -36,7 +36,7 @@ {{ column.header }} - + {{ column.cell(image) }} @@ -44,6 +44,13 @@ {{ (image.size / 1024).toFixed(2) }} GB + + +
+ {{image.operativeSystem?.name }} + {{ image.image?.name }} +
+
diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts index ddf03be..401a40f 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts @@ -72,9 +72,9 @@ export class ClientDetailsComponent { }, { columnDef: 'operativeSystem', - header: 'SO', + header: 'SO/Imagen', cell: (partition: any) => partition.operativeSystem?.name - }, + } ]; displayedColumns = [...this.columns.map(column => column.columnDef)]; isDiskUsageEmpty: boolean = true; From 757de78dc4718528ea587fda9a32970fcaa727fb Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 8 May 2025 17:09:56 +0200 Subject: [PATCH 38/47] refs #1969. Partition changes --- .../partition-assistant.component.css | 30 +++++- .../partition-assistant.component.html | 37 +++++-- .../partition-assistant.component.ts | 3 + .../app/shared/constants/filesystem-types.ts | 26 ++--- .../app/shared/constants/partition-types.ts | 102 +++++++++--------- 5 files changed, 123 insertions(+), 75 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css index ced0932..2e034f9 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css @@ -72,7 +72,7 @@ button.remove-btn { background-color: #dc3545; color: white; border-radius: 4px; - padding: 7px; + padding: 7px 10px; } button.remove-btn:hover { @@ -238,6 +238,34 @@ button.remove-btn:hover { align-items: center; } +.disk-space-info-container { + display: flex; + justify-self: start; + margin-top: 10px; + gap: 10px; +} + +.disk-space-info { + padding: 16px; + background-color: #f9f9f9; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + display: flex; + gap: 12px; + margin: 16px auto; +} + +.chip-free { + background-color: #d0f0c0; /* verde claro */ + color: #2e7d32; + font-weight: 500; +} + +.chip-full { + background-color: #ffcccb; /* rojo claro */ + color: #c62828; + font-weight: 500; +} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html index 3d59ac2..34290de 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html @@ -87,10 +87,30 @@
- + - Tabla de firmware: {{ selectedModelClient.firmwareType }} + Firmware: {{ selectedModelClient.firmwareType }} + + Tabla de particiones: {{ partitionCode }} + + +
+ + + +
+
+ Espacio usado: {{ selectedDisk.used }} MB +
+ +
+ Espacio libre: {{ selectedDisk.totalDiskSize - selectedDisk.used}} MB +
+ +
+ Espacio total: {{ selectedDisk.totalDiskSize }} MB +
@@ -104,7 +124,6 @@ Tamaño (MB) Tamaño (%) Formatear - Eliminar @@ -112,32 +131,32 @@ {{ partition.partitionNumber }} - - - - - + - + diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts index d5fbe0d..9632c68 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts @@ -51,6 +51,7 @@ export class PartitionAssistantComponent implements OnInit{ allSelected = true; selectedClients: any[] = []; selectedModelClient: any = null; + partitionCode: string = ''; constructor( private http: HttpClient, @@ -109,6 +110,7 @@ export class PartitionAssistantComponent implements OnInit{ (response) => { this.data = response; this.initializeDisks(); + this.partitionCode = this.data.partitions[0].partitionCode; }, (error) => { console.error('Error al cargar los datos del cliente:', error); @@ -415,6 +417,7 @@ export class PartitionAssistantComponent implements OnInit{ } updateDiskChart(disk: any) { + console.log('disk', disk); disk.chartData = this.generateChartData(disk.partitions); disk.used = this.calculateUsedSpace(disk.partitions); disk.percentage = (disk.used / disk.totalDiskSize) * 100; diff --git a/ogWebconsole/src/app/shared/constants/filesystem-types.ts b/ogWebconsole/src/app/shared/constants/filesystem-types.ts index dfbcc56..12cf87f 100644 --- a/ogWebconsole/src/app/shared/constants/filesystem-types.ts +++ b/ogWebconsole/src/app/shared/constants/filesystem-types.ts @@ -1,26 +1,26 @@ export const FILESYSTEM_TYPES = [ - { id: 1, name: 'EMPTY', description: 'EMPTY', active: 0 }, - { id: 2, name: 'CACHE', description: 'CACHE', active: 0 }, + { id: 22, name: '0', description: '', active: 0 }, { id: 3, name: 'BTRFS', description: 'BTRFS', active: 0 }, - { id: 4, name: 'EXT2', description: 'EXT2', active: 0 }, - { id: 5, name: 'EXT3', description: 'EXT3', active: 0 }, - { id: 6, name: 'EXT4', description: 'EXT4', active: 0 }, + { id: 2, name: 'CACHE', description: 'CACHE', active: 0 }, + { id: 1, name: 'EMPTY', description: 'EMPTY', active: 0 }, + { id: 20, name: 'F2FS', description: 'F2FS', active: 0 }, { id: 7, name: 'FAT12', description: 'FAT12', active: 0 }, { id: 8, name: 'FAT16', description: 'FAT16', active: 0 }, { id: 9, name: 'FAT32', description: 'FAT32', active: 0 }, + { id: 18, name: 'EXFAT', description: 'EXFAT', active: 0 }, + { id: 4, name: 'EXT2', description: 'EXT2', active: 0 }, + { id: 5, name: 'EXT3', description: 'EXT3', active: 0 }, + { id: 6, name: 'EXT4', description: 'EXT4', active: 0 }, { id: 10, name: 'HFS', description: 'HFS', active: 0 }, { id: 11, name: 'HFSPLUS', description: 'HFSPLUS', active: 0 }, + { id: 24, name: 'ISO9660', description: '', active: 0 }, { id: 12, name: 'JFS', description: 'JFS', active: 0 }, + { id: 23, name: 'LINUX-LVM', description: '', active: 0 }, + { id: 19, name: 'LINUX-SWAP', description: 'LINUX-SWAP', active: 0 }, + { id: 21, name: 'NILFS2', description: 'NILFS2', active: 0 }, { id: 13, name: 'NTFS', description: 'NTFS', active: 0 }, - { id: 14, name: 'REISERFS', description: 'REISERFS', active: 0 }, { id: 15, name: 'REISER4', description: 'REISER4', active: 0 }, + { id: 14, name: 'REISERFS', description: 'REISERFS', active: 0 }, { id: 16, name: 'UFS', description: 'UFS', active: 0 }, { id: 17, name: 'XFS', description: 'XFS', active: 0 }, - { id: 18, name: 'EXFAT', description: 'EXFAT', active: 0 }, - { id: 19, name: 'LINUX-SWAP', description: 'LINUX-SWAP', active: 0 }, - { id: 20, name: 'F2FS', description: 'F2FS', active: 0 }, - { id: 21, name: 'NILFS2', description: 'NILFS2', active: 0 }, - { id: 22, name: '0', description: '', active: 0 }, - { id: 23, name: 'LINUX-LVM', description: '', active: 0 }, - { id: 24, name: 'ISO9660', description: '', active: 0 }, ]; diff --git a/ogWebconsole/src/app/shared/constants/partition-types.ts b/ogWebconsole/src/app/shared/constants/partition-types.ts index 1b900ac..9f1f30d 100644 --- a/ogWebconsole/src/app/shared/constants/partition-types.ts +++ b/ogWebconsole/src/app/shared/constants/partition-types.ts @@ -1,72 +1,70 @@ export const PARTITION_TYPES = [ - { code: 0, name: 'EMPTY', active: false }, - { code: 1, name: 'FAT12', active: true }, - { code: 5, name: 'EXTENDED', active: false }, - { code: 6, name: 'FAT16', active: true }, - { code: 7, name: 'NTFS', active: true }, - { code: 11, name: 'FAT32', active: true }, - { code: 17, name: 'HFAT12', active: true }, - { code: 22, name: 'HFAT16', active: true }, - { code: 23, name: 'HNTFS', active: true }, - { code: 27, name: 'HFAT32', active: true }, - { code: 130, name: 'LINUX-SWAP', active: false }, - { code: 131, name: 'LINUX', active: true }, - { code: 142, name: 'LINUX-LVM', active: true }, - { code: 165, name: 'FREEBSD', active: true }, - { code: 166, name: 'OPENBSD', active: true }, - { code: 169, name: 'NETBSD', active: true }, - { code: 175, name: 'HFS', active: true }, - { code: 190, name: 'SOLARIS-BOOT', active: true }, - { code: 191, name: 'SOLARIS', active: true }, + { code: 61186, name: 'BIOS-BOOT', active: false }, { code: 202, name: 'CACHE', active: false }, - { code: 218, name: 'DATA', active: true }, - { code: 238, name: 'GPT', active: false }, - { code: 239, name: 'EFI', active: true }, - { code: 251, name: 'VMFS', active: true }, - { code: 253, name: 'LINUX-RAID', active: true }, - { code: 1792, name: 'WINDOWS', active: true }, - { code: 3073, name: 'WIN-RESERV', active: true }, - { code: 9984, name: 'WIN-RECOV', active: true }, + { code: 51712, name: 'CACHE', active: false }, { code: 32512, name: 'CHROMEOS-KRN', active: true }, { code: 32513, name: 'CHROMEOS', active: true }, { code: 32514, name: 'CHROMEOS-RESERV', active: true }, - { code: 33280, name: 'LINUX-SWAP', active: false }, - { code: 33536, name: 'LINUX', active: true }, - { code: 33537, name: 'LINUX-RESERV', active: true }, - { code: 33538, name: 'LINUX', active: true }, - { code: 36352, name: 'LINUX-LVM', active: true }, - { code: 42240, name: 'FREEBSD-DISK', active: false }, - { code: 42241, name: 'FREEBSD-BOOT', active: true }, - { code: 42242, name: 'FREEBSD-SWAP', active: false }, + { code: 218, name: 'DATA', active: true }, + { code: 239, name: 'EFI', active: true }, + { code: 61184, name: 'EFI', active: true }, + { code: 0, name: 'EMPTY', active: false }, + { code: 1, name: 'FAT12', active: true }, + { code: 6, name: 'FAT16', active: true }, + { code: 11, name: 'FAT32', active: true }, + { code: 165, name: 'FREEBSD', active: true }, { code: 42243, name: 'FREEBSD', active: true }, { code: 42244, name: 'FREEBSD', active: true }, - { code: 43265, name: 'NETBSD-SWAP', active: false }, + { code: 42241, name: 'FREEBSD-BOOT', active: true }, + { code: 42240, name: 'FREEBSD-DISK', active: false }, + { code: 42242, name: 'FREEBSD-SWAP', active: false }, + { code: 238, name: 'GPT', active: false }, + { code: 43776, name: 'HFS-BOOT', active: true }, + { code: 175, name: 'HFS', active: true }, + { code: 44800, name: 'HFS', active: true }, + { code: 44801, name: 'HFS-RAID', active: true }, + { code: 44802, name: 'HFS-RAID', active: true }, + { code: 17, name: 'HFAT12', active: true }, + { code: 22, name: 'HFAT16', active: true }, + { code: 27, name: 'HFAT32', active: true }, + { code: 39, name: 'HNTFS-WINRE', active: true }, + { code: 131, name: 'LINUX', active: true }, + { code: 33536, name: 'LINUX', active: true }, + { code: 33538, name: 'LINUX', active: true }, + { code: 142, name: 'LINUX-LVM', active: true }, + { code: 36352, name: 'LINUX-LVM', active: true }, + { code: 64768, name: 'LINUX-RAID', active: true }, + { code: 253, name: 'LINUX-RAID', active: true }, + { code: 33537, name: 'LINUX-RESERV', active: true }, + { code: 130, name: 'LINUX-SWAP', active: false }, + { code: 33280, name: 'LINUX-SWAP', active: false }, + { code: 65536, name: 'LVM-LV', active: true }, + { code: 61185, name: 'MBR', active: false }, + { code: 169, name: 'NETBSD', active: true }, { code: 43266, name: 'NETBSD', active: true }, { code: 43267, name: 'NETBSD', active: true }, { code: 43268, name: 'NETBSD', active: true }, { code: 43269, name: 'NETBSD', active: true }, { code: 43270, name: 'NETBSD-RAID', active: true }, - { code: 43776, name: 'HFS-BOOT', active: true }, - { code: 44800, name: 'HFS', active: true }, - { code: 44801, name: 'HFS-RAID', active: true }, - { code: 44802, name: 'HFS-RAID', active: true }, - { code: 48640, name: 'SOLARIS-BOOT', active: true }, + { code: 43265, name: 'NETBSD-SWAP', active: false }, + { code: 7, name: 'NTFS', active: true }, + { code: 166, name: 'OPENBSD', active: true }, + { code: 5, name: 'EXTENDED', active: false }, { code: 48896, name: 'SOLARIS', active: true }, - { code: 48897, name: 'SOLARIS', active: true }, - { code: 48898, name: 'SOLARIS-SWAP', active: false }, - { code: 48899, name: 'SOLARIS-DISK', active: true }, + { code: 191, name: 'SOLARIS', active: true }, { code: 48900, name: 'SOLARIS', active: true }, { code: 48901, name: 'SOLARIS', active: true }, - { code: 51712, name: 'CACHE', active: false }, - { code: 61184, name: 'EFI', active: true }, - { code: 61185, name: 'MBR', active: false }, - { code: 61186, name: 'BIOS-BOOT', active: false }, + { code: 48899, name: 'SOLARIS-DISK', active: true }, + { code: 48640, name: 'SOLARIS-BOOT', active: true }, + { code: 190, name: 'SOLARIS-BOOT', active: true }, + { code: 48898, name: 'SOLARIS-SWAP', active: false }, { code: 64256, name: 'VMFS', active: true }, - { code: 64257, name: 'VMFS-RESERV', active: true }, + { code: 251, name: 'VMFS', active: true }, { code: 64258, name: 'VMFS-KRN', active: true }, - { code: 64768, name: 'LINUX-RAID', active: true }, + { code: 64257, name: 'VMFS-RESERV', active: true }, + { code: 9984, name: 'WIN-RECOV', active: true }, + { code: 3073, name: 'WIN-RESERV', active: true }, + { code: 1792, name: 'WINDOWS', active: true }, { code: 65535, name: 'UNKNOWN', active: true }, - { code: 65536, name: 'LVM-LV', active: true }, { code: 65552, name: 'ZFS-VOL', active: true }, - { code: 39, name: 'HNTFS-WINRE', active: true }, ]; From d10a209a25c149e9c8e0a5d28b18a93216574e63 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 9 May 2025 13:07:19 +0200 Subject: [PATCH 39/47] Enhance unit tests for ClientTaskLogs and OutputDialog components by adding necessary imports, providers, and schemas --- .../client-task-logs.component.spec.ts | 42 ++++++++++++++++--- .../output-dialog.component.spec.ts | 16 +++++-- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.spec.ts b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.spec.ts index ee38857..1b0b661 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.spec.ts +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.spec.ts @@ -1,16 +1,48 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ClientTaskLogsComponent } from './client-task-logs.component'; +import { JoyrideService } from 'ngx-joyride'; +import { ToastrModule } from 'ngx-toastr'; +import { ConfigService } from '@services/config.service'; +import { MatIconModule } from '@angular/material/icon'; +import { TranslateModule } from '@ngx-translate/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { LoadingComponent } from 'src/app/shared/loading/loading.component'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; describe('ClientTaskLogsComponent', () => { let component: ClientTaskLogsComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockJoyrideService = jasmine.createSpyObj('JoyrideService', ['startTour']); + const mockConfigService = { + apiUrl: 'http://mock-api-url' + }; + await TestBed.configureTestingModule({ - declarations: [ClientTaskLogsComponent] - }) - .compileComponents(); + declarations: [ClientTaskLogsComponent, LoadingComponent], + imports: [HttpClientTestingModule, + ToastrModule.forRoot(), + MatIconModule, + TranslateModule.forRoot(), + MatFormFieldModule, + MatPaginatorModule, + MatSelectModule, + BrowserAnimationsModule + ], + providers: [ + { provide: MatDialogRef, useValue: { close: jasmine.createSpy('close') } }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: JoyrideService, useValue: mockJoyrideService }, + { provide: ConfigService, useValue: mockConfigService } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); fixture = TestBed.createComponent(ClientTaskLogsComponent); component = fixture.componentInstance; @@ -20,4 +52,4 @@ describe('ClientTaskLogsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); -}); +}); \ No newline at end of file diff --git a/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.spec.ts b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.spec.ts index d690f31..12643ba 100644 --- a/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.spec.ts +++ b/ogWebconsole/src/app/components/task-logs/output-dialog/output-dialog.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { OutputDialogComponent } from './output-dialog.component'; +import { TranslateModule } from '@ngx-translate/core'; describe('OutputDialogComponent', () => { let component: OutputDialogComponent; @@ -8,9 +9,16 @@ describe('OutputDialogComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [OutputDialogComponent] + declarations: [OutputDialogComponent], + imports: [ + TranslateModule.forRoot() + ], + providers: [ + { provide: MatDialogRef, useValue: { close: jasmine.createSpy('close') } }, + { provide: MAT_DIALOG_DATA, useValue: { input: {} } } + ] }) - .compileComponents(); + .compileComponents(); fixture = TestBed.createComponent(OutputDialogComponent); component = fixture.componentInstance; @@ -20,4 +28,4 @@ describe('OutputDialogComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); -}); +}); \ No newline at end of file From 600d5f9b374d8837afd0f74ce824bb9d120293ac Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 9 May 2025 13:41:00 +0200 Subject: [PATCH 40/47] refs #1963 Enhance loadTraces method to include command and status filters in HTTP parameters --- .../client-task-logs/client-task-logs.component.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts index 0d3e97c..fbab88c 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts @@ -184,11 +184,19 @@ export class ClientTaskLogsComponent implements OnInit { this.loading = true; - const params = new HttpParams() + let params = new HttpParams() .set('client.id', clientId) .set('page', (this.page + 1).toString()) .set('itemsPerPage', this.itemsPerPage.toString()); + if (this.filters['command']) { + params = params.set('command.name', this.filters['command']); + } + + if (this.filters['status']) { + params = params.set('status', this.filters['status']); + } + const url = `${this.baseUrl}/traces`; this.http.get(url, { params }).subscribe( @@ -204,6 +212,7 @@ export class ClientTaskLogsComponent implements OnInit { ); } + loadCommands() { this.loading = true; this.http.get(`${this.baseUrl}/commands?&page=1&itemsPerPage=10000`).subscribe( From 53d1191ddf0ab3ed2595d837c1e42a01c61c0c5f Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 9 May 2025 13:52:28 +0200 Subject: [PATCH 41/47] Erefs #1963 nhance ClientTaskLogs component to include date range filtering in trace loading and reset functionality --- .../client-task-logs.component.html | 14 ++++++++++++-- .../client-task-logs/client-task-logs.component.ts | 12 +++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html index 0ede3ba..239482c 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html @@ -10,8 +10,8 @@
-
@@ -47,6 +47,16 @@ close + + + {{ 'Filtrar por fecha' | translate }} + + + + + + +
diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts index fbab88c..3b0429f 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts @@ -35,6 +35,7 @@ export class ClientTaskLogsComponent implements OnInit { mode: ProgressBarMode = 'buffer'; progress = 0; bufferValue = 0; + dateRange = new FormControl(); filteredCommands2 = Object.keys(COMMAND_TYPES).map(key => ({ name: key, @@ -197,6 +198,15 @@ export class ClientTaskLogsComponent implements OnInit { params = params.set('status', this.filters['status']); } + const range = this.dateRange?.value; + if (range?.start && range?.end) { + const fromDate = this.datePipe.transform(range.start, 'yyyy-MM-dd'); + const toDate = this.datePipe.transform(range.end, 'yyyy-MM-dd'); + + params = params.set('executedAt[after]', fromDate!); + params = params.set('executedAt[before]', toDate!); + } + const url = `${this.baseUrl}/traces`; this.http.get(url, { params }).subscribe( @@ -212,7 +222,6 @@ export class ClientTaskLogsComponent implements OnInit { ); } - loadCommands() { this.loading = true; this.http.get(`${this.baseUrl}/commands?&page=1&itemsPerPage=10000`).subscribe( @@ -231,6 +240,7 @@ export class ClientTaskLogsComponent implements OnInit { this.loading = true; clientSearchCommandInput.value = null; clientSearchStatusInput.value = null; + this.dateRange.reset(); this.filters = {}; this.loadTraces(); } From b65d71544363c9b4054490c44e7ecff70d357766 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 9 May 2025 14:27:42 +0200 Subject: [PATCH 42/47] Refactor command filter to use command ID instead of name in ClientTaskLogs component --- .../client-task-logs/client-task-logs.component.html | 10 ---------- .../client-task-logs/client-task-logs.component.ts | 4 ++-- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html index 239482c..2b89d16 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html @@ -47,16 +47,6 @@ close - - - {{ 'Filtrar por fecha' | translate }} - - - - - - -
diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts index 3b0429f..2db9e1b 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts @@ -135,7 +135,7 @@ export class ClientTaskLogsComponent implements OnInit { } onOptionCommandSelected(selectedCommand: any): void { - this.filters['command'] = selectedCommand.name; + this.filters['command'] = selectedCommand.id; this.loadTraces(); } @@ -191,7 +191,7 @@ export class ClientTaskLogsComponent implements OnInit { .set('itemsPerPage', this.itemsPerPage.toString()); if (this.filters['command']) { - params = params.set('command.name', this.filters['command']); + params = params.set('command.id', this.filters['command']); } if (this.filters['status']) { From 114483410bbcbc7429f1f78b983dad046c397f3d Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 12 May 2025 16:26:35 +0200 Subject: [PATCH 43/47] refs #1827. UX changes. New calendar styles --- .../create-calendar-rule.component.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.ts b/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.ts index c9839cb..25440b8 100644 --- a/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.ts +++ b/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.ts @@ -74,6 +74,11 @@ export class CreateCalendarRuleComponent { .filter(index => index !== -1); } + convertDateToLocalISO(date: Date): string { + const adjustedDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + return adjustedDate.toISOString(); + } + submitRule(): void { this.getSelectedDaysIndices() const selectedDaysArray = Object.keys(this.busyWeekDays).map((day, index) => this.busyWeekDays[index]); @@ -83,8 +88,8 @@ export class CreateCalendarRuleComponent { busyWeekDays: this.selectedDaysIndices, busyFromHour: this.busyFromHour, busyToHour: this.busyToHour, - availableFromDate: this.availableFromDate, - availableToDate: this.availableToDate, + availableFromDate: this.availableFromDate ? this.convertDateToLocalISO(this.availableFromDate) : null, + availableToDate: this.availableToDate ? this.convertDateToLocalISO(this.availableToDate) : null, isRemoteAvailable: this.isRemoteAvailable, availableReason: this.availableReason }; From e3adf08bd955a53bfc677674aeb7d50644ea2766 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 12 May 2025 16:28:48 +0200 Subject: [PATCH 44/47] refs #1984. Git integration UX changes --- .../create-image/create-image.component.html | 22 +++- .../create-image/create-image.component.ts | 22 +++- .../deploy-image/deploy-image.component.html | 12 +- .../deploy-image/deploy-image.component.ts | 14 ++- .../create-image/create-image.component.html | 2 +- .../repositories/repositories.component.css | 19 ---- .../repositories/repositories.component.html | 43 +++++--- .../repositories/repositories.component.ts | 20 ++-- .../show-git-images.component.html | 20 +--- .../show-git-images.component.ts | 103 +++--------------- .../show-monolitic-images.component.html | 11 +- .../show-monolitic-images.component.ts | 13 +-- 12 files changed, 131 insertions(+), 170 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html index 6c3be8f..99b15e4 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html @@ -16,9 +16,9 @@
Tipo de imagen - - Monolítica - Git + + Monolítica + Git
@@ -31,7 +31,7 @@ Seleccione imagen - + {{ image?.name }} - \ No newline at end of file + diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.css b/ogWebconsole/src/app/components/repositories/repositories.component.css index 75b57f7..62feff0 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.css +++ b/ogWebconsole/src/app/components/repositories/repositories.component.css @@ -79,25 +79,6 @@ table { margin-bottom: 30px; } -.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; - -} - .header-container-title { flex-grow: 1; text-align: left; diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.html b/ogWebconsole/src/app/components/repositories/repositories.component.html index 3ddf7ec..ab330e5 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.html +++ b/ogWebconsole/src/app/components/repositories/repositories.component.html @@ -1,31 +1,33 @@
-
-

+

{{ 'repositoryTitle' | translate }}

- +
-
+
- Buscar nombre de repositorio + {{ 'search' | translate }} {{ 'repository' | translate }} search Pulsar 'enter' para buscar - Buscar IP de repositorio + {{ 'search' | translate }} IP de {{ 'repository' | translate }} search @@ -33,7 +35,7 @@
- +
- - +
{{ column.header }} @@ -42,15 +44,30 @@ - - + + Acciones + {{ 'actions' | translate }}
-
+
diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.ts b/ogWebconsole/src/app/components/repositories/repositories.component.ts index 4a398de..1c2dd73 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.ts +++ b/ogWebconsole/src/app/components/repositories/repositories.component.ts @@ -23,7 +23,7 @@ export class RepositoriesComponent implements OnInit { private apiUrl: string; dataSource = new MatTableDataSource(); length: number = 0; - itemsPerPage: number = 10; + itemsPerPage: number = 20; page: number = 0; loading: boolean = false; filters: { [key: string]: string } = {}; @@ -37,7 +37,7 @@ export class RepositoriesComponent implements OnInit { { columnDef: 'name', header: 'Nombre de repositorio', - cell: (repository: any) => `${repository.name}` + cell: (repository: any) => repository.name }, { columnDef: 'user', @@ -47,12 +47,12 @@ export class RepositoriesComponent implements OnInit { { columnDef: 'ip', header: 'Ip', - cell: (repository: any) => `${repository.ip}` + cell: (repository: any) => repository.ip }, { columnDef: 'images', - header: 'Imágenes', - cell: (repository: any) => `${repository.images}` + header: 'Gestionar imágenes', + cell: (repository: any) => repository.images }, { columnDef: 'createdAt', @@ -60,7 +60,7 @@ export class RepositoriesComponent implements OnInit { cell: (repository: any) => `${this.datePipe.transform(repository.createdAt, 'dd/MM/yyyy hh:mm:ss')}` } ]; - isGitModuleInstalled: boolean = false; + isGitModuleInstalled: boolean = true; displayedColumns: string[] = ['id', 'name', 'ip', 'user', 'images', 'createdAt', 'actions']; constructor( @@ -166,11 +166,17 @@ export class RepositoriesComponent implements OnInit { this.search(); } - iniciarTour(): void { + initTour(): void { this.joyrideService.startTour({ steps: [ 'repositoryTitleStep', 'addStep', + 'searchStep', + 'tableDateStep', + 'monolithicImageStep', + 'gitImageStep', + 'actionsStep', + 'paginationStep' ], showPrevButton: true, themeColor: '#3f51b5' diff --git a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.html b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.html index 5b0d65d..9812d06 100644 --- a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.html +++ b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.html @@ -14,9 +14,6 @@ -
@@ -48,10 +45,10 @@ {{ column.header }} - - - {{ image.image[column.columnDef] ? 'check_circle' : 'cancel' }} - + + + {{ image.isGlobal ? 'Sí' : 'No' }} + Acciones - @@ -86,7 +81,6 @@ menu - - - @@ -117,4 +107,4 @@ - \ No newline at end of file + diff --git a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.ts b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.ts index 2608b8a..94fd85a 100644 --- a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.ts +++ b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.ts @@ -22,7 +22,7 @@ import {EditImageComponent} from "../edit-image/edit-image.component"; templateUrl: './show-git-images.component.html', styleUrl: './show-git-images.component.css' }) -export class ShowGitImagesComponent { +export class ShowGitImagesComponent implements OnInit{ baseUrl: string; private apiUrl: string; dataSource = new MatTableDataSource(); @@ -43,27 +43,27 @@ baseUrl: string; { columnDef: 'name', header: 'Nombre de imagen', - cell: (image: any) => `${image.image.name}` + cell: (image: any) => image.name }, { - columnDef: 'version', - header: 'Version', - cell: (image: any) => `${image.version ? image.version : '0'}` + columnDef: 'tag', + header: 'Tag', + cell: (image: any) => image.tag }, { columnDef: 'isGlobal', header: 'Imagen global', - cell: (image: any) => `${image.image?.isGlobal}` + cell: (image: any) => image.image?.isGlobal }, { columnDef: 'status', header: 'Estado', - cell: (image: any) => `${image.status}` + cell: (image: any) => image.status }, { columnDef: 'description', header: 'Descripción', - cell: (image: any) => `${image.description ? image.description : 'Sin descripción'}` + cell: (image: any) => image.description }, { columnDef: 'createdAt', @@ -84,11 +84,10 @@ baseUrl: string; @Inject(MAT_DIALOG_DATA) public data: any ) { this.baseUrl = this.configService.apiUrl; - this.apiUrl = `${this.baseUrl}/image-image-repositories`; + this.apiUrl = `${this.baseUrl}/git-image-repositories`; } ngOnInit(): void { - console.error() if (this.data) { this.loadData(); } @@ -140,30 +139,7 @@ baseUrl: string; return this.http.get(`${this.apiUrl}/server/${image.uuid}/get`, {}); } - showImageInfo(event: MouseEvent, image:any) { - event.stopPropagation(); - this.loading = true; - this.loadImageAlert(image).subscribe( - response => { - this.alertMessage = response; - - this.dialog.open(ServerInfoDialogComponent, { - width: '800px', - data: { - message: this.alertMessage - } - }); - this.loading = false; - }, - error => { - this.toastService.error(error.error['hydra:description']); - this.loading = false; - } - ); - } - importImage(): void { - console.log(this.data) this.dialog.open(ImportImageComponent, { width: '600px', data: { @@ -177,33 +153,8 @@ baseUrl: string; }); } - convertImage(): void { - this.dialog.open(ConvertImageComponent, { - width: '600px', - data: { - repositoryUuid: this.data.repositoryUuid, - name: this.data.repositoryName - } - }).afterClosed().subscribe((result) => { - if (result) { - this.loadData(); - } - }); - } - toggleAction(image: any, action:string): void { switch (action) { - case 'get-aux': - this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/create-aux-files`, {}).subscribe({ - next: (message) => { - this.toastService.success('Petición de creación de archivos auxiliares enviada'); - this.loadData() - }, - error: (error) => { - this.toastService.error(error.error['hydra:description']); - } - }); - break; case 'delete-trash': if (!image.imageFullsum) { const dialogRef = this.dialog.open(DeleteModalComponent, { @@ -278,17 +229,6 @@ baseUrl: string; } }); break; - case 'status': - this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/status`, {}).subscribe({ - next: (response: any) => { - this.toastService.info(response?.output); - this.loadData() - }, - error: (error) => { - this.toastService.error(error.error['hydra:description']); - } - }); - break; case 'transfer': this.http.get(`${this.baseUrl}${image.image['@id']}`).subscribe({ next: (response) => { @@ -335,23 +275,6 @@ baseUrl: string; } }); break; - case 'convert-image-to-virtual': - this.http.get(`${this.baseUrl}${image.image['@id']}`).subscribe({ - next: (response) => { - this.dialog.open(ConvertImageToVirtualComponent, { - width: '600px', - data: { - image: response, - imageImageRepository: image - } - }); - this.router.navigate(['/commands-logs']); - }, - error: (error) => { - this.toastService.error(error.error['hydra:description']); - } - }); - break; default: console.error('Acción no soportada:', action); break; @@ -376,11 +299,11 @@ baseUrl: string; } loadAlert(): Observable { - return this.http.post(`${this.baseUrl}/image-repositories/server/${this.data.repositoryUuid}/get-collection`, {}); + return this.http.post(`${this.baseUrl}/image-repositories/server/git/${this.data.repositoryUuid}/get-collection`, {}); } syncRepository() { - this.http.post(`${this.baseUrl}/image-repositories/server/${this.data.repositoryUuid}/sync`, {}) + this.http.post(`${this.baseUrl}/image-repositories/server/git/${this.data.repositoryUuid}/sync`, {}) .subscribe(response => { this.toastService.success('Sincronización completada'); this.loadData() @@ -393,12 +316,12 @@ baseUrl: string; openImageInfoDialog() { this.loadAlert().subscribe( response => { - this.alertMessage = response.output; + this.alertMessage = response.repositories; this.dialog.open(ServerInfoDialogComponent, { width: '800px', data: { - message: this.alertMessage + repositories: this.alertMessage } }); }, diff --git a/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.html b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.html index 592c09d..59f426c 100644 --- a/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.html +++ b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.html @@ -6,7 +6,7 @@ -

Gestionar imágenes monolíticas en {{data.repositoryName}}

+

{{ 'monolithicImage' | translate }} {{data.repositoryName}}

@@ -48,11 +48,12 @@ {{ column.header }} - - - {{ image.image[column.columnDef] ? 'check_circle' : 'cancel' }} - + + + {{ image.isGlobal ? 'Sí' : 'No' }} + + - - +
@@ -81,6 +81,10 @@ menu + + + @@ -84,11 +88,7 @@ - - + (click)="toggleAction(image, 'show-branches')">Ver ramas