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 2bd3f99..0203339 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'; @@ -143,6 +142,8 @@ 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'; +import { PartitionTypeOrganizatorComponent } from './components/groups/shared/partition-type-organizator/partition-type-organizator.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); @@ -204,7 +205,6 @@ registerLocaleData(localeEs, 'es-ES'); TaskLogsComponent, ServerInfoDialogComponent, StatusComponent, - ClientMainViewComponent, ImagesComponent, CreateImageComponent, PartitionAssistantComponent, @@ -243,7 +243,9 @@ registerLocaleData(localeEs, 'es-ES'); SaveScriptComponent, EditImageComponent, ShowGitImagesComponent, - RenameImageComponent + RenameImageComponent, + ClientDetailsComponent, + PartitionTypeOrganizatorComponent ], 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 90af7b0..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 @@ -10,11 +10,13 @@ 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'; @Input() icon: string = 'terminal'; @Input() disabled: boolean = false; + @Input() runScriptContext: string = ''; baseUrl: string; loading: boolean = true; @@ -45,6 +47,48 @@ 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 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; + }); } onCommandSelect(action: any): void { @@ -137,7 +181,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 +205,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,18 +222,19 @@ 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 })); 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/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/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 116dbc3..5415b20 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -198,11 +198,16 @@ delete {{ 'delete' | translate }} + + [buttonText]="'Ejecutar comandos'" [icon]="'terminal'" + [disabled]="!((selectedNode?.clients ?? []).length > 0)" [runScriptContext]="selectedNode?.name || ''" + [runScriptContext]="getRunScriptContext(selectedNode?.clients || [])"> -
@@ -218,7 +223,8 @@
+ [buttonText]="'Ejecutar comandos'" [disabled]="selection.selected.length === 0" + [runScriptContext]="getRunScriptContext(selection.selected)"> @@ -260,8 +266,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 0235ab9..677b65e 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,8 @@ 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'; +import { PartitionTypeOrganizatorComponent } from './shared/partition-type-organizator/partition-type-organizator.component'; enum NodeType { OrganizationalUnit = 'organizational-unit', @@ -85,6 +87,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']; @@ -181,7 +184,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, @@ -608,7 +611,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: '1200px', + data: { clientData: client }, + }) } @@ -823,4 +829,36 @@ 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 ''; + } + + 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/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..902cb8f --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css @@ -0,0 +1,110 @@ +.modal-content { + max-height: 80vh; + overflow-y: auto; + background-color: #fff; + display: flex; + flex-direction: column; + padding: 1em 4em 2em 4em; +} + +.title { + margin-bottom: 1.5em; +} + +.client-info-card { + background-color: #f1f1f1; + padding: 24px; + border-radius: 12px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); + margin-bottom: 60px; +} + +.info-columns { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px; +} + +.info-column { + display: flex; + flex-direction: column; + gap: 16px; +} + +.info-pair { + display: flex; + flex-direction: column; +} + +.label { + font-size: 0.9rem; + font-weight: 550; + color: #3f51b5; +} + +.value { + font-size: 1rem; + color: #222; + word-break: break-word; +} + +.disk-container { + display: flex; + flex-direction: column; + gap: 32px; +} + +.disk-layout { + display: flex; + flex-direction: row; + gap: 32px; + flex-wrap: wrap; +} + +.table-container { + flex: 1 1 500px; + overflow-x: auto; +} + +.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; +} + +table { + border: 2px solid #f3f3f3; + border-radius: 0px !important; + box-shadow: none; +} + +.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 260px; + min-width: 240px; + padding: 16px; + background: #f9f9f9; + border-radius: 12px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); +} + +.chart { + width: 100%; + 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 new file mode 100644 index 0000000..9e8edc0 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html @@ -0,0 +1,66 @@ + + + +
+
+

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

+
+ +
+
+
+
+
{{ data?.property }}
+
{{ data?.value || '--' }}
+
+
+
+
+
{{ data?.property }}
+
{{ data?.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/components/client-main-view/client-main-view.component.ts b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts similarity index 90% rename from ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts rename to ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts index bf77392..919e84e 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts @@ -1,4 +1,5 @@ -import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +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"; @@ -14,11 +15,11 @@ interface ClientInfo { } @Component({ - selector: 'app-client-main-view', - templateUrl: './client-main-view.component.html', - styleUrl: './client-main-view.component.css' + selector: 'app-client-details', + templateUrl: './client-details.component.html', + styleUrl: './client-details.component.css' }) -export class ClientMainViewComponent implements OnInit { +export class ClientDetailsComponent { baseUrl: string; @ViewChild('assistantContainer') assistantContainer!: ElementRef; clientUuid: string; @@ -34,7 +35,7 @@ export class ClientMainViewComponent implements OnInit { partitions: any[] = []; commands: any[] = []; chartDisk: any[] = []; - view: [number, number] = [300, 200]; + view: [number, number] = [260, 160]; showLegend: boolean = true; arrayCommands: any[] = [ @@ -93,7 +94,8 @@ export class ClientMainViewComponent implements OnInit { private dialog: MatDialog, private configService: ConfigService, private router: Router, - private toastService: ToastrService + private toastService: ToastrService, + @Inject(MAT_DIALOG_DATA) public data: { clientData: any } ) { this.baseUrl = this.configService.apiUrl; const url = window.location.href; @@ -102,14 +104,18 @@ export class ClientMainViewComponent implements OnInit { } ngOnInit() { - this.clientData = history.state.clientData['@id']; - this.loadClient(this.clientData) - this.loadCommands() - this.calculateDiskUsage(); - this.loading = false; + if (this.data && this.data.clientData) { + this.clientData = this.data.clientData; + this.updateGeneralData(); + this.updateNetworkData(); + this.calculateDiskUsage(); + this.loadPartitions(); + this.loading = false; + } else { + console.error('No se recibieron datos del cliente.'); + } } - - + loadClient = (uuid: string) => { this.http.get(`${this.baseUrl}${uuid}`).subscribe({ next: data => { @@ -125,10 +131,6 @@ export class ClientMainViewComponent implements OnInit { }); } - navigateToGroups() { - this.router.navigate(['/groups']); - } - updateGeneralData() { this.generalData = [ { property: 'Nombre', value: this.clientData?.name || '' }, @@ -205,7 +207,12 @@ export class ClientMainViewComponent implements OnInit { } loadPartitions(): void { - this.http.get(`${this.baseUrl}/partitions?client.id=${this.clientData?.id}&order[diskNumber, partitionNumber]=ASC`).subscribe({ + 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; 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..6f25123 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.html @@ -0,0 +1,42 @@ + +
+

{{ client.name }}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Disco{{ element.diskNumber }}Partición{{ element.partitionNumber }}Tipo{{ element.partitionCode }}Tamaño{{ element.size }}Fyle System{{ element.filesystem }}Memoria{{ 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..2f47bd9 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/partition-type-organizator/partition-type-organizator.component.ts @@ -0,0 +1,32 @@ +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 { + console.log('Data recibida en modal:', this.data); + + 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 6bc649a..95a731c 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", @@ -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 fb6d793..28a8640 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", @@ -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" }