diff --git a/ogWebconsole/package-lock.json b/ogWebconsole/package-lock.json index befd096..461ff5a 100644 --- a/ogWebconsole/package-lock.json +++ b/ogWebconsole/package-lock.json @@ -24,6 +24,7 @@ "jwt-decode": "^4.0.0", "ngx-joyride": "^2.5.0", "ngx-toastr": "^19.0.0", + "papaparse": "^5.4.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "^0.14.6" @@ -35,6 +36,7 @@ "@angular/localize": "^18.1.0", "@ngx-env/builder": "^18.0.1", "@types/jasmine": "~5.1.0", + "@types/papaparse": "^5.3.15", "jasmine-core": "~5.1.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -5902,6 +5904,15 @@ "@types/node": "*" } }, + "node_modules/@types/papaparse": { + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.15.tgz", + "integrity": "sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", @@ -11955,6 +11966,11 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", diff --git a/ogWebconsole/package.json b/ogWebconsole/package.json index e3edac1..a2a5595 100644 --- a/ogWebconsole/package.json +++ b/ogWebconsole/package.json @@ -26,6 +26,7 @@ "jwt-decode": "^4.0.0", "ngx-joyride": "^2.5.0", "ngx-toastr": "^19.0.0", + "papaparse": "^5.4.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "^0.14.6" @@ -37,6 +38,7 @@ "@angular/localize": "^18.1.0", "@ngx-env/builder": "^18.0.1", "@types/jasmine": "~5.1.0", + "@types/papaparse": "^5.3.15", "jasmine-core": "~5.1.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 62de434..1245eeb 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -113,8 +113,6 @@ import { CreateSoftwareProfileComponent } from './components/software-profile/cr import { OperativeSystemComponent } from './components/operative-system/operative-system.component'; import { CreateOperativeSystemComponent } from './components/operative-system/create-operative-system/create-operative-system.component'; import { ShowTemplateContentComponent } from './components/ogboot/pxe/show-template-content/show-template-content.component'; -import { AddClientsToPxeComponent } from './components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component'; -import { ClientsComponent } from './components/ogboot/pxe/clients/clients.component'; import { RepositoriesComponent } from './components/repositories/repositories.component'; import { CreateRepositoryComponent } from './components/repositories/create-repository/create-repository.component'; import { ExecuteCommandComponent } from './components/commands/main-commands/execute-command/execute-command.component'; @@ -125,7 +123,7 @@ import { JoyrideModule } from 'ngx-joyride'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { EnvVarsComponent } from './components/admin/env-vars/env-vars.component'; - +import { MatSortModule } from '@angular/material/sort'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); } @@ -200,8 +198,6 @@ export function HttpLoaderFactory(http: HttpClient) { OperativeSystemComponent, CreateOperativeSystemComponent, ShowTemplateContentComponent, - AddClientsToPxeComponent, - ClientsComponent, RepositoriesComponent, CreateRepositoryComponent, ExecuteCommandComponent, @@ -238,6 +234,7 @@ export function HttpLoaderFactory(http: HttpClient) { MatDatepickerModule, MatNativeDateModule, MatSliderModule, + MatSortModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, diff --git a/ogWebconsole/src/app/components/admin/users/users/users.component.html b/ogWebconsole/src/app/components/admin/users/users/users.component.html index 66c6869..f26e1ff 100644 --- a/ogWebconsole/src/app/components/admin/users/users/users.component.html +++ b/ogWebconsole/src/app/components/admin/users/users/users.component.html @@ -1,5 +1,5 @@
-

{{ 'adminImagesTitle' | translate }}

+

{{ 'adminUsersTitle' | translate }}

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

Discos/Particiones

+
+
+
{{ 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

-
-
+
+ +
+ + + + - + + +
{{ 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 index 83a7d83..95a3563 100644 --- 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 @@ -6,6 +6,7 @@ import {PartitionAssistantComponent} from "./partition-assistant/partition-assis import {MatDialog} from "@angular/material/dialog"; import {Router} from "@angular/router"; import {EditClientComponent} from "../../shared/clients/edit-client/edit-client.component"; +import {ToastrService} from "ngx-toastr"; interface ClientInfo { property: string; @@ -33,7 +34,7 @@ export class ClientMainViewComponent implements OnInit { partitions: any[] = []; commands: any[] = []; chartDisk: any[] = []; - view: [number, number] = [600, 300]; + view: [number, number] = [300, 200]; showLegend: boolean = true; arrayCommands: any[] = [ @@ -90,7 +91,8 @@ export class ClientMainViewComponent implements OnInit { constructor( private http: HttpClient, private dialog: MatDialog, - private router: Router + private router: Router, + private toastService: ToastrService ) { const url = window.location.href; const segments = url.split('/'); @@ -98,38 +100,47 @@ export class ClientMainViewComponent implements OnInit { } ngOnInit() { - this.clientData = history.state.clientData; - this.loadPartitions() - this.updateGeneralData(); - this.updateNetworkData(); + 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); + } + }); + } updateGeneralData() { this.generalData = [ { property: 'Nombre', value: this.clientData?.name || '' }, - { property: 'Uuid', value: this.clientData?.uuid || '' }, { 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 || '' }, + { property: 'Netiface', value: this.clientData?.netiface || this.clientData?.organizationalUnit?.networkSettings?.netiface || '' }, { property: 'Perfil hardware', value: this.clientData?.hardwareProfile?.description || '' }, - { property: 'Menú', value: this.clientData?.menu?.description || '' }, ]; } updateNetworkData() { this.networkData = [ + { 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: 'Autoexec', value: '' }, { property: 'Repositorio', value: this.clientData?.repository?.name || '' }, - { property: 'Validación', value: this.clientData?.organizationalUnit?.networkSettings?.validation || '' }, - { property: 'Pxe', value: this.clientData?.template?.name || '' }, - { property: 'Creado por', value: this.clientData?.createdBy || '' } ]; } @@ -186,17 +197,8 @@ export class ClientMainViewComponent implements OnInit { dialogRef.afterClosed().subscribe(); } - getStrokeOffset(partitions: any[], index: number): number { - const totalSize = partitions.reduce((acc, part) => acc + (part.size / 1024), 0); - - const offset = partitions.slice(0, index).reduce((acc, part) => acc + (part.size / 1024), 0); - console.log(offset, totalSize) - - return totalSize > 0 ? (offset / totalSize) : 0; - } - loadPartitions(): void { - this.http.get(`${this.baseUrl}/partitions?client.id=${this.clientData?.id}`).subscribe({ + this.http.get(`${this.baseUrl}/partitions?client.id=${this.clientData?.id}&order[partitionNumber]=ASC`).subscribe({ next: data => { this.dataSource = data['hydra:member']; this.partitions = data['hydra:member']; @@ -231,6 +233,55 @@ export class ClientMainViewComponent implements OnInit { 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 { diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html index 5762385..eb9611c 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html @@ -1,5 +1,5 @@
-

Deploy imagen en {{ clientName }}

+

Desplegar imagen en {{ clientName }}

diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts index 58623ea..2e9e25b 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts @@ -3,7 +3,7 @@ import {MatTableDataSource} from "@angular/material/table"; import {SelectionModel} from "@angular/cdk/collections"; import {HttpClient} from "@angular/common/http"; import {ToastrService} from "ngx-toastr"; -import {ActivatedRoute} from "@angular/router"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'app-deploy-image', @@ -20,7 +20,7 @@ export class DeployImageComponent { images: any[] = []; clientName: string = ''; selectedImage: string | null = null; - selectedOption: string | null = null; + selectedOption: string | null = 'deploy-image'; selectedMethod: string | null = null; selectedPartition: any = null; mcastIp: string = ''; @@ -92,12 +92,13 @@ export class DeployImageComponent { constructor( private http: HttpClient, private toastService: ToastrService, - private route: ActivatedRoute + private route: ActivatedRoute, + private router: Router, ) {} ngOnInit() { this.clientId = this.route.snapshot.paramMap.get('id'); - + this.selectedOption = 'deploy-image'; this.loadPartitions(); this.loadImages(); } @@ -134,7 +135,7 @@ export class DeployImageComponent { } loadImages() { - const url = `${this.baseUrl}/images?page=1&itemsPerPage=1000`; + const url = `${this.baseUrl}/images?status=success&page=1&itemsPerPage=1000`; this.http.get(url).subscribe( (response: any) => { this.images = response['hydra:member']; @@ -146,6 +147,21 @@ export class DeployImageComponent { } save(): void { + if (!this.selectedImage) { + this.toastService.error('Debe seleccionar una imagen'); + return; + } + + if (!this.selectedMethod) { + this.toastService.error('Debe seleccionar un método'); + return; + } + + if (!this.selectedPartition) { + this.toastService.error('Debe seleccionar una partición'); + return; + } + const payload = { client: `/clients/${this.clientId}`, method: this.selectedMethod, @@ -162,6 +178,7 @@ export class DeployImageComponent { .subscribe({ next: (response) => { this.toastService.success('Imagen creada exitosamente'); + this.router.navigate(['/commmands-logs']) }, error: (error) => { console.error('Error:', error); diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css index 284808b..3746bb0 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css @@ -122,3 +122,43 @@ button.remove-btn:hover { border-radius: 4px; margin-top: 10px; } + +.partition-assistant .row { + display: flex; + flex-wrap: wrap; + margin-bottom: 20px; +} + +.form-container { + flex: 0 0 65%; + max-width: 65%; + padding-right: 20px; + box-sizing: border-box; +} + +.chart-container { + flex: 0 0 35%; + max-width: 35%; +} + +.partition-bar { + display: flex; + height: 40px; + margin: 20px 0; +} + +.partition-segment { + display: flex; + justify-content: center; + align-items: center; + text-align: center; + font-size: 10px; + color: white; + height: 100%; +} + +.chart-container ngx-charts-pie-chart { + display: block; + align-content: center; + justify-self: center; +} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html index af3e4ca..311b8f6 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html @@ -1,67 +1,100 @@ -

Asistente de particionado

+
+

Asistente de particionado

+
+ +
+
+
- + Tamaño: {{ (disk.totalDiskSize / 1024).toFixed(2) }} GB
-
-
- {{ partition.type }} ({{ (partition.size / 1024).toFixed(2) }} GB) +
+
+ {{ partition.partitionCode }} ({{ (partition.size / 1024).toFixed(2) }} GB) +
+
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ParticiónTipo particiónS. ficherosTamaño (MB)Tamaño (%)FormatearEliminar
{{ partition.partitionNumber }} + + + + + + + + + + + +
+
+ +
+ + +
+ - - - - - - - - - - - - - - - - - - - - - - - -
ParticiónTipo particiónTamaño (MB)Uso (%)FormatearEliminar
{{ partition.partitionNumber }} - - - - - - - - - -
-
{{ errorMessage }}
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts index ab0803c..752a376 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts @@ -2,18 +2,23 @@ import {Component, EventEmitter, Inject, Input, OnInit, Output} from '@angular/c import { HttpClient } from '@angular/common/http'; import { ToastrService } from 'ngx-toastr'; import {MAT_DIALOG_DATA} from "@angular/material/dialog"; -import {ActivatedRoute} from "@angular/router"; +import {ActivatedRoute, Router} from "@angular/router"; +import { PARTITION_TYPES } from '../../../../../shared/constants/partition-types'; +import { FILESYSTEM_TYPES } from '../../../../../shared/constants/filesystem-types'; +import {toUnredirectedSourceFile} from "@angular/compiler-cli/src/ngtsc/util/src/typescript"; interface Partition { uuid?: string; partitionNumber: number; size: number; - type: string; + partitionCode: string; + filesystem: string; sizeBytes: number; memoryUsage: number; format: boolean; color: string; percentage: number; + removed: boolean; } @Component({ @@ -24,19 +29,27 @@ interface Partition { export class PartitionAssistantComponent implements OnInit { baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; @Output() dataChange = new EventEmitter(); - + partitionTypes = PARTITION_TYPES; + filesystemTypes = FILESYSTEM_TYPES; errorMessage = ''; originalPartitions: any[] = []; clientId: string | null = null; + newPartitions: any[] = []; + updateRequests: any[] = []; data: any = {}; - disks: { diskNumber: number; totalDiskSize: number; partitions: Partition[] }[] = []; + disks: { diskNumber: number; totalDiskSize: number; partitions: Partition[]; chartData: any[]; used: number; percentage: number }[] = []; private apiUrl: string = this.baseUrl + '/partitions'; + view: [number, number] = [400, 300]; + showLegend = true; + showLabels = true; + constructor( private http: HttpClient, private toastService: ToastrService, - private route: ActivatedRoute + private route: ActivatedRoute, + private router: Router, ) {} ngOnInit() { @@ -77,33 +90,81 @@ export class PartitionAssistantComponent implements OnInit { partitionNumber: partition.partitionNumber, size: this.convertBytesToGB(partition.size), memoryUsage: partition.memoryUsage, - type: partition.type || partition.filesystem || 'NTFS', + partitionCode: partition.partitionCode, + filesystem: partition.filesystem, sizeBytes: partition.size, format: false, color: '#1f1b91', - percentage: 0 + percentage: 0, + removed: false }); } }); disksMap.forEach((disk, diskNumber) => { this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); + + const used = this.calculateUsedSpace(disk.partitions); + const percentage = (used / disk.totalDiskSize) * 100; + const chartData = this.generateChartData(disk.partitions); + this.disks.push({ diskNumber: diskNumber, totalDiskSize: disk.totalDiskSize, - partitions: disk.partitions + partitions: disk.partitions, + chartData: chartData, + used: used, + percentage: percentage }); }); + this.disks.forEach((disk) => { + this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); + }); } convertBytesToGB(bytes: number): number { return bytes } + activePartitions(diskNumber: number) { + const disk = this.disks.find((d) => d.diskNumber === diskNumber); + if (disk) { + return disk.partitions.filter((partition) => !partition.removed); + } + + return null; + } + updatePartitionPercentages(partitions: Partition[], totalDiskSize: number) { + let totalUsedPercentage = 0; + partitions.forEach((partition) => { - partition.percentage = (partition.size / totalDiskSize) * 100 + partition.percentage = Number(((partition.size / totalDiskSize) * 100).toFixed(2)) + totalUsedPercentage += partition.percentage; }); + + const unusedPercentage = 100 - totalUsedPercentage; + + const chartData = partitions + .filter((partition) => !partition.removed) + .map((partition) => ({ + name: `Partición ${partition.partitionNumber}`, + value: partition.percentage, + color: partition.color, + })); + + if (unusedPercentage > 0) { + chartData.push({ + name: 'Espacio sin usar', + value: unusedPercentage, + color: '#ffffff' + }); + } + + const disk = this.disks.find(d => d.partitions === partitions); + if (disk) { + disk.chartData = chartData; + } } addPartition(diskNumber: number) { @@ -112,21 +173,26 @@ export class PartitionAssistantComponent implements OnInit { if (disk) { const remainingGB = this.getRemainingGB(disk.partitions, disk.totalDiskSize); if (remainingGB > 0) { + const removedPartitions = disk.partitions.filter((p) => !p.removed); const maxPartitionNumber = - disk.partitions.length > 0 ? Math.max(...disk.partitions.map((p) => p.partitionNumber)) : 0; + removedPartitions.length > 0 ? Math.max(...removedPartitions.map((p) => p.partitionNumber)) : 0; const newPartitionNumber = maxPartitionNumber + 1; + disk.partitions.push({ partitionNumber: newPartitionNumber, size: 0, - type: 'NTFS', + partitionCode: 'LINUX', + filesystem: 'EXT4', memoryUsage: 0, sizeBytes: 0, format: false, color: '#' + Math.floor(Math.random() * 16777215).toString(16), - percentage: 0 + percentage: 0, + removed: false }); this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); + this.updateDiskChart(disk); } else { this.errorMessage = 'No hay suficiente espacio libre en el disco para crear una nueva partición.'; } @@ -138,7 +204,9 @@ export class PartitionAssistantComponent implements OnInit { if (disk) { const partition = disk.partitions[index]; const remainingGB = this.getRemainingGB(disk.partitions, disk.totalDiskSize) + partition.size; - + if (partition) { + partition.percentage = Number(((size / disk.totalDiskSize) * 100).toFixed(2)); + } if (size > remainingGB) { this.errorMessage = `El tamaño de la partición no puede superar el espacio libre (${remainingGB.toFixed(2)} GB).`; } else { @@ -148,41 +216,16 @@ export class PartitionAssistantComponent implements OnInit { partition.percentage = (size / disk.totalDiskSize) * 100; this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); + this.updateDiskChart(disk); } } } - updatePartitionPercentage(diskNumber: number, index: number, percentage: number) { - const disk = this.disks.find((d) => d.diskNumber === diskNumber); - if (disk) { - const partition = disk.partitions[index]; - - const newSizeMB = (percentage / 100) * disk.totalDiskSize; - - const totalPercentage = disk.partitions.reduce((sum, part) => sum + (part === partition ? percentage : part.percentage), 0); - - if (totalPercentage > 100) { - this.errorMessage = 'El tamaño total en porcentaje de las particiones no puede exceder el 100%'; - partition.percentage = 100 - (totalPercentage - percentage); - } else { - this.errorMessage = ''; - partition.percentage = percentage; - partition.size = newSizeMB; - } - } - } - - - getRemainingGB(partitions: Partition[], totalDiskSize: number): number { const totalUsedGB = partitions.reduce((acc, partition) => acc + partition.size, 0); return Math.max(0, totalDiskSize - totalUsedGB); } - isPartitionModified(original: any, current: Partition): boolean { - return this.convertBytesToGB(original.size) !== current.size || original.type !== current.type; - } - getModifiedOrNewPartitions() { const modifiedPartitions: any[] = []; @@ -191,22 +234,11 @@ export class PartitionAssistantComponent implements OnInit { const originalPartition = this.originalPartitions.find( (p) => p.diskNumber === disk.diskNumber && p.partitionNumber === partition.partitionNumber ); - - if (!originalPartition) { - modifiedPartitions.push({ - partition, - diskNumber: disk.diskNumber, - partitionNumber: partition.partitionNumber, - isNew: true - }); - } else if (this.isPartitionModified(originalPartition, partition)) { - modifiedPartitions.push({ - partition, - diskNumber: disk.diskNumber, - partitionNumber: partition.partitionNumber, - isNew: false - }); - } + modifiedPartitions.push({ + partition, + diskNumber: disk.diskNumber, + partitionNumber: partition.partitionNumber, + }); }); }); @@ -219,6 +251,7 @@ export class PartitionAssistantComponent implements OnInit { return totalPartitionSize > disk.totalDiskSize; }); + console.log(invalidDisks); if (invalidDisks.length > 0) { this.errorMessage = 'El tamaño total de las particiones en uno o más discos excede el tamaño total del disco.'; return; @@ -233,63 +266,78 @@ export class PartitionAssistantComponent implements OnInit { return; } - modifiedPartitions.forEach(({ partition, diskNumber, partitionNumber, isNew }) => { + modifiedPartitions.forEach(({ partition, diskNumber, partitionNumber }) => { const payload = { diskNumber: diskNumber, partitionNumber: partitionNumber, memoryUsage: partition.memoryUsage, size: partition.size, - filesystem: partition.type, - client: `/clients/${this.clientId}` + partitionCode: partition.partitionCode, + filesystem: partition.filesystem, + client: `/clients/${this.clientId}`, + uuid: partition.uuid, + removed: partition.removed || false, + format: partition.format || false, }; - if (isNew) { - this.http.post(this.apiUrl, payload).subscribe( - (response) => { - this.toastService.success('Partición creada exitosamente'); - window.location.reload(); - }, - (error) => { - console.error('Error al crear la partición:', error); - this.toastService.error('Error al crear la partición'); - } - ); - } else if (partition.uuid) { - const patchUrl = `${this.apiUrl}/${partition.uuid}`; - this.http.patch(patchUrl, payload).subscribe( - (response) => { - this.toastService.success('Partición actualizada exitosamente'); - window.location.reload(); - }, - (error) => { - console.error('Error al actualizar la partición:', error); - this.toastService.error('Error al actualizar la partición'); - } - ); - } + this.newPartitions.push(payload); }); + + if (this.newPartitions.length > 0) { + const bulkPayload = { partitions: this.newPartitions }; + + this.http.post(this.apiUrl, bulkPayload).subscribe( + (response) => { + this.toastService.success('Particiones creadas exitosamente'); + this.router.navigate(['/commands-logs']); + }, + (error) => { + console.error('Error al crear las particiones:', error); + this.toastService.error('Error al crear las particiones'); + } + ); + } } removePartition(diskNumber: number, partition: Partition) { const disk = this.disks.find((d) => d.diskNumber === diskNumber); - if (disk) { - const index = disk.partitions.indexOf(partition); - if (index !== -1) { - disk.partitions.splice(index, 1); - this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); - - if (partition.uuid) { - const deleteUrl = `${this.apiUrl}/${partition.uuid}`; - this.http.delete(deleteUrl).subscribe( - (response) => { - this.toastService.success('Partición eliminada exitosamente'); - window.location.reload(); - }, - (error) => {} - ); - } + const partitionToRemove = disk.partitions.find((p) => p === partition); + if (partitionToRemove) { + partitionToRemove.removed = true; } + this.updateDiskChart(disk); + this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); } } + + updatePartitionSizeFromPercentage(diskNumber: number, partitionIndex: number, percentage: number): void { + const disk = this.disks.find(d => d.diskNumber === diskNumber); + if (disk) { + const partition = disk.partitions[partitionIndex]; + if (partition) { + partition.size = (disk.totalDiskSize * percentage) / 100; + } + this.updateDiskChart(disk); + this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); + } + } + + calculateUsedSpace(partitions: Partition[]): number { + return partitions.reduce((acc, partition) => acc + partition.size, 0); + } + + generateChartData(partitions: Partition[]): any[] { + return partitions.map((partition) => ({ + name: `Partición ${partition.partitionNumber}`, + value: partition.percentage, + color: partition.color + })); + } + + updateDiskChart(disk: any) { + disk.chartData = this.generateChartData(disk.partitions); + disk.used = this.calculateUsedSpace(disk.partitions); + disk.percentage = (disk.used / disk.totalDiskSize) * 100; + } } diff --git a/ogWebconsole/src/app/components/groups/groups.component.css b/ogWebconsole/src/app/components/groups/groups.component.css index b2923f7..d0d9fe2 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.css +++ b/ogWebconsole/src/app/components/groups/groups.component.css @@ -1,321 +1,586 @@ -.groupLists-container { - display: flex; - flex-wrap: wrap; - height: auto; - margin-bottom: 30px; -} - -.search-container { - display: flex; - flex-grow: 1; - margin: 10px; -} - -.search-container mat-form-field { - width: 50%; -} - -.card { - flex-grow: 1; - margin: 10px; - - border: 2px solid rgba(102, 102, 102, 0.103) +.card-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + padding: 20px; } .header-container { display: flex; justify-content: space-between; align-items: center; - height: 100px; - padding: 10px; -} - -.unidad-card { - flex: 1 1 20%; - background-color: #fafafa; - height: 600px; - overflow-y: auto; - box-shadow: none !important; - -} - -.elements-card { - flex: 1 1 75%; - background-color: #fafafa; - height: 600px; - overflow-y: auto; - box-shadow: none !important; -} - -.element-content { - overflow-y: auto; -} - -.title { - margin-left: 10px; -} - -.details-card, .classroom-view { - flex: 1 1 25%; -} - -mat-card-title { - display: flex; - justify-content: space-between; - margin: 10px; -} - -.title-with-breadcrumb { - display: flex; - flex-direction: column; -} - -mat-card-subtitle { - font-size: 0.875rem; - color: rgba(0, 0, 0, 0.54); -} - -mat-card-subtitle a { - cursor: pointer; - text-decoration: underline; - color: #929292; -} - -mat-card-subtitle a:hover { - text-decoration: none; + padding: 20px; + background-color: #f5f5f5; + border-bottom: 1px solid #ddd; } .groups-button-row { display: flex; - gap: 10px; + gap: 15px; } -.item-content { +.button-container { display: flex; - width: 100%; + justify-content: center; + margin: 20px 0; } -.item-content mat-icon { - margin-right: 10px; +button[mat-raised-button] { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + font-size: 16px; } -.clickable-item:hover { +mat-card { + background-color: #ffffff; + border-radius: 10px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: transform 0.2s, box-shadow 0.2s; + overflow: hidden; + align-items: center; +} + +mat-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15); +} + +.unidad-card { cursor: pointer; -} - -.selected-item { - background-color: #e0e0e0; -} - -.actions { + padding: 16px; + font-size: 14px; display: flex; - margin-left: auto; - align-self: center; + flex-direction: column; + justify-content: space-between; +} + +.unidad-card.selected-item { + border: 2px solid #1976d2; +} + +mat-card-title { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: flex; + align-items: center; + font-size: 16px; + font-weight: bold; + color: #333; + margin: 0; +} + +mat-card-title mat-icon { + font-size: 20px; + margin-right: 8px; + color: #1976d2; +} + +mat-card-actions { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 16px; } .actions mat-icon { - cursor: pointer; - margin-left: 16px; color: #757575; + cursor: pointer; + transition: color 0.2s; } .actions mat-icon:hover { - color: #212121; + color: #1976d2; } .empty-list { display: flex; justify-content: center; align-items: center; - height: 100%; + height: 200px; + font-size: 16px; + color: #777; } -mat-spinner { - margin: 0 auto; - align-self: center; -} - -.container { +.search-container { display: flex; - justify-content: flex-end; -} -.classroomBtn-container { - display: flex; - justify-content: flex-end; - width: 100%; + gap: 20px; + margin: 20px 0; } -.container { - display: flex; - flex-direction: column; -} - -.header { - display: flex; - align-items: center; - gap: 10px; - padding: 20px; -} - -.header mat-form-field { - width: 300px; -} - -.main-content { - display: flex; +.search-container mat-form-field { + flex: 1; } .filters { padding: 20px; - display: flex; - flex-direction: column; - width: 300px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 8px; } -.saved-filter { +.details-container { display: flex; flex-direction: column; - width: 300px; - margin-bottom: 10px; - padding: 10px; + gap: 20px; + padding: 20px; + align-items: center; + text-align: center; } -.results { +.details-wrapper { + width: 95%; + padding: 20px; + display: block; +} + +.details-placeholder { width: 100%; } -.results-container { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); +button[mat-raised-button] { + align-self: flex-start; +} + +@media (max-width: 1024px) { + .card-container { + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 15px; + } + + .header-container { + flex-direction: column; + gap: 10px; + } + + .groups-button-row { + flex-wrap: wrap; + gap: 10px; + } +} + +@media (max-width: 768px) { + mat-card { + padding: 12px; + } + + .unidad-card { + font-size: 12px; + } +} + +.details-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; + padding: 30px; + background-color: #f9f9f9; + border-radius: 12px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + max-width: 1200px; + margin: 20px auto; +} + +mat-tree { + background-color: #f9f9f9; + padding: 10px; +} + +button { + margin: 5px; +} + +mat-tree mat-tree-node { + display: flex; + align-items: center; + padding: 10px; + border-radius: 6px; + transition: background-color 0.2s, color 0.2s; + cursor: pointer; +} + +mat-tree mat-tree-node:hover { + background-color: #e3f2fd; +} + +mat-tree mat-tree-node button.mat-icon-button { + margin-left: auto; + color: #757575; +} + +mat-tree mat-tree-node button.mat-icon-button:hover { + color: #1976d2; +} + +mat-tree mat-tree-node span { + font-size: 16px; + font-weight: 500; + color: #555; +} + +mat-tree mat-tree-node mat-icon { + margin-right: 10px; + color: #757575; + transition: color 0.2s; +} + +mat-tree mat-tree-node.expandable mat-icon { + color: black; + cursor: pointer; +} + +mat-tree mat-tree-node.expandable.disabled mat-icon { + color: grey; + opacity: 0.5; + cursor: not-allowed; +} + +mat-tree mat-tree-node:hover mat-icon { + color: black; +} + +mat-tree mat-tree-node mat-icon.node-icon { + color: #757575; + margin-right: 10px; +} + +mat-tree mat-tree-node mat-icon.node-icon.organizational-unit { + color: #1976d2; +} + +mat-tree mat-tree-node mat-icon.node-icon.classroom { + color: #757575; +} + +mat-tree mat-tree-node mat-icon.node-icon.client { + color: #757575; +} + +mat-tree mat-tree-node mat-icon.node-icon.group { + color: #757575; +} + +mat-tree mat-tree-node button.mat-icon-button { + margin-right: 10px; +} + +mat-tree mat-tree-node button.mat-icon-button.disabled-toggle { + color: grey; + opacity: 0.5; + cursor: not-allowed; +} + +mat-tree mat-tree-node button.mat-icon-button.disabled-toggle:hover { + background-color: transparent; +} + +mat-tree mat-tree-node:hover { + background-color: #e3f2fd; + cursor: pointer; +} + +mat-tree mat-tree-node.disabled { + cursor: not-allowed; +} + +mat-tree mat-tree-node.disabled:hover { + background-color: transparent; +} + +.selected-node { + background-color: #e0f7fa; + border-left: 4px solid #3F51B5; + padding-left: calc(16px - 4px); +} + +.mat-menu-item .mat-menu-item-submenu-icon { + display: none; +} + +.filters-container { + display: flex; + flex-wrap: wrap; gap: 16px; margin-bottom: 16px; } -.result-card { - width: 100%; - max-width: 250px; - height: 250px; -} -.paginator-container { - display: flex; - justify-content: center; - margin-bottom: 30px; - +.filters-container mat-form-field { + flex: 1 1 100%; + max-width: 300px; } -.divider { - margin: 20px 0; +.filter-container { + margin-bottom: 16px; } -mat-card { - margin-bottom: 20px; +.chip-busy { + background-color: indianred !important; + color: black; } -.mat-tooltip { - white-space: pre-line; +.chip-og-live { + background-color: yellow !important; + color: black; } -.classroom-grid { +.chip-windows, +.chip-windows-session, +.chip-macos { + background-color: cornflowerblue !important; + color: white; +} + +.chip-linux, +.chip-linux-session { + background-color: mediumpurple !important; + color: white; +} + +.chip-off { + background-color: darkgrey !important; + color: white; +} + + +.clients-card-container { display: flex; flex-wrap: wrap; gap: 16px; - justify-content: flex-start; /* Opcional: para alinear a la izquierda */ + padding: 16px; + justify-content: flex-start; } .classroom-item { - flex: 0 1 calc(16.66% - 16px); /* 6 columnas */ - max-width: calc(16.66% - 16px); - text-align: center; + flex: 1 1 calc(25% - 16px); + max-width: calc(25% - 16px); box-sizing: border-box; } .classroom-pc { - position: relative; + border: 1px solid #ccc; + border-radius: 8px; + padding: 16px; display: flex; flex-direction: column; align-items: center; - padding: 8px; - background-color: #f4f4f4; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + text-align: center; } -.pc-image { - width: 80px; - height: 80px; +.classroom-pc .pc-image { + width: 64px; + height: 64px; + margin-bottom: 8px; } .pc-details { - margin-top: 8px; - font-size: 12px; + margin-bottom: 8px; } -.client-name { - font-weight: bold; +.pc-details .client-name, +.pc-details .client-ip, +.pc-details .client-mac { display: block; -} - -.client-ip, -.client-mac { + font-size: 14px; color: #666; - font-size: 10px; - display: block; } -.pc-actions { - margin-top: 8px; +.pc-actions button { + margin: 0 4px; +} + +.main-container { + display: flex; + flex-direction: row; +} + +.tree-container { + width: 25%; + padding: 16px; + overflow-x: hidden; + overflow-y: auto; +} + +.clients-container { + width: 75%; + padding: 16px; + box-sizing: border-box; + overflow-y: auto; +} + +.clients-container h3 { + margin-bottom: 15px; + font-size: 1.5em; + color: #333; +} + +.client-item { display: flex; justify-content: center; - gap: 8px; } -.pc-og-live { - border: 2px solid #4caf50; +.client-card { + background-color: #fff; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 300px; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 15px; + transition: transform 0.3s ease, box-shadow 0.3s ease; } -.pc-busy { - border: 2px solid #ff9800; +.client-card:hover { + transform: translateY(-5px); + box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15); } -.pc-off { - border: 2px solid #f44336; +.client-image { + width: 60px; + height: 60px; + margin-bottom: 15px; } -.pc-linux { - border: 2px solid #9c27b0; -} - -.pc-windows { - border: 2px solid #2196f3; -} - -/* Pantallas medianas: 4 columnas */ -@media (max-width: 1024px) { - .classroom-item { - flex: 0 1 calc(25% - 16px); /* 4 columnas */ - } -} - -/* Pantallas pequeñas: 2 columnas */ -@media (max-width: 768px) { - .classroom-item { - flex: 0 1 calc(50% - 16px); /* 2 columnas */ - } -} - -/* Pantallas muy pequeñas: 1 columna */ -@media (max-width: 480px) { - .classroom-item { - flex: 0 1 100%; /* 1 columna */ - } -} - -.client-text { - font-size: 0.8rem; - color: rgba(0, 0, 0, 0.54); +.client-details { + margin-top: 10px; } .client-name { - font-size: 0.9rem; - text-align: center; + display: block; + font-size: 1.2em; + font-weight: 600; + color: #333; + margin-bottom: 5px; } + +.client-ip { + display: block; + font-size: 0.9em; + color: #666; +} + +button[mat-raised-button] { + margin-top: 15px; +} + +.clients-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 16px; +} + +.clients-list { + display: flex; + flex-direction: column; + gap: 16px; +} + +.client-item-list { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; + border: 1px solid #ccc; + border-radius: 4px; +} + +.client-details-list { + display: flex; + flex-direction: column; +} + +.view-toggle-container { + display: flex; + gap: 1rem; + margin-bottom: 1rem; +} + +.clients-grid { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +.clients-list .list-item-content { + display: flex; + justify-content: space-between; + width: 100%; + align-items: center; +} + +.client-card, .list-item-content { + border: 1px solid #ccc; + padding: 1rem; + border-radius: 5px; +} + +.client-card { + display: flex; + flex-direction: column; + align-items: center; +} + +.client-image { + width: 50px; + height: 50px; + margin-bottom: 0.5rem; +} + +.header-actions-container { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.back-button { + flex-shrink: 0; +} + +.view-toggle-container { + display: flex; + gap: 1rem; + align-items: center; +} + +.filters-container { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin: 16px 0; + padding: 0 16px; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +.filters-container mat-form-field { + flex: 1 1 300px; + max-width: 300px; +} + +.client-info { + display: flex; + flex-direction: column; + gap: 3px; + margin: 5px; +} + +.client-name { + font-size: 16px; + font-weight: bold; +} + +.flex { + display: flex; + justify-self: center; + align-items: center; +} + diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index 2de059a..47d5587 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -1,156 +1,361 @@ - - -
- +

+ {{ 'adminGroupsTitle' | translate }} +

+
+ + + +
+
+ + + + {{ 'filters' | translate }} + +
+ + {{ 'searchClient' | translate }} + + + + + + {{ savedFilter[0] }} + + + + + {{ 'searchTree' | translate }} + + + + {{ 'filterByType' | translate }} + + {{ 'all' | translate }} + {{ 'classroomsGroup' | translate }} + {{ 'classrooms' | translate }} + {{ 'computerGroups' | translate }} + + + +
+
+ +
+ + + + apartment {{ unidad.name }} + + + +
+ +
+ + + + + + + + +
+
+
+ + +
+ +
+ -

{{ 'adminGroupsTitle' | translate }}

-
- - - + +
+
+
+
+

{{ selectedUnidad?.name }}

+ + + + + {{ + node.type === 'organizational-unit' ? 'apartment' + : node.type === 'classrooms-group' ? 'meeting_room' + : node.type === 'classroom' ? 'school' + : node.type === 'clients-group' ? 'lan' + : node.type === 'client' ? 'computer' + : 'group' + }} + + {{ node.name }} + + + + + + {{ + node.type === 'organizational-unit' ? 'apartment' + : node.type === 'classrooms-group' ? 'meeting_room' + : node.type === 'classroom' ? 'school' + : node.type === 'clients-group' ? 'lan' + : node.type === 'client' ? 'computer' + : 'group' + }} + + {{ node.name }} + + - IP: {{ node.ip }} + + + + +
+ + + + + + + + + + + + + + +
+

{{ 'clients' | translate }} {{ selectedNode?.name ? ('del ' + selectedNode?.name) : '' }}

+
+
+
+ Client Icon +
+ {{ client.name }} + {{ client.ip }} +
+ + {{ client.status || 'off' }} + + + + + +
+ + + + + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'name' | translate }} +
+
{{ client.name }}
+
{{ client.ip }}
+
{{ client.mac }}
+
+
OG Live {{ client.ogLive?.name }} {{ 'status' | translate }} + + {{ client.status || 'off' }} + + + + + + {{ 'maintenance' | translate }} {{ client.maintenance }} {{ 'subnet' | translate }} {{ client.subnet }} {{ 'pxeTemplate' | translate }} {{ client.template?.name }} {{ 'parent' | translate }} {{ client.parentName }} {{ 'actions' | translate }} + + + + + + + + + + + + + + + + +
+
- -
- - {{ 'organizationalUnitTitle' | translate }} - - - - -
- apartment - {{ unidad.name }} - - more_vert - - - - - - - - - - - - -
-
-
-
-
- - - -
- {{ 'internalElementsTitle' | translate }} - - - {{ crumb }} - > - - -
-
- - - - - -
- info - {{ 'noInternalElementsMessage' | translate }} -
- -
- - apartment - meeting_room - school - computer - lan - help_outline - - {{ child.name }} -
- more_vert - - - - -
-
-
-
- - -
-
-
- PC Icon -
- {{ pc.name }} - {{ pc.ip }} - {{ pc.mac }} -
-
- - -
-
-
-
- -
-
-
- - - - - - - - - - - - - - +
+ diff --git a/ogWebconsole/src/app/components/groups/groups.component.spec.ts b/ogWebconsole/src/app/components/groups/groups.component.spec.ts index 5fb654f..b5646bf 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.spec.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.spec.ts @@ -113,131 +113,4 @@ describe('GroupsComponent', () => { expect(component.onSelectUnidad).toHaveBeenCalledWith(unidad); }); - it('should call onSelectChild method', () => { - spyOn(component, 'onSelectChild'); - const child = { id: '1', name: 'Test', type: 'unit' } as any; - component.onSelectChild(child); - expect(component.onSelectChild).toHaveBeenCalledWith(child); - }); - - it('should call navigateToBreadcrumb method', () => { - spyOn(component, 'navigateToBreadcrumb'); - component.navigateToBreadcrumb(1); - expect(component.navigateToBreadcrumb).toHaveBeenCalledWith(1); - }); - - it('should call loadChildrenAndClients method', () => { - spyOn(component, 'loadChildrenAndClients'); - component.loadChildrenAndClients('1'); - expect(component.loadChildrenAndClients).toHaveBeenCalledWith('1'); - }); - - it('should call onDeleteClick method', () => { - spyOn(component, 'onDeleteClick'); - const event = new MouseEvent('click'); - component.onDeleteClick(event, 'uuid', 'name', 'client'); - expect(component.onDeleteClick).toHaveBeenCalledWith(event, 'uuid', 'name', 'client'); - }); - - it('should call onEditClick method', () => { - spyOn(component, 'onEditClick'); - const event = new MouseEvent('click'); - component.onEditClick(event, 'client', 'uuid'); - expect(component.onEditClick).toHaveBeenCalledWith(event, 'client', 'uuid'); - }); - - it('should call onShowClick method', () => { - spyOn(component, 'onShowClick'); - const event = new MouseEvent('click'); - component.onShowClick(event, { type: 'unit' }); - expect(component.onShowClick).toHaveBeenCalledWith(event, { type: 'unit' }); - }); - - it('should call onTreeClick method', () => { - spyOn(component, 'onTreeClick'); - const event = new MouseEvent('click'); - component.onTreeClick(event, { type: 'unit' }); - expect(component.onTreeClick).toHaveBeenCalledWith(event, { type: 'unit' }); - }); - - it('should call onExecuteCommand method', () => { - spyOn(component, 'onExecuteCommand'); - const event = new MouseEvent('click'); - component.onExecuteCommand(event, 'child', 'name', 'type'); - expect(component.onExecuteCommand).toHaveBeenCalledWith(event, 'child', 'name', 'type'); - }); - - it('should call openSnackBar method', () => { - spyOn(component, 'openSnackBar'); - component.openSnackBar(true, 'message'); - expect(component.openSnackBar).toHaveBeenCalledWith(true, 'message'); - }); - - it('should call openBottomSheet method', () => { - spyOn(component, 'openBottomSheet'); - component.openBottomSheet(); - expect(component.openBottomSheet).toHaveBeenCalled(); - }); - - it('should call roomMap method', () => { - spyOn(component, 'roomMap'); - component.roomMap(); - expect(component.roomMap).toHaveBeenCalled(); - }); - - it('should call applyFilter method', () => { - spyOn(component, 'applyFilter'); - component.applyFilter(); - expect(component.applyFilter).toHaveBeenCalled(); - }); - - it('should call onPageChange method', () => { - spyOn(component, 'onPageChange'); - const event = { pageIndex: 1, pageSize: 10 } as any; - component.onPageChange(event); - expect(component.onPageChange).toHaveBeenCalledWith(event); - }); - - it('should call saveFilters method', () => { - spyOn(component, 'saveFilters'); - component.saveFilters(); - expect(component.saveFilters).toHaveBeenCalled(); - }); - - it('should call loadSelectedFilter method', () => { - spyOn(component, 'loadSelectedFilter'); - component.loadSelectedFilter(['name', 'uuid']); - expect(component.loadSelectedFilter).toHaveBeenCalledWith(['name', 'uuid']); - }); - - it('should call onCheckboxChange method', () => { - spyOn(component, 'onCheckboxChange'); - const event = { checked: true } as any; - component.onCheckboxChange(event, 'name', 'uuid'); - expect(component.onCheckboxChange).toHaveBeenCalledWith(event, 'name', 'uuid'); - }); - - it('should call toggleSelectAll method', () => { - spyOn(component, 'toggleSelectAll'); - component.toggleSelectAll(); - expect(component.toggleSelectAll).toHaveBeenCalled(); - }); - - it('should call isSelected method', () => { - spyOn(component, 'isSelected'); - component.isSelected('name'); - expect(component.isSelected).toHaveBeenCalledWith('name'); - }); - - it('should call sendActions method', () => { - spyOn(component, 'sendActions'); - component.sendActions(); - expect(component.sendActions).toHaveBeenCalled(); - }); - - it('should call iniciarTour method', () => { - spyOn(component, 'iniciarTour'); - component.iniciarTour(); - expect(component.iniciarTour).toHaveBeenCalled(); - }); }); diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 3066539..8451a2f 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -1,458 +1,592 @@ -import {Component, OnInit, ViewChild} from '@angular/core'; -import { DataService } from './services/data.service'; -import { ClientCollection, UnidadOrganizativa } from './model/model'; +import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; +import { MatBottomSheet } from '@angular/material/bottom-sheet'; +import { MatTabChangeEvent } from '@angular/material/tabs'; +import { ToastrService } from 'ngx-toastr'; +import { JoyrideService } from 'ngx-joyride'; +import { FlatTreeControl } from '@angular/cdk/tree'; +import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree'; +import { Subscription } from 'rxjs'; +import { DataService } from './services/data.service'; +import { UnidadOrganizativa, Client, TreeNode, FlatNode, Command } from './model/model'; import { CreateOrganizationalUnitComponent } from './shared/organizational-units/create-organizational-unit/create-organizational-unit.component'; -import { DeleteModalComponent } from '../../shared/delete_modal/delete-modal/delete-modal.component'; import { CreateClientComponent } from './shared/clients/create-client/create-client.component'; import { EditOrganizationalUnitComponent } from './shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component'; import { EditClientComponent } from './shared/clients/edit-client/edit-client.component'; -import { ShowOrganizationalUnitComponent} from "./shared/organizational-units/show-organizational-unit/show-organizational-unit.component"; -import {ToastrService} from "ngx-toastr"; -import {TreeViewComponent} from "./shared/tree-view/tree-view.component"; -import {MatBottomSheet} from "@angular/material/bottom-sheet"; -import {LegendComponent} from "./shared/legend/legend.component"; +import { ShowOrganizationalUnitComponent } from './shared/organizational-units/show-organizational-unit/show-organizational-unit.component'; +import { TreeViewComponent } from './shared/tree-view/tree-view.component'; +import { LegendComponent } from './shared/legend/legend.component'; +import { ClientTabViewComponent } from './components/client-tab-view/client-tab-view.component'; +import { OrganizationalUnitTabViewComponent } from './components/organizational-unit-tab-view/organizational-unit-tab-view.component'; +import { DeleteModalComponent } from '../../shared/delete_modal/delete-modal/delete-modal.component'; import { ClassroomViewDialogComponent } from './shared/classroom-view/classroom-view-modal'; -import {HttpClient} from "@angular/common/http"; -import {PageEvent} from "@angular/material/paginator"; -import { SaveFiltersDialogComponent } from './shared/save-filters-dialog/save-filters-dialog.component'; -import { AcctionsModalComponent } from './shared/acctions-modal/acctions-modal.component'; -import {MatTableDataSource} from "@angular/material/table"; -import {DatePipe} from "@angular/common"; -import {AdvancedSearchComponent} from "./components/advanced-search/advanced-search.component"; -import {MatTabChangeEvent} from "@angular/material/tabs"; -import {ClientTabViewComponent} from "./components/client-tab-view/client-tab-view.component"; -import { - OrganizationalUnitTabViewComponent -} from "./components/organizational-unit-tab-view/organizational-unit-tab-view.component"; -import { ExecuteCommandComponent } from '../commands/main-commands/execute-command/execute-command.component'; -import { ExecuteCommandOuComponent } from './shared/execute-command-ou/execute-command-ou.component'; -import { JoyrideService } from 'ngx-joyride'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; +import { MatPaginator } from '@angular/material/paginator'; + +enum NodeType { + OrganizationalUnit = 'organizational-unit', + ClassroomsGroup = 'classrooms-group', + Classroom = 'classroom', + ClientsGroup = 'clients-group', + Client = 'client', +} @Component({ selector: 'app-groups', templateUrl: './groups.component.html', - styleUrls: ['./groups.component.css'] + styleUrls: ['./groups.component.css'], }) -export class GroupsComponent implements OnInit { +export class GroupsComponent implements OnInit, OnDestroy { baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; - dataSource = new MatTableDataSource(); organizationalUnits: UnidadOrganizativa[] = []; selectedUnidad: UnidadOrganizativa | null = null; - selectedDetail: any | null = null; - children: any[] = []; - breadcrumb: string[] = []; - clientsData: any[] = []; - breadcrumbData: any[] = []; - loading:boolean = false; - loadingChildren:boolean = false; - searchTerm: string = ''; - selectedFilter1: string = 'none'; - selectedFilter2: string = 'none'; - selectedFilterOS: string[] = []; - selectedFilterStatus: string[] = []; - filterIP: string = ''; - filterMAC: string = ''; - filterName: string = ''; - filteredResults: any[] = []; - savedFilterNames: any[] = []; - length: number = 0; - itemsPerPage: number = 10; - page: number = 1; - pageSizeOptions: number[] = [5, 10, 25, 100]; - selectedElements: any[] = []; - isAllSelected: boolean = false; - filters: { [key: string]: string } = {}; - datePipe: DatePipe = new DatePipe('es-ES'); + selectedDetail: UnidadOrganizativa | null = null; + loading = false; + searchTerm = ''; + treeControl: FlatTreeControl; + treeFlattener: MatTreeFlattener; + treeDataSource: MatTreeFlatDataSource; + selectedNode: TreeNode | null = null; + commands: Command[] = []; + commandsLoading = false; + selectedClients = new MatTableDataSource([]); + cols = 4; + selectedClientsOriginal: Client[] = []; + currentView: 'card' | 'list' = 'list'; + isTreeViewActive = false; + savedFilterNames: [string, string][] = []; + selectedTreeFilter = ''; + syncStatus = false; + syncingClientId: string | null = null; + private originalTreeData: TreeNode[] = []; + + displayedColumns: string[] = ['name', 'oglive', 'status', 'maintenace', 'subnet', 'pxeTemplate', 'parentName', 'actions']; + + private _sort!: MatSort; + private _paginator!: MatPaginator; + + @ViewChild(MatSort) + set matSort(ms: MatSort) { + this._sort = ms; + if (this.selectedClients) { + this.selectedClients.sort = this._sort; + } + } + + @ViewChild(MatPaginator) + set matPaginator(mp: MatPaginator) { + this._paginator = mp; + if (this.selectedClients) { + this.selectedClients.paginator = this._paginator; + } + } + @ViewChild('clientTab') clientTabComponent!: ClientTabViewComponent; + @ViewChild('organizationalUnitTab') organizationalUnitTabComponent!: OrganizationalUnitTabViewComponent; + + private subscriptions: Subscription = new Subscription(); constructor( - private dataService: DataService, - public dialog: MatDialog, - private toastService: ToastrService, - private _bottomSheet: MatBottomSheet, - private http: HttpClient, - private joyrideService: JoyrideService - ) {} + private http: HttpClient, + private router: Router, + private dataService: DataService, + public dialog: MatDialog, + private bottomSheet: MatBottomSheet, + private joyrideService: JoyrideService, + private toastr: ToastrService + ) { + this.treeFlattener = new MatTreeFlattener( + this.transformer, + (node) => node.level, + (node) => node.expandable, + (node) => node.children + ); + + this.treeControl = new FlatTreeControl( + (node) => node.level, + (node) => node.expandable + ); + + this.treeDataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); + } ngOnInit(): void { this.search(); this.getFilters(); + this.updateGridCols(); + window.addEventListener('resize', this.updateGridCols); + + this.selectedClients.filterPredicate = (client: Client, filter: string): boolean => { + const lowerTerm = filter.toLowerCase(); + return ( + client.name.toLowerCase().includes(lowerTerm) || + client.ip?.toLowerCase().includes(lowerTerm) || + client.status?.toLowerCase().includes(lowerTerm) || + client.mac?.toLowerCase().includes(lowerTerm) + ); + }; } - @ViewChild('clientTab') clientTabComponent!: ClientTabViewComponent; - @ViewChild('organizationalUnitTab') organizationalUnitTabComponent!: OrganizationalUnitTabViewComponent; + ngOnDestroy(): void { + window.removeEventListener('resize', this.updateGridCols); + this.subscriptions.unsubscribe(); + } - onTabChange(event: MatTabChangeEvent) { - switch (event.index) { - case 2: - this.clientTabComponent.search(); - break; - case 3: - this.organizationalUnitTabComponent.search(); - break; - default: - break; + private transformer = (node: TreeNode, level: number): FlatNode => ({ + name: node.name, + type: node.type, + level, + expandable: !!node.children?.length, + hasClients: node.hasClients, + ip: node.ip, + '@id': node['@id'], + }); + + toggleView(view: 'card' | 'list'): void { + this.currentView = view; + } + + updateGridCols = (): void => { + const width = window.innerWidth; + this.cols = width <= 600 ? 1 : width <= 960 ? 2 : width <= 1280 ? 3 : 4; + }; + + clearSelection(): void { + this.selectedUnidad = null; + this.selectedDetail = null; + this.selectedClients.data = []; + this.isTreeViewActive = false; + } + + onTabChange(event: MatTabChangeEvent): void { + if (event.index === 2) { + this.clientTabComponent.search(); + } else if (event.index === 3) { + this.organizationalUnitTabComponent.search(); } } getFilters(): void { - this.dataService.getFilters().subscribe( - data => { - this.savedFilterNames = data.map((filter: any) => [filter.name, filter.uuid]); - }, - error => { - console.error('Error fetching filters:', error); - } + this.subscriptions.add( + this.dataService.getFilters().subscribe( + (data) => { + this.savedFilterNames = data.map((filter: { name: string; uuid: string; }) => [filter.name, filter.uuid]); + }, + (error) => { + console.error('Error fetching filters:', error); + } + ) + ); + } + + loadSelectedFilter(savedFilter: [string, string]): void { + this.subscriptions.add( + this.dataService.getFilter(savedFilter[1]).subscribe( + (response) => { + if (response) { + console.log('Filter:', response.filters); + } + }, + (error) => { + console.error('Error:', error); + } + ) ); } search(): void { this.loading = true; - this.dataService.getOrganizationalUnits(this.searchTerm).subscribe( - data => { - this.organizationalUnits = data; - this.loading = false; - }, - error => { - console.error('Error fetching unidades organizativas', error); - this.loading = false; - } + this.subscriptions.add( + this.dataService.getOrganizationalUnits(this.searchTerm).subscribe( + (data) => { + this.organizationalUnits = data; + this.loading = false; + }, + (error) => { + console.error('Error fetching organizational units', error); + this.loading = false; + } + ) ); } onSelectUnidad(unidad: UnidadOrganizativa): void { this.selectedUnidad = unidad; this.selectedDetail = unidad; - this.breadcrumb = [unidad.name]; - this.breadcrumbData = [unidad]; - this.loadChildrenAndClients(unidad.id); + this.selectedClients.data = this.collectAllClients(unidad); + this.selectedClientsOriginal = [...this.selectedClients.data]; + this.loadChildrenAndClients(unidad.id).then((fullData) => { + const treeData = this.convertToTreeData(fullData); + this.treeDataSource.data = treeData[0]?.children || []; + }); + this.isTreeViewActive = true; + + console.log('Selected unidad:', unidad); } - onSelectChild(child: any): void { - this.selectedDetail = child; - if (child.type !== 'client' && child.uuid && child.id) { - this.breadcrumb.push(child.name || child.name); - this.breadcrumbData.push(child); - this.loadChildrenAndClients(child.id); + private collectAllClients(node: UnidadOrganizativa): Client[] { + let clients = (node.clients || []).map(client => ({ + ...client, + parentName: node.name + })); + + if (node.children) { + node.children.forEach((child) => { + clients = clients.concat(this.collectAllClients(child).map(client => ({ + ...client, + parentName: client.parentName || '' + }))); + }); + } + return clients; + } + + + private async loadChildrenAndClients(id: string): Promise { + try { + const childrenData = await this.dataService.getChildren(id).toPromise(); + + const processHierarchy = (nodes: UnidadOrganizativa[]): UnidadOrganizativa[] => { + return nodes.map((node) => ({ + ...node, + children: node.children ? processHierarchy(node.children) : [], + })); + }; + + return { + ...this.selectedUnidad!, + children: childrenData ? processHierarchy(childrenData) : [], + }; + } catch (error) { + console.error('Error loading children:', error); + return this.selectedUnidad!; } } - navigateToBreadcrumb(index: number): void { - this.breadcrumb = this.breadcrumb.slice(0, index + 1); - const target = this.breadcrumbData[index]; - this.breadcrumbData = this.breadcrumbData.slice(0, index + 1); - this.selectedDetail = target; - this.loadChildrenAndClients(target.id); + + private convertToTreeData(data: UnidadOrganizativa): TreeNode[] { + const processNode = (node: UnidadOrganizativa): TreeNode => ({ + name: node.name, + type: node.type, + '@id': node['@id'], + children: node.children?.map(processNode) || [], + hasClients: (node.clients?.length ?? 0) > 0, + }); + return [processNode(data)]; } - loadChildrenAndClients(id: string): void { - this.loadingChildren = true - this.dataService.getChildren(id).subscribe( - childrenData => { - this.dataService.getClients(id).subscribe( - clientsData => { - this.clientsData = clientsData; - const newChildren = [...childrenData, ...clientsData]; - if (newChildren.length > 0) { - this.children = newChildren; - } else { - this.children = []; + onNodeClick(node: TreeNode): void { + this.selectedNode = node; + this.fetchClientsForNode(node); + } + + private fetchClientsForNode(node: TreeNode): void { + if (node.hasClients && node['@id']) { + this.subscriptions.add( + this.http.get<{ clients: Client[] }>(`${this.baseUrl}${node['@id']}`).subscribe( + (data) => { + const clientsWithParentName = (data.clients || []).map(client => ({ + ...client, + parentName: node.name + })); + this.selectedClients.data = clientsWithParentName; + this.selectedClients._updateChangeSubscription(); + if (this._paginator) { + this._paginator.firstPage(); } - this.loadingChildren = false }, - error => { - console.error('Error fetching clients', error); - this.clientsData = []; - this.children = []; - this.loadingChildren = false + (error) => { + console.error('Error fetching clients:', error); } - ); - }, - error => { - console.error('Error fetching children', error); - this.children = []; - this.loadingChildren = false + ) + ); + } else { + this.selectedClients.data = []; + this.selectedClients._updateChangeSubscription(); + } + } + + getNodeIcon(node: TreeNode): string { + switch (node.type) { + case NodeType.OrganizationalUnit: + return 'apartment'; + case NodeType.ClassroomsGroup: + return 'doors'; + case NodeType.Classroom: + return 'school'; + case NodeType.ClientsGroup: + return 'lan'; + case NodeType.Client: + return 'computer'; + default: + return 'group'; + } + } + + addOU(event: MouseEvent, parent: TreeNode | null = null): void { + event.stopPropagation(); + const dialogRef = this.dialog.open(CreateOrganizationalUnitComponent, { + data: { parent }, + width: '900px', + }); + dialogRef.afterClosed().subscribe(() => { + this.refreshOrganizationalUnits(); + }); + } + + addClient(event: MouseEvent, organizationalUnit: TreeNode | null = null): void { + event.stopPropagation(); + const dialogRef = this.dialog.open(CreateClientComponent, { + data: { organizationalUnit }, + width: '900px', + }); + dialogRef.afterClosed().subscribe(() => { + this.refreshOrganizationalUnits(); + if (organizationalUnit && organizationalUnit['@id']) { + this.refreshClientsForNode(organizationalUnit); } + }); + } + + private refreshOrganizationalUnits(): void { + this.subscriptions.add( + this.dataService.getOrganizationalUnits().subscribe( + (data) => { + this.organizationalUnits = data; + if (this.selectedUnidad) { + this.loadChildrenAndClients(this.selectedUnidad?.id || '').then((updatedData) => { + this.selectedUnidad = updatedData; + const treeData = this.convertToTreeData(updatedData); + this.originalTreeData = treeData[0]?.children || []; + this.treeDataSource.data = [...this.originalTreeData]; + }); + } + }, + (error) => console.error('Error fetching organizational units', error) + ) ); } - addOU(event: MouseEvent, parent:any = null): void { + onEditNode(event: MouseEvent, node: TreeNode | null): void { event.stopPropagation(); - const dialogRef = this.dialog.open(CreateOrganizationalUnitComponent, { data: { parent }, width: '900px'}); - dialogRef.afterClosed().subscribe(() => { - this.dataService.getOrganizationalUnits().subscribe( - data => { - this.organizationalUnits = data - }, - error => console.error('Error fetching unidades organizativas', error) - ); - }); - } + const uuid = node ? this.extractUuid(node['@id']) : null; + if (!uuid) return; - addClient(event: MouseEvent, organizationalUnit: any = null): void { - event.stopPropagation(); - - const dialogRef = this.dialog.open(CreateClientComponent, { data: { organizationalUnit }, width: '900px' }); - - dialogRef.afterClosed().subscribe(() => { - this.dataService.getOrganizationalUnits().subscribe( - data => { - this.organizationalUnits = data; - - if (organizationalUnit && organizationalUnit.id) { - this.loadChildrenAndClients(organizationalUnit.id); - } - }, - error => console.error('Error fetching unidades organizativas', error) - ); - }); -} - onDeleteClick(event: MouseEvent, uuid: string, name: string, type: string): void { - event.stopPropagation(); - if (type === 'client') { - const dialogRef = this.dialog.open(DeleteModalComponent, { - width: '400px', - data: { name } - }); - - dialogRef.afterClosed().subscribe(result => { - if (result) { - this.dataService.deleteElement(uuid, type).subscribe( - () => { - this.loadChildrenAndClients(this.selectedUnidad?.id || ''); - this.dataService.getOrganizationalUnits().subscribe( - data => this.organizationalUnits = data, - error => console.error('Error fetching unidades organizativas', error) - ); - this.openSnackBar(false, 'Entidad eliminada exitosamente') - }, - error => { - console.error('Error deleting element', error) - this.openSnackBar(true, error.error['hydra:description']) - } - ); - } - }); + if (node && node.type !== NodeType.Client) { + this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' }); } else { - const dialogDeleteGroupRef = this.dialog.open(DeleteModalComponent, { - width: '400px', - data: { name } - }); - - dialogDeleteGroupRef.afterClosed().subscribe(result => { - if (result && result === 'delete') { - this.dataService.deleteElement(uuid, type).subscribe( - () => { - this.loadChildrenAndClients(this.selectedUnidad?.id || ''); - this.dataService.getOrganizationalUnits().subscribe( - data => this.organizationalUnits = data, - error => console.error('Error fetching unidades organizativas', error) - ); - this.openSnackBar(false, 'Entidad eliminada exitosamente') - }, - error => { - console.error('Error deleting element', error) - this.openSnackBar(true, error.error['hydra:description']) - } - ); - } else if (result && result === 'change') { - this.dataService.changeParent(uuid).subscribe( - () => { - this.loadChildrenAndClients(this.selectedUnidad?.id || ''); - this.dataService.getOrganizationalUnits().subscribe( - data => this.organizationalUnits = data, - error => console.error('Error fetching unidades organizativas', error) - ); - this.openSnackBar(false, 'Entidad eliminada exitosamente') - }, - error => { - console.error('Error deleting element', error) - this.openSnackBar(true, error.error['hydra:description']) - } - ); - } - }); + this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' }); } } - onEditClick(event: MouseEvent, type: any, uuid: string): void { + onDeleteClick(event: MouseEvent, node: TreeNode | null, clientNode?: TreeNode | null): void { event.stopPropagation(); - if (type != "client") { - const dialogRef = this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px'}); - } else { - const dialogRef = this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' } ); - } - } + const uuid = node ? this.extractUuid(node['@id']) : null; + if (!uuid) return; - onShowClick(event: MouseEvent, data: any): void { - event.stopPropagation(); - if (data.type != "client") { - const dialogRef = this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '700px'}); - } - } + if (!node) return; + const dialogRef = this.dialog.open(DeleteModalComponent, { + width: '400px', + data: { name: node.name }, + }); - onTreeClick(event: MouseEvent, data: any): void { - event.stopPropagation(); - if (data.type != "client") { - const dialogRef = this.dialog.open(TreeViewComponent, { data: { data }, width: '800px'}); - } - } - - onExecuteCommand(event: MouseEvent, child: any, name: string, type:string): void { - console.log('Executing command on:', child); - - this.dialog.open(ExecuteCommandOuComponent, { - width: '50%', - data: { childUnitUuid: child } - }).afterClosed().subscribe((result) => { - if (result) { - console.log('Comando ejecutado con éxito'); - } else { - console.log('Ejecución de comando cancelada'); + dialogRef.afterClosed().subscribe((result) => { + if (result === true) { + this.deleteEntity(uuid, node.type, node); } }); } - openSnackBar(isError: boolean, message: string) { - if (isError) { - this.toastService.error(' Error al eliminar la entidad: ' + message, 'Error'); - } else - this.toastService.success(message, 'Éxito'); + private deleteEntity(uuid: string, type: string, node: TreeNode): void { + this.subscriptions.add( + this.dataService.deleteElement(uuid, type).subscribe( + () => { + this.refreshOrganizationalUnits(); + if (type === NodeType.Client) { + this.refreshClientsForNode(node); + } + this.toastr.success('Entidad eliminada exitosamente'); + }, + (error) => { + console.error('Error deleting entity:', error); + this.toastr.error('Error al eliminar la entidad', error.message); + } + ) + ); + } + + private refreshClientsForNode(node: TreeNode): void { + if (!node['@id']) { + this.selectedClients.data = []; + return; + } + this.fetchClientsForNode(node); + } + + onEditClick(event: MouseEvent, type: string, uuid: string): void { + event.stopPropagation(); + if (type !== NodeType.Client) { + this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' }); + } else { + this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' }); + } + } + + onRoomMap(room: TreeNode | null): void { + if (!room || !room['@id']) return; + this.subscriptions.add( + this.http.get<{ clients: Client[] }>(`${this.baseUrl}${room['@id']}`).subscribe( + (response) => { + this.dialog.open(ClassroomViewDialogComponent, { + width: '90vw', + data: { clients: response.clients }, + }); + }, + (error) => { + console.error('Error fetching room data:', error); + } + ) + ); + } + + fetchCommands(): void { + this.commandsLoading = true; + this.subscriptions.add( + this.http.get<{ 'hydra:member': Command[] }>(`${this.baseUrl}/commands?page=1&itemsPerPage=30`).subscribe( + (response) => { + this.commands = response['hydra:member']; + this.commandsLoading = false; + }, + (error) => { + console.error('Error fetching commands:', error); + this.commandsLoading = false; + } + ) + ); + } + + executeCommand(command: Command, selectedNode: TreeNode | null): void { + + if (!selectedNode) { + this.toastr.error('No hay un nodo seleccionado.'); + return; + } else { + this.toastr.success(`Ejecutando comando: ${command.name} en ${selectedNode.name}`); + } + } + + executeClientCommand(command: Command, client: Client): void { + this.toastr.success(`Ejecutando comando: ${command.name} en ${client.name}`); + } + + onShowClientDetail(event: MouseEvent, client: Client): void { + event.stopPropagation(); + this.router.navigate(['clients', client.uuid], { state: { clientData: client } }); + } + + onShowDetailsClick(event: MouseEvent, data: TreeNode | null): void { + event.stopPropagation(); + if (data && data.type !== NodeType.Client) { + this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '700px' }); + } else { + if (data) { + this.router.navigate(['clients', this.extractUuid(data['@id'])], { state: { clientData: data } }); + } + } + } + + onTreeClick(event: MouseEvent, data: TreeNode): void { + event.stopPropagation(); + if (data.type !== NodeType.Client) { + this.dialog.open(TreeViewComponent, { data: { data }, width: '800px' }); + } } openBottomSheet(): void { - this._bottomSheet.open(LegendComponent); - } - - roomMap(): void { - if (this.selectedDetail && this.selectedDetail.type === 'classroom') { - const dialogRef = this.dialog.open(ClassroomViewDialogComponent, { - width: '90vw', - data: { clients: this.clientsData } - }); - - dialogRef.afterClosed().subscribe(result => { - console.log('The dialog was closed'); - }); - } - } - - applyFilter() { - this.dataService.getFilteredResults(this.selectedFilter1, this.selectedFilter2, this.filterName, this.filterIP, this.filterMAC, this.page, this.itemsPerPage) - .subscribe( - response => { - this.filteredResults = response.results; - this.length = response.total; - }, - error => { - console.error('Error al obtener los resultados filtrados', error); - this.filteredResults = []; - } - ); - } - - onPageChange(event: PageEvent) { - this.page = event.pageIndex; - this.itemsPerPage = event.pageSize; - this.applyFilter(); - } - - saveFilters() { - const dialogRef = this.dialog.open(SaveFiltersDialogComponent); - - dialogRef.afterClosed().subscribe(result => { - if (result) { - const filters = { - name: result, - favourite: true, - filters: { - filter0: this.filterName, - filter1: this.selectedFilter1, - filter2: this.selectedFilter2, - filter3: this.selectedFilterOS, - filter4: this.selectedFilterStatus, - filter5: this.filterIP, - filter6: this.filterMAC, - - } - }; - - this.http.post(`${this.baseUrl}/views`, filters).subscribe(response => { - console.log('Response from server:', response); - this.toastService.success('Se ha guardado el filtro correctamente'); - }, error => { - console.error('Error:', error); - this.toastService.error(error); - }); - } - }); - } - - loadSelectedFilter(savedFilter: any) { - const url = `${this.baseUrl}/views/` + savedFilter[1]; - console.log('llamando a:', url); - - this.dataService.getFilter(savedFilter[1]).subscribe(response => { - console.log('Response from server:', response.filters); - if (response) { - console.log('Filter1:', response.filters); - this.filterName = response.filters.filter0 || ''; - this.selectedFilter1 = response.filters.filter1 || null; - this.selectedFilter2 = response.filters.filter2 || ''; - - this.selectedFilterOS = response.filters.filter3 || []; - this.selectedFilterStatus = response.filters.filter4 || []; - this.filterIP = response.filters.filter5 || ''; - this.filterMAC = response.filters.filter6 || ''; - - this.applyFilter(); - } - }, error => { - console.error('Error:', error); - }); - - } - - - onCheckboxChange(event: any, name: string, uuid: string) { - if (event.checked) { - this.selectedElements.push(uuid); - } else { - const index = this.selectedElements.indexOf(name); - if (index > -1) { - this.selectedElements.splice(index, 1); - } - } - - this.isAllSelected = this.selectedElements.length === this.filteredResults.length; - } - - toggleSelectAll() { - this.isAllSelected = !this.isAllSelected; - - if (this.isAllSelected) { - this.selectedElements = this.filteredResults.map(result => result.uuid); - } else { - this.selectedElements = []; - } - } - - isSelected(name: string): boolean { - return this.selectedElements.includes(name); - - } - - - sendActions() { - const dialogRef = this.dialog.open(AcctionsModalComponent, { data: { selectedElements: this.selectedElements }, width: '700px'}); + this.bottomSheet.open(LegendComponent); } iniciarTour(): void { this.joyrideService.startTour({ steps: ['groupsTitleStepText', 'addStep', 'keyStep', 'unitStep', 'elementsStep', 'tabsStep'], showPrevButton: true, - themeColor: '#3f51b5' + themeColor: '#3f51b5', }); } + + hasChild = (_: number, node: FlatNode): boolean => node.expandable; + isLeafNode = (_: number, node: FlatNode): boolean => !node.expandable; + + filterTree(searchTerm: string, filterType: string): void { + const filterNodes = (nodes: TreeNode[]): TreeNode[] => { + const filteredNodes: TreeNode[] = []; + for (const node of nodes) { + const matchesName = node.name.toLowerCase().includes(searchTerm.toLowerCase()); + const matchesType = filterType ? node.type.toLowerCase() === filterType.toLowerCase() : true; + const filteredChildren = node.children ? filterNodes(node.children) : []; + + if ((matchesName && matchesType) || filteredChildren.length > 0) { + filteredNodes.push({ ...node, children: filteredChildren }); + } + } + return filteredNodes; + }; + + const filteredData = filterNodes(this.originalTreeData); + this.treeDataSource.data = filteredData; + } + + + + onTreeFilterInput(event: Event): void { + const input = event.target as HTMLInputElement; + const searchTerm = input?.value || ''; + this.filterTree(searchTerm, this.selectedTreeFilter); + } + + onClientFilterInput(event: Event): void { + const input = event.target as HTMLInputElement; + const searchTerm = input?.value || ''; + this.filterClients(searchTerm); + } + + filterClients(searchTerm: string): void { + this.searchTerm = searchTerm.trim().toLowerCase(); + this.selectedClients.filter = this.searchTerm; + } + + + public setSelectedNode(node: TreeNode): void { + this.selectedNode = node; + } + + getStatus(client: Client): void { + if (!client.uuid || !client['@id']) return; + + this.syncingClientId = client.uuid; + this.syncStatus = true; + + this.subscriptions.add( + this.http.post(`${this.baseUrl}${client['@id']}/agent/status`, {}).subscribe( + () => { + this.toastr.success('Cliente actualizado correctamente'); + this.search(); + this.syncStatus = false; + this.syncingClientId = null; + }, + () => { + this.toastr.error('Error de conexión con el cliente'); + this.syncStatus = false; + this.syncingClientId = null; + } + ) + ); + } + + private extractUuid(idPath: string | undefined): string | null { + return idPath ? idPath.split('/').pop() || null : null; + } } diff --git a/ogWebconsole/src/app/components/groups/model/model.ts b/ogWebconsole/src/app/components/groups/model/model.ts index b37b389..39f747a 100644 --- a/ogWebconsole/src/app/components/groups/model/model.ts +++ b/ogWebconsole/src/app/components/groups/model/model.ts @@ -10,10 +10,13 @@ export interface Aula { export interface UnidadOrganizativa { id: string; - name: string; uuid: string; + name: string; type: string; - parent: UnidadOrganizativa[]; + '@id': string; + clients?: Client[]; + children?: UnidadOrganizativa[]; + parent?: UnidadOrganizativa; } export interface OrganizationalUnit { @@ -26,6 +29,9 @@ export interface OrganizationalUnit { } export interface Client { + mac: any; + status: any; + ip: any; "@id": string; "@type": string; id: number; @@ -38,6 +44,7 @@ export interface Client { createdAt: string; createdBy: string; uuid: string; + parentName?: string; } export interface ClientCollection { @@ -51,3 +58,35 @@ export interface ClientCollection { "@type": string; }; } + +export interface TreeNode { + name: string; + type: string; + '@id'?: string; + children?: TreeNode[]; + hasClients?: boolean; + clients?: Client[]; + ip?: string; +} + +export interface FlatNode { + name: string; + type: string; + level: number; + expandable: boolean; + hasClients?: boolean; + ip?: string; + '@id'?: string; +} + + +export interface Command { + name: string; + description?: string; +} + +export interface Filter { + name: string; + uuid: string; +} + diff --git a/ogWebconsole/src/app/components/groups/services/data.service.ts b/ogWebconsole/src/app/components/groups/services/data.service.ts index ffc42f2..997c149 100644 --- a/ogWebconsole/src/app/components/groups/services/data.service.ts +++ b/ogWebconsole/src/app/components/groups/services/data.service.ts @@ -86,10 +86,46 @@ export class DataService { ); } + getOgLives(): Observable { + const url = `${this.baseUrl}/og-lives`; + return this.http.get(url).pipe( + map(response => { + if (response['hydra:member'] && Array.isArray(response['hydra:member'])) { + return response['hydra:member'] + } else { + throw new Error('Unexpected response format'); + } + }), + catchError(error => { + console.error('Error fetching clients', error); + return throwError(error); + }) + ); + } + + getRepositories(): Observable { + const url = `${this.baseUrl}/image-repositories`; + return this.http.get(url).pipe( + map(response => { + if (response['hydra:member'] && Array.isArray(response['hydra:member'])) { + return response['hydra:member'] + } else { + throw new Error('Unexpected response format'); + } + }), + catchError(error => { + console.error('Error fetching clients', error); + return throwError(error); + }) + ); + } + deleteElement(uuid: string, type: string): Observable { const url = type === 'client' ? `${this.baseUrl}/clients/${uuid}` : `${this.baseUrl}/organizational-units/${uuid}`; + + console.log('DELETE URL:', url); // Depuración return this.http.delete(url).pipe( catchError(error => { console.error('Error deleting element', error); @@ -98,6 +134,7 @@ export class DataService { ); } + changeParent(uuid: string): Observable { const url = `${this.baseUrl}/organizational-units/${uuid}/change-parent`; // @ts-ignore @@ -184,5 +221,15 @@ export class DataService { ); } + getOrganizationalUnitById(id: string): Observable { + const url = `${this.baseUrl}/organizational-units/${id}`; + return this.http.get(url).pipe( + catchError(error => { + console.error('Error fetching organizational unit', error); + return throwError(error); + }) + ); + } + } diff --git a/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.css b/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.css index 371b00b..bcf134e 100644 --- a/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.css +++ b/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.css @@ -31,18 +31,11 @@ mat-card { } .client-info { - position: absolute; - top: 20px; - left: 5px; - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - background-color: rgba(0, 0, 0, 0); - color: black; text-align: center; - padding: 15px; - box-sizing: border-box; + margin-top: 5px; + font-size: medium; + color: gray; + align-items: center; } .client-name { diff --git a/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.html b/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.html index 5085467..5485a3d 100644 --- a/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.html +++ b/ogWebconsole/src/app/components/groups/shared/classroom-view/classroom-view.component.html @@ -10,12 +10,9 @@
{{ 'clientAlt' | translate }} -
-
{{ client.name }}
-
- {{ client.ip }} -
-
+
+
+ {{ client.name }}
diff --git a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.css b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.css index fcf40bd..b7bc183 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.css +++ b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.css @@ -1,63 +1,161 @@ -h1 { - text-align: center; - font-family: 'Roboto', sans-serif; - font-weight: 400; - color: #3f51b5; - margin-bottom: 20px; -} - -.network-form { +.create-client-container { display: flex; flex-direction: column; - gap: 15px; + padding: 16px; + font-family: Arial, sans-serif; + font-size: 14px; + align-items: center; } -.form-field { - width: 100%; - margin-top: 10px; +h1, h3, h4 { + margin: 0 0 16px; + color: #333; + font-weight: 600; +} + +h1 { + font-size: 20px; +} + +h3 { + font-size: 18px; +} + +h4 { + font-size: 16px; + margin-top: 16px; +} + +.inputs-container { + display: flex; + gap: 24px; + margin-top: 16px; } .mat-dialog-content { - padding: 50px; + flex: 1; + background-color: #f9f9f9; + border-radius: 8px; + padding: 16px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + min-width: 600px; + max-width: 90vw; + width: 800px; } -button { - text-transform: none; - font-size: 16px; - font-weight: 500; +.create-multiple-client-container { + flex: 1; + background-color: #f9f9f9; + border-radius: 8px; + padding: 16px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } -.mat-slide-toggle { - margin-top: 20px; -} - -mat-option .unit-name { - display: block; -} - -mat-option .unit-path { - display: block; - font-size: 0.8em; - color: gray; -} - -.loading-spinner { - display: block; - margin: 0 auto; - align-items: center; - justify-content: center; -} - -.create-client-container { - position: relative; -} - -.grid-form { +.client-form { display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 20px; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; } .form-field { width: 100%; } + +.mat-form-field { + width: 100%; +} + +.scrollable-table { + max-height: 200px; + overflow-y: auto; + margin-top: 16px; + border: 1px solid #ddd; + border-radius: 8px; +} + +table { + width: 100%; + border-collapse: collapse; +} + +th, td { + text-align: left; + padding: 8px; + border-bottom: 1px solid #ddd; +} + +th { + background-color: #f1f1f1; + font-weight: bold; +} + +tr:hover { + background-color: #f9f9f9; +} + +button { + margin-right: 8px; +} + +button:last-child { + margin-right: 0; +} + +.mat-dialog-actions { + margin-top: 16px; + display: flex; + justify-content: space-between; +} + +button.mat-raised-button { + text-transform: none; + font-weight: 600; +} + +.loading-spinner { + margin: 16px auto; + display: block; +} + +.toggle-button { + background: none; + border: none; + color: #007BFF; + cursor: pointer; + font-size: 14px; + text-decoration: underline; +} + +.toggle-button:hover { + text-decoration: none; +} + +.mat-divider { + margin: 0 16px; +} + +.upload-container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; +} + +input[type="file"] { + display: none; +} + +@media (max-width: 768px) { + .inputs-container { + flex-direction: column; + gap: 16px; + } + + .mat-dialog-content, .create-multiple-client-container { + padding: 12px; + } + + .scrollable-table { + max-height: 150px; + } +} diff --git a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.html b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.html index 130eaaa..c724c43 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.html +++ b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.html @@ -1,92 +1,136 @@ -
-

{{ 'addClientDialogTitle' | translate }}

-
- -
- - {{ 'organizationalUnitLabel' | translate }} - - -
{{ unit.name }}
-
{{ unit.path }}
-
-
-
+
+

{{ 'addClientTitle' | translate }}s

+
+
+ + + + {{ 'organizationalUnitLabel' | translate }} + + +
{{ unit.name }}
+
+
+
+ - - {{ 'nameLabel' | translate }} - - +
+

Añadir múltiples clientes

+
+ + +

o añadelos manualmente:

+
+ + +
+
- - {{ 'ogLiveLabel' | translate }} - - - {{ oglive.name }} - - - +

Clientes importados:

+
+ + + + + - - {{ 'serialNumberLabel' | translate }} - - + + + + - - {{ 'netifaceLabel' | translate }} - - - {{ type.name }} - - - + + +
Nombre {{ client.name }} IP {{ client.ip }}
+
+
- - {{ 'netDriverLabel' | translate }} - - - {{ type.name }} - - - + - - {{ 'macLabel' | translate }} - {{ 'macHint' | translate }} - - {{ 'macError' | translate }} - + +

Añadir un cliente

+ +
+ + + {{ 'nameLabel' | translate }} + + - - {{ 'ipLabel' | translate }} - {{ 'ipHint' | translate }} - - {{ 'ipError' | translate }} - + + {{ 'ogLiveLabel' | translate }} + + + {{ oglive.name }} + + + - - {{ 'templateLabel' | translate }} - - - {{ template.name }} - - - + + {{ 'serialNumberLabel' | translate }} + + - - {{ 'hardwareProfileLabel' | translate }} - - - {{ unit.description }} - - - {{ 'hardwareProfileError' | translate }} - -
+ + {{ 'netifaceLabel' | translate }} + + + {{ type.name }} + + + + + + {{ 'netDriverLabel' | translate }} + + + {{ type.name }} + + + + + + {{ 'macLabel' | translate }} + {{ 'macHint' | translate }} + + {{ 'macError' | translate }} + + + + {{ 'ipLabel' | translate }} + {{ 'ipHint' | translate }} + + {{ 'ipError' | translate }} + + + + {{ 'templateLabel' | translate }} + + + {{ template.name }} + + + + + + {{ 'hardwareProfileLabel' | translate }} + + + {{ unit.description }} + + + {{ 'hardwareProfileError' | translate }} + + +
+
- - + + +
diff --git a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.ts b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.ts index 5cffebd..d79f63f 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.ts @@ -5,6 +5,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; import { ToastrService } from 'ngx-toastr'; import { DataService } from '../../../services/data.service'; +import * as Papa from 'papaparse'; @Component({ selector: 'app-create-client', @@ -18,16 +19,19 @@ export class CreateClientComponent implements OnInit { hardwareProfiles: any[] = []; ogLives: any[] = []; templates: any[] = []; - private errorForm: boolean = false; + uploadedClients: any[] = []; + loading: boolean = false; + displayedColumns: string[] = ['name', 'ip']; + isSingleClientForm: boolean = false; + showTextarea: boolean = true; protected netifaceTypes = [ - { "name": 'Eth0', "value": "eth0" }, - { "name": 'Eth1', "value": "eth1" }, - { "name": 'Eth2', "value": "eth2" }, + { name: 'Eth0', value: 'eth0' }, + { name: 'Eth1', value: 'eth1' }, + { name: 'Eth2', value: 'eth2' } ]; protected netDriverTypes = [ - { "name": 'Generic', "value": "generic" }, + { name: 'Generic', value: 'generic' } ]; - loading: boolean = false; constructor( private fb: FormBuilder, @@ -37,16 +41,22 @@ export class CreateClientComponent implements OnInit { private toastService: ToastrService, private dataService: DataService, @Inject(MAT_DIALOG_DATA) public data: any - ) { } + ) {} ngOnInit(): void { - console.log(this.data); + this.initForm(); this.loadParentUnits(); this.loadHardwareProfiles(); this.loadOgLives(); - this.loadPxeTemplates() + this.loadPxeTemplates(); + } + + initForm(): void { this.clientForm = this.fb.group({ - organizationalUnit: [this.data.organizationalUnit ? this.data.organizationalUnit['@id'] : null, Validators.required], + organizationalUnit: [ + this.data.organizationalUnit ? this.data.organizationalUnit['@id'] : null, + Validators.required + ], name: ['', Validators.required], serialNumber: [''], netiface: null, @@ -54,25 +64,14 @@ export class CreateClientComponent implements OnInit { mac: ['', Validators.required], ip: ['', Validators.required], template: [null], - hardwareProfile: [this.data.organizationalUnit && this.data.organizationalUnit.networkSettings && this.data.organizationalUnit.networkSettings.hardwareProfile ? this.data.organizationalUnit.networkSettings.hardwareProfile['@id'] : null], + hardwareProfile: [ + this.data.organizationalUnit?.networkSettings?.hardwareProfile?.['@id'] || null + ], ogLive: [null] }); } - loadHardwareProfiles(): void { - this.dataService.getHardwareProfiles().subscribe( - (data: any[]) => { - this.hardwareProfiles = data; - this.loading = false; - }, - (error: any) => { - console.error('Error fetching hardware profiles', error); - this.loading = false; - } - ); - } - - loadParentUnits() { + loadParentUnits(): void { this.loading = true; const url = `${this.baseUrl}/organizational-units?page=1&itemsPerPage=10000`; @@ -88,9 +87,22 @@ export class CreateClientComponent implements OnInit { ); } - loadOgLives() { + loadHardwareProfiles(): void { + this.dataService.getHardwareProfiles().subscribe( + (data: any[]) => { + this.hardwareProfiles = data; + this.loading = false; + }, + error => { + console.error('Error fetching hardware profiles:', error); + this.loading = false; + } + ); + } + loadOgLives(): void { const url = `${this.baseUrl}/og-lives?page=1&itemsPerPage=30`; + this.http.get(url).subscribe( response => { this.ogLives = response['hydra:member']; @@ -109,39 +121,123 @@ export class CreateClientComponent implements OnInit { this.templates = response['hydra:member']; }, error => { - console.error('Error fetching ogLives:', error); + console.error('Error fetching PXE templates:', error); } ); } - onSubmit() { - if (this.clientForm.valid) { - this.errorForm = false; - const formData = this.clientForm.value; - formData.ogLive = formData.ogLive; - this.http.post(`${this.baseUrl}/clients`, formData).subscribe( - response => { - this.dialogRef.close(response); - this.openSnackBar(false, 'Cliente creado exitosamente'); - }, - error => { - console.error('Error during POST:', error); - this.errorForm = true; - this.openSnackBar(true, 'Error al crear el cliente: ' + error.error['hydra:description']); + onFileUpload(event: any): void { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + + reader.onload = (e: any) => { + const textData = e.target.result; + const regex = /host\s+(\S+)\s+\{\s+hardware\s+ethernet\s+([\da-fA-F:]+);\s+fixed-address\s+([\d.]+);\s+\}/g; + let match; + const clients = []; + + while ((match = regex.exec(textData)) !== null) { + clients.push({ + name: match[1], + mac: match[2], + ip: match[3] + }); } - ); + + if (clients.length > 0) { + this.uploadedClients = clients; + this.toastService.success('Archivo cargado correctamente, los datos están listos para enviarse.', 'Éxito'); + this.showTextarea = false; + } else { + this.toastService.error('No se encontraron datos válidos', 'Error'); + this.showTextarea = true; + } + }; + + reader.readAsText(file); } } - + + onTextarea(text: string): void { + const regex = /host\s+(\S+)\s+\{\s+hardware\s+ethernet\s+([\da-fA-F:]+);\s+fixed-address\s+([\d.]+);\s+\}/g; + let match; + const clients = []; + + while ((match = regex.exec(text)) !== null) { + clients.push({ + name: match[1], + mac: match[2], + ip: match[3] + }); + } + + if (clients.length > 0) { + this.uploadedClients = clients; + this.toastService.success('Datos cargados correctamente, los datos están listos para enviarse.', 'Éxito'); + this.showTextarea = false; + } else { + this.toastService.error('No se encontraron datos válidos', 'Error'); + this.showTextarea = true; + } + } + + onSubmit(): void { + if (this.isSingleClientForm) { + if (this.clientForm.valid) { + const formData = this.clientForm.value; + console.log('Form data:', formData); + this.http.post(`${this.baseUrl}/clients`, formData).subscribe( + response => { + this.toastService.success('Cliente creado exitosamente', 'Éxito'); + this.dialogRef.close(response); + }, + error => { + console.error('Error durante POST:', error); + this.toastService.error('Error al crear el cliente', 'Error'); + } + ); + } + } else { + if (this.uploadedClients.length > 0) { + this.uploadedClients.forEach(client => { + const formData = { + organizationalUnit: this.clientForm.value.organizationalUnit || null, + name: client.name || null, + mac: client.mac || null, + ip: client.ip || null, + template: this.clientForm.value.template || null, + hardwareProfile: this.clientForm.value.hardwareProfile || null, + ogLive: this.clientForm.value.ogLive || null, + serialNumber: null, + netiface: null, + netDriver: null + }; + + this.http.post(`${this.baseUrl}/clients`, formData).subscribe( + response => { + this.toastService.success(`Cliente ${client.name} creado exitosamente`, 'Éxito'); + }, + error => { + console.error(`Error al crear el cliente ${client.name}:`, error); + this.toastService.error(`Error al crear el cliente ${client.name}`, 'Error'); + } + ); + }); + + this.uploadedClients = []; + this.dialogRef.close(); + } else { + this.toastService.error('No hay clientes cargados para añadir', 'Error'); + } + } + } + + toggleClientForm(): void { + this.isSingleClientForm = !this.isSingleClientForm; + } + onNoClick(): void { this.dialogRef.close(); } - - openSnackBar(isError: boolean, message: string) { - if (isError) { - this.toastService.error(' Error al crear el cliente: ' + message, 'Error'); - } else { - this.toastService.success(message, 'Éxito'); - } - } } diff --git a/ogWebconsole/src/app/components/groups/shared/legend/legend.component.html b/ogWebconsole/src/app/components/groups/shared/legend/legend.component.html index c60c785..6cd66de 100644 --- a/ogWebconsole/src/app/components/groups/shared/legend/legend.component.html +++ b/ogWebconsole/src/app/components/groups/shared/legend/legend.component.html @@ -19,4 +19,14 @@ computer
{{ 'clientTitle' | translate }}
+ + + school +
Disponible acceso remoto
+
+ + school +
No disponible acceso remoto
+
+ diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html index b477281..2d63221 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html @@ -84,6 +84,22 @@
{{ 'networkSettingsStepLabel' | translate }} + + {{ 'ogLiveLabel' | translate }} + + + {{ oglive.name }} + + + + + {{ 'repositoryLabel' | translate }} + + + {{ repository.name }} + + + {{ 'nextServerLabel' | translate }} diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.ts b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.ts index bffb4a9..71a8b19 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.ts @@ -37,6 +37,8 @@ export class CreateOrganizationalUnitComponent implements OnInit { parentUnits: any[] = []; hardwareProfiles: any[] = []; calendars: any[] = []; + ogLives: any[] = []; + repositories: any[] = []; selectedCalendarUuid: string | null = null; @Output() unitAdded = new EventEmitter(); @@ -59,6 +61,8 @@ export class CreateOrganizationalUnitComponent implements OnInit { comments: [''], }); this.networkSettingsFormGroup = this._formBuilder.group({ + ogLive: [null], + ogRepository: [null], nextServer: [''], bootFileName: [''], proxy: [''], @@ -89,6 +93,8 @@ export class CreateOrganizationalUnitComponent implements OnInit { this.loadParentUnits(); this.loadHardwareProfiles(); this.loadCalendars(); + this.loadOgLives(); + this.loadRepositories(); } loadParentUnits() { @@ -106,6 +112,20 @@ export class CreateOrganizationalUnitComponent implements OnInit { ); } + loadOgLives() { + this.dataService.getOgLives().subscribe( + (data: any[]) => this.ogLives = data, + error => console.error('Error fetching ogLives', error) + ); + } + + loadRepositories() { + this.dataService.getRepositories().subscribe( + (data: any[]) => this.repositories = data, + error => console.error('Error fetching repositories', error) + ); + } + loadCalendars() { const apiUrl = `${this.baseUrl}/remote-calendars?page=1&itemsPerPage=30`; this.http.get(apiUrl).subscribe( @@ -169,14 +189,25 @@ export class CreateOrganizationalUnitComponent implements OnInit { comments: additionalInfoFormValues.comments, networkSettings: { ...networkSettingsFormValues }, menu: networkSettingsFormValues.menu || null, + ogLive: networkSettingsFormValues.ogLive || null, + ogRepository: networkSettingsFormValues.ogRepository || null, hardwareProfile: networkSettingsFormValues.hardwareProfile || null, }; } onCalendarChange(event: any) { + this.generalFormGroup.value.remoteCalendar = event.value; this.selectedCalendarUuid = event.value; } + onOgLiveChange(event: any) { + this.networkSettingsFormGroup.value.ogLive = event.value; + } + + onRepositoryChange(event: any) { + this.networkSettingsFormGroup.value.ogRepository = event + } + onNoClick(): void { this.dialogRef.close(); } diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html index a9ea382..2c3e5d7 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html @@ -81,6 +81,22 @@ {{ 'networkSettingsStepLabel' | translate }} + + {{ 'ogLiveLabel' | translate }} + + + {{ oglive.name }} + + + + + {{ 'repositoryLabel' | translate }} + + + {{ repository.name }} + + + {{ 'proxyUrlLabel' | translate }} @@ -143,12 +159,12 @@ {{ 'validationToggle' | translate }}
-
-
+
+
diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts index 1bc65e3..7f293ef 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts @@ -1,8 +1,8 @@ -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { CreateOrganizationalUnitComponent } from '../create-organizational-unit/create-organizational-unit.component'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; +import {Component, EventEmitter, Inject, OnInit, Output} from '@angular/core'; +import {FormBuilder, FormGroup} from '@angular/forms'; +import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {CreateOrganizationalUnitComponent} from '../create-organizational-unit/create-organizational-unit.component'; import {DataService} from "../../../services/data.service"; import {ToastrService} from "ngx-toastr"; @@ -23,6 +23,8 @@ export class EditOrganizationalUnitComponent implements OnInit { hardwareProfiles: any[] = []; isEditMode: boolean; currentCalendar: any = []; + ogLives: any[] = []; + repositories: any[] = []; protected p2pModeOptions = [ {"name": 'Leecher', "value": "p2p-mode-leecher"}, {"name": 'Peer', "value": "p2p-mode-peer"}, @@ -57,6 +59,8 @@ export class EditOrganizationalUnitComponent implements OnInit { }); this.networkSettingsFormGroup = this._formBuilder.group({ + ogLive: [null], + repository: [null], proxy: [null], dns: [null], netmask: [null], @@ -90,6 +94,8 @@ export class EditOrganizationalUnitComponent implements OnInit { this.loadParentUnits(); this.loadHardwareProfiles(); this.loadCalendars(); + this.loadOgLives(); + this.loadRepositories(); } loadParentUnits() { @@ -116,6 +122,20 @@ export class EditOrganizationalUnitComponent implements OnInit { ); } + loadOgLives() { + this.dataService.getOgLives().subscribe( + (data: any[]) => this.ogLives = data, + error => console.error('Error fetching ogLives', error) + ); + } + + loadRepositories() { + this.dataService.getRepositories().subscribe( + (data: any[]) => this.repositories = data, + error => console.error('Error fetching repositories', error) + ); + } + loadCalendars() { const apiUrl = `${this.baseUrl}/remote-calendars?page=1&itemsPerPage=30`; this.http.get(apiUrl).subscribe( @@ -138,9 +158,15 @@ export class EditOrganizationalUnitComponent implements OnInit { );} onCalendarChange(event: any) { - const selectedCalendarId = event.value; - console.log('Selected calendar ID:', selectedCalendarId); - this.generalFormGroup.value.remoteCalendar = selectedCalendarId; + this.generalFormGroup.value.remoteCalendar = event.value; + } + + onOgLiveChange(event: any) { + this.networkSettingsFormGroup.value.ogLive = event.value; + } + + onRepositoryChange(event: any) { + this.networkSettingsFormGroup.value.repository = event.value } loadData(uuid: string) { @@ -171,6 +197,8 @@ export class EditOrganizationalUnitComponent implements OnInit { mcastMode: data.networkSettings.mcastMode, menu: data.networkSettings.menu ? data.networkSettings.menu['@id'] : null, hardwareProfile: data.networkSettings.hardwareProfile ? data.networkSettings.hardwareProfile['@id'] : null, + ogLive: data.networkSettings.ogLive ? data.networkSettings.ogLive['@id'] : null, + repository: data.networkSettings.repository ? data.networkSettings.repository['@id'] : null, validation: data.networkSettings.validation }); this.classroomInfoFormGroup.patchValue({ @@ -245,5 +273,5 @@ export class EditOrganizationalUnitComponent implements OnInit { this.dialogRef.close(); } - + } diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/show-organizational-unit/show-organizational-unit.component.ts b/ogWebconsole/src/app/components/groups/shared/organizational-units/show-organizational-unit/show-organizational-unit.component.ts index a62e1ee..14be025 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/show-organizational-unit/show-organizational-unit.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/show-organizational-unit/show-organizational-unit.component.ts @@ -11,9 +11,9 @@ export class ShowOrganizationalUnitComponent { baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; displayedColumns: string[] = ['property', 'value']; currentCalendar: any; + ou: any; generalData: any[] = []; - networkData = [ - ]; + networkData: any[] = []; constructor( @Inject(MAT_DIALOG_DATA) public data: any, @@ -22,11 +22,22 @@ export class ShowOrganizationalUnitComponent { } ngOnInit(): void { - if (this.data.data.remoteCalendar) { - this.loadCurrentCalendar(this.data.data.remoteCalendar.uuid); - } else { - this.initializeGeneralData(); - } + this.loadOrganizationalUnit(this.data.data['@id']); + } + + loadOrganizationalUnit(uuid: string): void { + console.log(this.data['@id']) + const apiUrl = `${this.baseUrl}${uuid}`; + this.http.get(apiUrl).subscribe( + response => { + this.ou = response; + console.log('Organizational unit', this.ou); + this.initializeData(); + }, + error => { + console.error('Error loading organizational unit', error); + this.initializeData(); + }); } loadCurrentCalendar(uuid: string): void { @@ -34,26 +45,39 @@ export class ShowOrganizationalUnitComponent { this.http.get(apiUrl).subscribe( response => { this.currentCalendar = response.name; - this.initializeGeneralData(); + this.initializeData(); }, error => { console.error('Error loading current calendar', error); - this.initializeGeneralData(); + this.initializeData(); } ); } - initializeGeneralData(): void { + initializeData(): void { this.generalData = [ - { property: 'Nombre', value: this.data.data.name }, - { property: 'Uuid', value: this.data.data.uuid }, - { property: 'Descripción', value: this.data.data.description }, - { property: 'Comentarios', value: this.data.data.comments }, - { property: 'Tipo', value: this.data.data.type }, - { property: 'Unidad organizativa superior', value: this.data.data.parent ? this.data.data.parent.name : '-' }, - { property: 'Creado por', value: this.data.data.createdBy }, - { property: 'Creado el', value: this.data.data.createdAt }, - { property: 'Calendario asociado', value: this.data.data.remoteCalendar ? this.currentCalendar : '-' } + { property: 'Nombre', value: this.ou.name }, + { property: 'Uuid', value: this.ou.uuid }, + { property: 'Descripción', value: this.ou.description }, + { property: 'Comentarios', value: this.ou.comments }, + { property: 'Tipo', value: this.ou.type }, + { property: 'Unidad organizativa superior', value: this.ou.parent ? this.ou.parent.name : '-' }, + { property: 'Creado el', value: this.ou.createdAt }, ]; + + this.networkData = [ + { property: 'Calendario asociado', value: this.ou.remoteCalendar ? this.currentCalendar : '-' }, + { property: 'Aforo', value: this.ou.capacity }, + { property: 'Localización', value: this.ou.location }, + { property: 'Calendario', value: this.ou.calendar ? this.ou.calendar.name : '-' }, + { property: 'Proyector', value: this.ou.projector }, + { property: 'Pizarra', value: this.ou.board }, + { property: 'OgLive', value: this.ou.networkSettings.ogLive ? this.ou.networkSettings.ogLive.name : '-' }, + { property: 'Repositorio', value: this.ou.networkSettings.repository ? this.ou.networkSettings.repository.name : '-' }, + { property: 'OgLog', value: this.ou.networkSettings.oglog }, + { property: 'OgShare', value: this.ou.networkSettings.ogshare }, + { property: 'Perfil de hardware', value: this.ou.networkSettings.hardwareProfile ? this.ou.networkSettings.hardwareProfile.name : '-' }, + { property: 'Máscara de red', value: this.ou.networkSettings.netmask }, + ] } } diff --git a/ogWebconsole/src/app/components/images/images.component.css b/ogWebconsole/src/app/components/images/images.component.css index 6c78292..50f0f3a 100644 --- a/ogWebconsole/src/app/components/images/images.component.css +++ b/ogWebconsole/src/app/components/images/images.component.css @@ -71,19 +71,23 @@ table { margin: 8px 8px 8px 0; } -.status-success { - background-color: #4caf50; /* Verde */ +.chip-failed { + background-color: #e87979 !important; color: white; } -.status-failed { - background-color: #f44336; /* Rojo */ +.chip-success { + background-color: #46c446 !important; color: white; } -.status-pending { - background-color: #ff9800; /* Naranja */ +.chip-pending { + background-color: lightgrey !important; + color: black; +} + +.chip-in-progress { + background-color: #f5a623 !important; color: white; } - diff --git a/ogWebconsole/src/app/components/images/images.component.html b/ogWebconsole/src/app/components/images/images.component.html index 071d165..235fdb9 100644 --- a/ogWebconsole/src/app/components/images/images.component.html +++ b/ogWebconsole/src/app/components/images/images.component.html @@ -32,7 +32,12 @@ - + {{ getStatusLabel(image[column.columnDef]) }} @@ -54,7 +59,7 @@ menu - + diff --git a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html index c9ae5c5..19cc3ac 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html +++ b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html @@ -8,30 +8,14 @@
- - {{ 'selectUnitLabel' | translate }} - - {{ 'loadingUnitsOption' | translate }} - - {{ unit.name }} - - - - {{ 'requiredFieldError' | translate }} - - - - - {{ 'selectClassLabel' | translate }} - - - {{ 'noClassesOption' | translate }} - - - {{ child.name }} - - - +
+ + {{ 'selectClassLabel' | translate }} + + {{ unit.name }} + + +
@@ -49,7 +33,7 @@ -
-
- -
- - -
@@ -35,7 +22,7 @@ search {{ 'searchHint' | translate }} - + {{ 'searchDefaultLabel' | translate }} @@ -55,7 +42,8 @@
- + +
- +
{{ column.header }} @@ -78,7 +66,7 @@ - {{ image.name ? image.name.substring(0, 20) + '...' : '' }} + {{ image.name }} @@ -95,8 +83,8 @@ - {{ 'actionsColumnHeader' | translate }} + {{ 'actionsColumnHeader' | translate }} @@ -110,7 +98,7 @@ menu - + diff --git a/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.ts b/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.ts index 0015fb1..25125b5 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.ts +++ b/ogWebconsole/src/app/components/ogboot/pxe-images/pxe-images.component.ts @@ -44,11 +44,6 @@ export class PXEimagesComponent implements OnInit { header: 'Nombre de imagen', cell: (user: any) => `${user.name}` }, - { - columnDef: 'downloadUrl', - header: 'Url descarga', - cell: (user: any) => `${user.downloadUrl}` - }, { columnDef: 'isDefault', header: 'Imagen por defecto', @@ -56,7 +51,7 @@ export class PXEimagesComponent implements OnInit { }, { columnDef: 'installed', - header: 'Imagen instalada en ogBoot', + header: 'Imagen instalada', cell: (user: any) => `${user.installed}` }, { @@ -83,8 +78,11 @@ export class PXEimagesComponent implements OnInit { ) {} ngOnInit(): void { + this.loading = true; this.search(); this.loadAlert(); + this.syncOgBoot() + this.loading = false; } addImage(): void { @@ -99,15 +97,12 @@ export class PXEimagesComponent implements OnInit { } search(): void { - this.loading = true; this.dataService.getImages(this.filters).subscribe( data => { this.dataSource.data = data; - this.loading = false; }, error => { console.error('Error fetching og lives', error); - this.loading = false; } ); } @@ -250,7 +245,7 @@ export class PXEimagesComponent implements OnInit { syncOgBoot(): void { this.http.post(`${this.apiUrl}/sync`, {}) .subscribe(response => { - this.toastService.success('Sincronización completada'); + this.toastService.success('Sincronización con oGBoot exitosa'); this.search() }, error => { console.error('Error al sincronizar', error); @@ -261,7 +256,6 @@ export class PXEimagesComponent implements OnInit { iniciarTour(): void { this.joyrideService.startTour({ steps: [ - 'serverInfoStep', 'titleStep', 'addImageStep', 'searchNameStep', @@ -275,5 +269,5 @@ export class PXEimagesComponent implements OnInit { themeColor: '#3f51b5' }); } - + } diff --git a/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.css b/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.css deleted file mode 100644 index f994fc3..0000000 --- a/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.css +++ /dev/null @@ -1,17 +0,0 @@ -.loading-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100px; -} - -mat-form-field { - width: 100%; -} - -mat-dialog-actions { - display: flex; - justify-content: flex-end; -} - diff --git a/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.html b/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.html deleted file mode 100644 index d72e22f..0000000 --- a/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.html +++ /dev/null @@ -1,39 +0,0 @@ -

{{ 'addClientsTitle' | translate: { subnetName: data.subnetName } }}

- - - - - - - {{ client.name }} - - - - -
-

{{ 'selectedClientsTitle' | translate }}

-
    -
  • - {{ client.name }} - -
  • -
-
-
- - - - - diff --git a/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.spec.ts b/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.spec.ts deleted file mode 100644 index 0a4cd09..0000000 --- a/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AddClientsToPxeComponent } from './add-clients-to-pxe.component'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatOptionModule } from '@angular/material/core'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatIconModule } from '@angular/material/icon'; -import { MatInputModule } from '@angular/material/input'; -import { MatPaginatorModule } from '@angular/material/paginator'; -import { MatProgressSpinner, MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { MatSelectModule } from '@angular/material/select'; -import { MatTableModule } from '@angular/material/table'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { ToastrModule } from 'ngx-toastr'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; -import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatListModule } from '@angular/material/list'; -import { MatTabsModule } from '@angular/material/tabs'; -import { TranslateModule } from '@ngx-translate/core'; - -describe('AddClientsToPxeComponent', () => { - let component: AddClientsToPxeComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AddClientsToPxeComponent], - imports: [ - HttpClientTestingModule, - ToastrModule.forRoot(), - BrowserAnimationsModule, - MatDividerModule, - MatFormFieldModule, - MatInputModule, - MatIconModule, - MatButtonModule, - MatTableModule, - MatPaginatorModule, - MatTooltipModule, - FormsModule, - ReactiveFormsModule, - MatProgressSpinnerModule, - MatDialogModule, - MatSelectModule, - MatTabsModule, - MatAutocompleteModule, - MatListModule, - TranslateModule.forRoot() - ], - providers: [ - { provide: MatDialogRef, useValue: {} }, - { provide: MAT_DIALOG_DATA, useValue: {} } - ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(AddClientsToPxeComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.ts b/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.ts deleted file mode 100644 index 7a349d1..0000000 --- a/ogWebconsole/src/app/components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component.ts +++ /dev/null @@ -1,98 +0,0 @@ -import {Component, Inject} from '@angular/core'; -import {Observable, startWith} from "rxjs"; -import {FormControl} from "@angular/forms"; -import {HttpClient} from "@angular/common/http"; -import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; -import {ToastrService} from "ngx-toastr"; -import {map} from "rxjs/operators"; - -@Component({ - selector: 'app-add-clients-to-pxe', - templateUrl: './add-clients-to-pxe.component.html', - styleUrl: './add-clients-to-pxe.component.css' -}) -export class AddClientsToPxeComponent { - baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; - clients: any[] = []; - selectedClients: any[] = []; - loading: boolean = true; - filters: { [key: string]: string } = {}; - filteredClients!: Observable; - clientControl = new FormControl(); - - constructor( - private http: HttpClient, - public dialogRef: MatDialogRef, - private toastService: ToastrService, - @Inject(MAT_DIALOG_DATA) public data: { subnetUuid: string, subnetName: string } - ) {} - - ngOnInit(): void { - this.loading = true; - - this.loadClients(); - - this.filteredClients = this.clientControl.valueChanges.pipe( - startWith(''), - map(value => (typeof value === 'string' ? value : value?.name)), - map(name => (name ? this._filterClients(name) : this.clients.slice())) - ); - } - - loadClients() { - this.http.get( `${this.baseUrl}/clients?&page=1&itemsPerPage=10000&exists[template]=false`).subscribe( - response => { - this.clients = response['hydra:member']; - this.loading = false; - }, - error => { - console.error('Error fetching parent units:', error); - this.loading = false; - } - ); - } - - save() { - const postData = { - clients: this.selectedClients.map(client => client['@id']) - }; - - this.http.post(`${this.baseUrl}/pxe-templates/${this.data.subnetUuid}/add-clients`, postData).subscribe( - response => { - this.toastService.success('Clientes asignados correctamente'); - }, - error => { - this.toastService.error(error.error['hydra:description']); - } - ); - - this.dialogRef.close(this.selectedClients); - } - - close() { - this.dialogRef.close(); - } - - removeClient(client: any) { - const index = this.selectedClients.indexOf(client); - if (index >= 0) { - this.selectedClients.splice(index, 1); - } - } - - private _filterClients(name: string): any[] { - const filterValue = name.toLowerCase(); - return this.clients.filter(client => client.name.toLowerCase().includes(filterValue)); - } - - displayFnClient(client: any): string { - return client && client.name ? client.name : ''; - } - - onOptionClientSelected(client: any) { - if (!this.selectedClients.includes(client)) { - this.selectedClients.push(client); - } - this.clientControl.setValue(''); - } -} diff --git a/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.css b/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.css deleted file mode 100644 index 4e5aff4..0000000 --- a/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.css +++ /dev/null @@ -1,47 +0,0 @@ - -mat-dialog-actions { - margin-top: 20px; - display: flex; - justify-content: flex-end; -} - -button { - margin-left: 10px; -} - -.green-icon { - color: green; -} - -.red-icon { - color: red; -} - - -.spacing-container { - margin-top: 20px; - margin-bottom: 16px; -} - -.list-item-content { - display: flex; - align-items: flex-start; /* Alinea el contenido al inicio */ - justify-content: space-between; /* Espacio entre los textos y los íconos */ - width: 100%; /* Asegúrate de que el contenido ocupe todo el ancho */ -} - -.text-content { - flex-grow: 1; - margin-right: 16px; - margin-left: 10px; -} - -.icon-container { - display: flex; - align-items: center; -} - -.right-icon { - margin-left: 8px; - cursor: pointer; -} diff --git a/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.html b/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.html deleted file mode 100644 index 7a75d2e..0000000 --- a/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.html +++ /dev/null @@ -1,32 +0,0 @@ -

{{ 'manageClientsTitle' | translate }}

- - - - -
- - computer - -
-
{{ client.name }}
-
{{ client.mac }}
-
-
- - - -
-
-
-
-
-
- - - diff --git a/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.spec.ts b/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.spec.ts deleted file mode 100644 index d1b206f..0000000 --- a/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ClientsComponent } from './clients.component'; -import { MatInputModule } from '@angular/material/input'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatOptionModule } from '@angular/material/core'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatIconModule } from '@angular/material/icon'; -import { MatPaginatorModule } from '@angular/material/paginator'; -import { MatProgressSpinner, MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { MatSelectModule } from '@angular/material/select'; -import { MatTableModule } from '@angular/material/table'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { ToastrModule } from 'ngx-toastr'; -import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; -import { MatListModule } from '@angular/material/list'; -import { MatTabsModule } from '@angular/material/tabs'; -import { TranslateModule } from '@ngx-translate/core'; - -describe('ClientsComponent', () => { - let component: ClientsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ClientsComponent], - imports: [ - HttpClientTestingModule, - ToastrModule.forRoot(), - BrowserAnimationsModule, - MatDividerModule, - MatFormFieldModule, - MatInputModule, - MatIconModule, - MatButtonModule, - MatTableModule, - MatPaginatorModule, - MatTooltipModule, - FormsModule, - ReactiveFormsModule, - MatProgressSpinnerModule, - MatDialogModule, - MatSelectModule, - MatTabsModule, - MatAutocompleteModule, - MatListModule, - TranslateModule.forRoot() - ], - providers: [ - { provide: MatDialogRef, useValue: {} }, - { provide: MAT_DIALOG_DATA, useValue: {data: {id: 123}} } - - ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ClientsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.ts b/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.ts deleted file mode 100644 index c479610..0000000 --- a/ogWebconsole/src/app/components/ogboot/pxe/clients/clients.component.ts +++ /dev/null @@ -1,107 +0,0 @@ -import {Component, Inject} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog"; -import {HttpClient} from "@angular/common/http"; -import {ToastrService} from "ngx-toastr"; -import {DeleteModalComponent} from "../../../../shared/delete_modal/delete-modal/delete-modal.component"; -import {Observable} from "rxjs"; -import { - ServerInfoDialogComponent -} from "../../../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component"; - -@Component({ - selector: 'app-clients', - templateUrl: './clients.component.html', - styleUrl: './clients.component.css' -}) -export class ClientsComponent { - baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; - templateForm!: FormGroup; - clients: any[] = []; - alertMessage: string | null = null; - - constructor( - public dialogRef: MatDialogRef, - public dialog: MatDialog, - private http: HttpClient, - private toastService: ToastrService, - @Inject(MAT_DIALOG_DATA) public data: any - ) {} - - ngOnInit() { - this.getPxeClients() - } - - getPxeClients(): void { - this.http.get(`${this.baseUrl}/clients?template.id=${this.data.data.id}`).subscribe({ - next: data => { - this.clients = data['hydra:member'] - }, - error: error => { - console.error('Error al obtener los clientes PXE:', error); - } - }); - } - - addClientToTemplate(client: any): void { - const postData = { - client: client['@id'] - }; - - this.http.post(`${this.baseUrl}/pxe-templates/${this.data.data.uuid}/sync-client`, postData).subscribe( - response => { - this.toastService.success('Clientes asignados correctamente'); - this.getPxeClients() - }, - error => { - this.toastService.error(error.error['hydra:description']); - } - ); - } - - loadAlert(client: any): Observable { - return this.http.post(`${this.baseUrl}/clients/server/${client.uuid}/get-pxe`, {}); - } - - showInfo(client:any) { - this.loadAlert(client).subscribe( - response => { - this.alertMessage = response.message; - - this.dialog.open(ServerInfoDialogComponent, { - width: '600px', - data: { - message: this.alertMessage - } - }); - }, - error => { - console.error('Error al cargar la información del alert', error); - } - ); - } - - deleteClient(client: any): void { - const dialogRef = this.dialog.open(DeleteModalComponent, { - width: '300px', - data: { name: client.name } - }); - - dialogRef.afterClosed().subscribe(result => { - if (result) { - this.http.post(`${this.baseUrl}/pxe-templates/${this.data.data.uuid}/delete-client`, { client: client['@id'] }).subscribe({ - next: () => { - this.toastService.success('Cliente eliminado exitosamente'); - this.dialogRef.close(); - }, - error: (error) => { - this.toastService.error(error.error['hydra:description']); - } - }); - }}) - } - - onCancel(): void { - this.dialogRef.close(false); - } -} diff --git a/ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.ts b/ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.ts index 14d208a..2cded87 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.ts +++ b/ogWebconsole/src/app/components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component.ts @@ -34,7 +34,7 @@ set ISODIR ogLive kernel http://__SERVERIP__/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs} initrd http://__SERVERIP__/tftpboot/\${ISODIR}/oginitrd.img boot`, - + disco: `#!ipxe iseq \${platform} efi && goto uefi_boot || goto bios_boot @@ -104,8 +104,7 @@ exit` this.dialogRef.close(true); }, error: error => { - this.toastService.error('Error al crear la plantilla PXE'); - this.dialogRef.close(false); + this.toastService.error(error.error['hydra:description']); } }); } diff --git a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.css b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.css index b9ee046..b478ce5 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.css +++ b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.css @@ -34,6 +34,7 @@ table { display: flex; justify-content: space-between; align-items: center; + height: 100px; padding: 10px; } @@ -41,27 +42,21 @@ table { box-shadow: 0px 0px 0px rgba(0,0,0,0.2); } +.template-button-row { + display: flex; + justify-content: flex-start; + margin-top: 16px; + gap: 10px; +} + +mat-spinner { + margin: 0 auto; + align-self: center; +} + .paginator-container { display: flex; justify-content: end; margin-bottom: 30px; } -.example-headers-align .mat-expansion-panel-header-description { - justify-content: space-between; - align-items: center; -} - -.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field { - margin-left: 8px; -} - -.example-button-row { - display: table-cell; - max-width: 600px; -} - -.example-button-row .mat-mdc-button-base { - margin: 8px 8px 8px 0; -} - diff --git a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.html b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.html index f2fb59b..303362e 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.html +++ b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.html @@ -1,23 +1,10 @@ - - - - {{ 'serverInfoTitle' | translate }} - -
- -
-
- -
-
-
-

{{ 'adminPxeTitle' | translate }}

-
+
+
@@ -41,7 +28,8 @@
- + +
- + diff --git a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.ts b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.ts index 40dc2c1..c0ff7a7 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.ts +++ b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.ts @@ -6,21 +6,12 @@ import { MatTableDataSource } from '@angular/material/table'; import { PageEvent } from '@angular/material/paginator'; import { ToastrService } from 'ngx-toastr'; import { DatePipe } from '@angular/common'; -import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component'; import { DataService } from './data.service'; -import { - ShowOrganizationalUnitComponent -} from "../../groups/shared/organizational-units/show-organizational-unit/show-organizational-unit.component"; import {ShowTemplateContentComponent} from "./show-template-content/show-template-content.component"; import {ServerInfoDialogComponent} from "../../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component"; -import { - AddClientsToSubnetComponent -} from "../../ogdhcp/og-dhcp-subnets/add-clients-to-subnet/add-clients-to-subnet.component"; -import {Subnet} from "../../ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component"; -import {AddClientsToPxeComponent} from "./add-clients-to-pxe/add-clients-to-pxe.component"; import {Observable} from "rxjs"; -import {ClientsComponent} from "./clients/clients.component"; import { JoyrideService } from 'ngx-joyride'; +import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/delete-modal.component"; @Component({ selector: 'app-pxe', @@ -78,19 +69,20 @@ export class PxeComponent { ) { } ngOnInit(): void { + this.loading = true; this.search(); + this.loadAlert() + this.syncTemplates() + this.loading = false; } search(): void { - this.loading = true; this.dataService.getPxeTemplates(this.filters).subscribe( data => { this.dataSource.data = data; - this.loading = false; }, error => { console.error('Error fetching pxe templates', error); - this.loading = false; } ); } @@ -105,6 +97,7 @@ export class PxeComponent { }); } + editPxeTemplate(template: any) { const dialogRef = this.dialog.open(CreatePxeTemplateComponent, { data: template, // Pasa los datos del template para edición @@ -118,40 +111,26 @@ export class PxeComponent { toggleAction(image: any, action: string): void { switch (action) { - case 'create': - this.http.post(`${this.apiUrl}/server/${image.uuid}/post`, {}).subscribe({ - next: (response) => { - this.search(); - // @ts-ignore - this.toastService.success(response.success); - }, - error: (error) => { - this.toastService.error(error.error.error); - } - }); - break; - case 'sync': - this.http.get(`${this.apiUrl}/server/${image.uuid}/get`, {}).subscribe({ - next: (response) => { - this.search(); - // @ts-ignore - this.toastService.success(response.success); - }, - error: (error) => { - this.toastService.error(error.error.error); - } - }); - break; case 'delete': - this.http.post(`${this.apiUrl}/server/${image.uuid}/delete`, {}).subscribe({ - next: () => { - this.toastService.success('Plantilla eliminada correctamente'); - this.search(); - }, - error: (error) => { - this.toastService.error(error.error.error); - } + const dialogRef = this.dialog.open(DeleteModalComponent, { + width: '300px', + data: { name: image.name } }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.http.post(`${this.apiUrl}/server/${image.uuid}/delete`, {}).subscribe({ + next: () => { + this.toastService.success('Plantilla eliminada exitosamente'); + this.search(); + }, + error: (error) => { + console.error('Error al eliminar la subred', error); + this.toastService.error(error.error['hydra:description']); + } + }); + } + }) break; default: console.error('Acción no soportada:', action); @@ -164,11 +143,6 @@ export class PxeComponent { const dialogRef = this.dialog.open(ShowTemplateContentComponent, { data: { data }, width: '700px'}); } - editClients(event: MouseEvent, data: any): void { - event.stopPropagation(); - const dialogRef = this.dialog.open(ClientsComponent, { data: { data }, width: '700px'}); - } - syncTemplates() { this.http.post(`${this.apiUrl}/sync`, {}) .subscribe(response => { @@ -180,17 +154,6 @@ export class PxeComponent { }); } - addClientsToPxe(template: Subnet) { - const dialogRef = this.dialog.open(AddClientsToPxeComponent, { - width: '600px', - data: { subnetUuid: template.uuid, subnetName: template.name } - }); - - dialogRef.afterClosed().subscribe(result => { - this.search(); - }); - } - applyFilter() { this.http.get(`${this.apiUrl}?page=${this.page}&itemsPerPage=${this.itemsPerPage}`).subscribe({ next: (response) => { @@ -207,7 +170,7 @@ export class PxeComponent { return this.http.get(`${this.apiUrl}/server/get-collection`); } - openSubnetInfoDialog() { + openInfoDialog() { this.loadAlert().subscribe( response => { this.alertMessage = response.message; @@ -248,5 +211,5 @@ export class PxeComponent { themeColor: '#3f51b5' }); } - + } diff --git a/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/create-subnet/create-subnet.component.html b/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/create-subnet/create-subnet.component.html index 6892323..68f9b0b 100644 --- a/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/create-subnet/create-subnet.component.html +++ b/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/create-subnet/create-subnet.component.html @@ -31,6 +31,10 @@ Boot File Name + + Router + + @@ -60,5 +64,5 @@ - + diff --git a/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/create-subnet/create-subnet.component.ts b/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/create-subnet/create-subnet.component.ts index a0d5ef4..ada8414 100644 --- a/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/create-subnet/create-subnet.component.ts +++ b/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/create-subnet/create-subnet.component.ts @@ -17,6 +17,7 @@ export class CreateSubnetComponent implements OnInit { ipAddress: string = ''; nextServer: string = ''; bootFileName: string = ''; + router: string = ''; syncronized: boolean = false; serverId: number = 0; clients: any[] = []; @@ -44,6 +45,7 @@ export class CreateSubnetComponent implements OnInit { this.ipAddress = this.data.ipAddress; this.nextServer = this.data.nextServer; this.bootFileName = this.data.bootFileName; + this.router = this.data.router; this.syncronized = this.data.syncronized; this.serverId = this.data.serverId; this.clients = this.data.clients @@ -53,13 +55,14 @@ export class CreateSubnetComponent implements OnInit { this.dialogRef.close(); } - addNetworkConfig(): void { + save(): void { const payload = { name: this.name, netmask: this.netmask, ipAddress: this.ipAddress, - nextServer: this.nextServer || null, - bootFileName: this.bootFileName || null + router: this.router || null, + nextServer: this.nextServer || null, + bootFileName: this.bootFileName || null }; if (!this.data){ diff --git a/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component.html b/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component.html index 903ebd5..1338179 100644 --- a/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component.html +++ b/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component.html @@ -38,7 +38,6 @@ -
{{ column.header }} @@ -57,26 +45,17 @@ - {{ 'actionsColumn' | translate }} + {{ 'actionsColumn' | translate }} - - - - - - - -
diff --git a/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component.ts b/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component.ts index dfec859..6305bda 100644 --- a/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component.ts +++ b/ogWebconsole/src/app/components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component.ts @@ -18,6 +18,7 @@ export interface Subnet { netmask: string; ipAddress: string; nextServer: string; + router: string; bootFileName: string; synchronized: boolean; serverId: number; diff --git a/ogWebconsole/src/app/shared/constants/filesystem-types.ts b/ogWebconsole/src/app/shared/constants/filesystem-types.ts new file mode 100644 index 0000000..dfbcc56 --- /dev/null +++ b/ogWebconsole/src/app/shared/constants/filesystem-types.ts @@ -0,0 +1,26 @@ +export const FILESYSTEM_TYPES = [ + { id: 1, name: 'EMPTY', description: 'EMPTY', active: 0 }, + { id: 2, name: 'CACHE', description: 'CACHE', active: 0 }, + { id: 3, name: 'BTRFS', description: 'BTRFS', active: 0 }, + { id: 4, name: 'EXT2', description: 'EXT2', active: 0 }, + { id: 5, name: 'EXT3', description: 'EXT3', active: 0 }, + { id: 6, name: 'EXT4', description: 'EXT4', active: 0 }, + { id: 7, name: 'FAT12', description: 'FAT12', active: 0 }, + { id: 8, name: 'FAT16', description: 'FAT16', active: 0 }, + { id: 9, name: 'FAT32', description: 'FAT32', active: 0 }, + { id: 10, name: 'HFS', description: 'HFS', active: 0 }, + { id: 11, name: 'HFSPLUS', description: 'HFSPLUS', active: 0 }, + { id: 12, name: 'JFS', description: 'JFS', active: 0 }, + { id: 13, name: 'NTFS', description: 'NTFS', active: 0 }, + { id: 14, name: 'REISERFS', description: 'REISERFS', active: 0 }, + { id: 15, name: 'REISER4', description: 'REISER4', active: 0 }, + { id: 16, name: 'UFS', description: 'UFS', active: 0 }, + { id: 17, name: 'XFS', description: 'XFS', active: 0 }, + { id: 18, name: 'EXFAT', description: 'EXFAT', active: 0 }, + { id: 19, name: 'LINUX-SWAP', description: 'LINUX-SWAP', active: 0 }, + { id: 20, name: 'F2FS', description: 'F2FS', active: 0 }, + { id: 21, name: 'NILFS2', description: 'NILFS2', active: 0 }, + { id: 22, name: '0', description: '', active: 0 }, + { id: 23, name: 'LINUX-LVM', description: '', active: 0 }, + { id: 24, name: 'ISO9660', description: '', active: 0 }, +]; diff --git a/ogWebconsole/src/app/shared/constants/partition-types.ts b/ogWebconsole/src/app/shared/constants/partition-types.ts new file mode 100644 index 0000000..1b900ac --- /dev/null +++ b/ogWebconsole/src/app/shared/constants/partition-types.ts @@ -0,0 +1,72 @@ +export const PARTITION_TYPES = [ + { code: 0, name: 'EMPTY', active: false }, + { code: 1, name: 'FAT12', active: true }, + { code: 5, name: 'EXTENDED', active: false }, + { code: 6, name: 'FAT16', active: true }, + { code: 7, name: 'NTFS', active: true }, + { code: 11, name: 'FAT32', active: true }, + { code: 17, name: 'HFAT12', active: true }, + { code: 22, name: 'HFAT16', active: true }, + { code: 23, name: 'HNTFS', active: true }, + { code: 27, name: 'HFAT32', active: true }, + { code: 130, name: 'LINUX-SWAP', active: false }, + { code: 131, name: 'LINUX', active: true }, + { code: 142, name: 'LINUX-LVM', active: true }, + { code: 165, name: 'FREEBSD', active: true }, + { code: 166, name: 'OPENBSD', active: true }, + { code: 169, name: 'NETBSD', active: true }, + { code: 175, name: 'HFS', active: true }, + { code: 190, name: 'SOLARIS-BOOT', active: true }, + { code: 191, name: 'SOLARIS', active: true }, + { code: 202, name: 'CACHE', active: false }, + { code: 218, name: 'DATA', active: true }, + { code: 238, name: 'GPT', active: false }, + { code: 239, name: 'EFI', active: true }, + { code: 251, name: 'VMFS', active: true }, + { code: 253, name: 'LINUX-RAID', active: true }, + { code: 1792, name: 'WINDOWS', active: true }, + { code: 3073, name: 'WIN-RESERV', active: true }, + { code: 9984, name: 'WIN-RECOV', active: true }, + { code: 32512, name: 'CHROMEOS-KRN', active: true }, + { code: 32513, name: 'CHROMEOS', active: true }, + { code: 32514, name: 'CHROMEOS-RESERV', active: true }, + { code: 33280, name: 'LINUX-SWAP', active: false }, + { code: 33536, name: 'LINUX', active: true }, + { code: 33537, name: 'LINUX-RESERV', active: true }, + { code: 33538, name: 'LINUX', active: true }, + { code: 36352, name: 'LINUX-LVM', active: true }, + { code: 42240, name: 'FREEBSD-DISK', active: false }, + { code: 42241, name: 'FREEBSD-BOOT', active: true }, + { code: 42242, name: 'FREEBSD-SWAP', active: false }, + { code: 42243, name: 'FREEBSD', active: true }, + { code: 42244, name: 'FREEBSD', active: true }, + { code: 43265, name: 'NETBSD-SWAP', active: false }, + { code: 43266, name: 'NETBSD', active: true }, + { code: 43267, name: 'NETBSD', active: true }, + { code: 43268, name: 'NETBSD', active: true }, + { code: 43269, name: 'NETBSD', active: true }, + { code: 43270, name: 'NETBSD-RAID', active: true }, + { code: 43776, name: 'HFS-BOOT', active: true }, + { code: 44800, name: 'HFS', active: true }, + { code: 44801, name: 'HFS-RAID', active: true }, + { code: 44802, name: 'HFS-RAID', active: true }, + { code: 48640, name: 'SOLARIS-BOOT', active: true }, + { code: 48896, name: 'SOLARIS', active: true }, + { code: 48897, name: 'SOLARIS', active: true }, + { code: 48898, name: 'SOLARIS-SWAP', active: false }, + { code: 48899, name: 'SOLARIS-DISK', active: true }, + { code: 48900, name: 'SOLARIS', active: true }, + { code: 48901, name: 'SOLARIS', active: true }, + { code: 51712, name: 'CACHE', active: false }, + { code: 61184, name: 'EFI', active: true }, + { code: 61185, name: 'MBR', active: false }, + { code: 61186, name: 'BIOS-BOOT', active: false }, + { code: 64256, name: 'VMFS', active: true }, + { code: 64257, name: 'VMFS-RESERV', active: true }, + { code: 64258, name: 'VMFS-KRN', active: true }, + { code: 64768, name: 'LINUX-RAID', active: true }, + { code: 65535, name: 'UNKNOWN', active: true }, + { code: 65536, name: 'LVM-LV', active: true }, + { code: 65552, name: 'ZFS-VOL', active: true }, + { code: 39, name: 'HNTFS-WINRE', active: true }, +]; diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index 0cc49e6..f45a04f 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -6,7 +6,7 @@ "loginError": "Login error: {{error}}", "labelUsers": "Users", "labelRoles": "Roles", - "adminImagesTitle": "Manage clients", + "adminImagesTitle": "Manage images", "addUser": "Add users", "searchLabel": "Search image name", "searchPlaceholder": "Search", @@ -399,6 +399,7 @@ "ogLive": "ogLive", "TOOLTIP_PXE_IMAGES": "View available PXE boot images", "pxeTemplates": "PXE Templates", + "pxeTemplate" : "Plantilla", "TOOLTIP_PXE_TEMPLATES": "Manage PXE boot templates", "pxeBootFiles": "PXE Boot Files", "TOOLTIP_PXE_BOOT_FILES": "Configure PXE boot files", @@ -419,5 +420,30 @@ "menus": "Menus", "TOOLTIP_MENUS": "Menu management (option disabled)", "search": "Search", - "TOOLTIP_SEARCH": "Search function (option disabled)" + "TOOLTIP_SEARCH": "Search function (option disabled)", + "detailsOf": "Details of", + "editUnitMenu": "Edit", + "addInternalUnitMenu": "Add", + "addClientMenu": "Add client", + "filters": "Filters", + "searchClient": "Search client", + "searchTree": "Search in tree", + "filterByType": "Filter by type", + "all": "All", + "classroomsGroup": "Classroom groups", + "classrooms": "Classrooms", + "computerGroups": "PC groups", + "executeCommand": "Execute command", + "roomMap": "Classroom map", + "addOrganizationalUnit": "Add organizational unit", + "edit": "Edit", + "delete": "Delete", + "clients": "Clients", + "sync": "Sync", + "viewDetails": "View details", + "name": "Name", + "maintenance": "Maintenance", + "subnet": "Subnet", + "parent": "Parent", + "adminUsersTitle": "Manage users" } diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 784d4b6..02c57ca 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -6,7 +6,7 @@ "loginError": "Login error: {{error}}", "labelUsers": "Usuarios", "labelRoles": "Roles", - "adminImagesTitle": "Administrar clientes", + "adminImagesTitle": "Administrar OgLives", "addUser": "Añadir usuarios", "searchLabel": "Buscar nombre de imagen", "searchPlaceholder": "Búsqueda", @@ -233,6 +233,7 @@ "editClientDialogTitle": "Editar Cliente", "organizationalUnitLabel": "Padre", "ogLiveLabel": "OgLive", + "repositoryLabel": "Repositorio", "serialNumberLabel": "Número de Serie", "netifaceLabel": "Interfaz de red", "netDriverLabel": "Controlador de red", @@ -400,6 +401,7 @@ "ogLive": "ogLive", "TOOLTIP_PXE_IMAGES": "Ver imágenes disponibles para arranque PXE", "pxeTemplates": "Plantillas PXE", + "pxeTemplate": "Plantilla", "TOOLTIP_PXE_TEMPLATES": "Gestionar plantillas de arranque PXE", "pxeBootFiles": "Arranque PXE", "TOOLTIP_PXE_BOOT_FILES": "Configurar archivos de arranque PXE", @@ -420,5 +422,30 @@ "menus": "Menús", "TOOLTIP_MENUS": "Gestión de menús (opción deshabilitada)", "search": "Buscar", - "TOOLTIP_SEARCH": "Función de búsqueda (opción deshabilitada)" + "TOOLTIP_SEARCH": "Función de búsqueda (opción deshabilitada)", + "detailsOf": "Detalles de", + "editUnitMenu": "Editar", + "addInternalUnitMenu": "Añadir", + "addClientMenu": "Añadir cliente", + "filters": "Filtros", + "searchClient": "Buscar cliente", + "searchTree": "Buscar en árbol", + "filterByType": "Filtrar por tipo", + "all": "Todos", + "classroomsGroup": "Grupos de aulas", + "classrooms": "Aulas", + "computerGroups": "Grupos de PCs", + "executeCommand": "Ejecutar comando", + "roomMap": "Plano de aula", + "addOrganizationalUnit": "Añadir unidad organizativa", + "edit": "Editar", + "delete": "Eliminar", + "clients": "Clientes", + "sync": "Sincronizar", + "viewDetails": "Ver detalles", + "name": "Nombre", + "maintenance": "Mantenimiento", + "subnet": "Subred", + "parent": "Padre", + "adminUsersTitle": "Administrar usuarios" }