From 558a3205ab06d98224adfba9e44f7ad89be64f22 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Mon, 21 Apr 2025 14:56:09 +0200 Subject: [PATCH 01/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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 { })) })); } + }
DiskDisco {{ element.diskNumber }} Partition #Partición {{ element.partitionNumber }} CodeTipo {{ element.partitionCode }} SizeTamaño {{ element.size }} FSFyle System {{ element.filesystem }} MemoryMemoria {{ element.memoryUsage }}