From c2b1ce28eb29f136b65f0cd41bcda2e4eb5f1888 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 23 Sep 2025 08:40:30 +0200 Subject: [PATCH 1/2] refs #2836. Added new HW componnetes --- ogWebconsole/src/app/app-routing.module.ts | 6 + ogWebconsole/src/app/app.module.ts | 14 +- .../execute-command.component.ts | 4 +- .../hardware-collection-modal.component.css | 57 +++++ .../hardware-collection-modal.component.html | 31 +++ ...ardware-collection-modal.component.spec.ts | 27 +++ .../hardware-collection-modal.component.ts | 64 ++++++ .../hardware-profile.component.css | 80 +++++++ .../hardware-profile.component.html | 88 ++++++++ .../hardware-profile.component.spec.ts | 23 ++ .../hardware-profile.component.ts | 206 ++++++++++++++++++ .../hardware-profile.service.ts | 28 +++ .../hardware-type/hardware-type.component.css | 60 +++++ .../hardware-type.component.html | 69 ++++++ .../hardware-type.component.spec.ts | 23 ++ .../hardware-type/hardware-type.component.ts | 131 +++++++++++ .../show-details/show-details.component.css | 0 .../show-details/show-details.component.html | 1 + .../show-details.component.spec.ts | 23 ++ .../show-details/show-details.component.ts | 10 + .../create-hardware.component.css | 0 .../create-hardware.component.html | 1 + .../create-hardware.component.spec.ts | 23 ++ .../create-hardware.component.ts | 10 + .../hardware/hardware.component.css | 60 +++++ .../hardware/hardware.component.html | 69 ++++++ .../hardware/hardware.component.spec.ts | 23 ++ .../components/hardware/hardware.component.ts | 127 +++++++++++ .../app/layout/sidebar/sidebar.component.html | 36 ++- .../app/layout/sidebar/sidebar.component.ts | 7 +- ogWebconsole/src/locale/en.json | 13 +- ogWebconsole/src/locale/es.json | 13 +- 32 files changed, 1319 insertions(+), 8 deletions(-) create mode 100644 ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.css create mode 100644 ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.html create mode 100644 ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.spec.ts create mode 100644 ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.ts create mode 100644 ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.css create mode 100644 ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.html create mode 100644 ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.spec.ts create mode 100644 ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.ts create mode 100644 ogWebconsole/src/app/components/hardware-profile/hardware-profile.service.ts create mode 100644 ogWebconsole/src/app/components/hardware-type/hardware-type.component.css create mode 100644 ogWebconsole/src/app/components/hardware-type/hardware-type.component.html create mode 100644 ogWebconsole/src/app/components/hardware-type/hardware-type.component.spec.ts create mode 100644 ogWebconsole/src/app/components/hardware-type/hardware-type.component.ts create mode 100644 ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.css create mode 100644 ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.html create mode 100644 ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.spec.ts create mode 100644 ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.ts create mode 100644 ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.css create mode 100644 ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.html create mode 100644 ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.spec.ts create mode 100644 ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.ts create mode 100644 ogWebconsole/src/app/components/hardware/hardware.component.css create mode 100644 ogWebconsole/src/app/components/hardware/hardware.component.html create mode 100644 ogWebconsole/src/app/components/hardware/hardware.component.spec.ts create mode 100644 ogWebconsole/src/app/components/hardware/hardware.component.ts diff --git a/ogWebconsole/src/app/app-routing.module.ts b/ogWebconsole/src/app/app-routing.module.ts index 72f410d..0f1abee 100644 --- a/ogWebconsole/src/app/app-routing.module.ts +++ b/ogWebconsole/src/app/app-routing.module.ts @@ -41,6 +41,9 @@ import { } from "./components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component"; import { roleGuard } from './guards/role.guard'; import { LogoutGuard } from './guards/logout.guard'; +import { HardwareComponent } from './components/hardware/hardware.component'; +import { HardwareProfileComponent } from './components/hardware-profile/hardware-profile.component'; +import { HardwareTypeComponent } from './components/hardware-type/hardware-type.component'; const routes: Routes = [ { path: '', redirectTo: 'auth/login', pathMatch: 'full' }, { @@ -71,6 +74,9 @@ const routes: Routes = [ { path: 'software-profiles', component: SoftwareProfileComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, { path: 'operative-systems', component: OperativeSystemComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, { path: 'menus', component: MenusComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'hardware', component: HardwareComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'hardware-profiles', component: HardwareProfileComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'hardware-types', component: HardwareTypeComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, ], }, { diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index ef79fbe..1df101c 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -164,6 +164,12 @@ import { CreateTagModalComponent } from './components/repositories/show-git-imag import { CreateBranchModalComponent } from './components/repositories/show-git-images/create-branch-modal/create-branch-modal.component'; import { ClientLogsModalComponent } from './components/groups/shared/client-logs-modal/client-logs-modal.component'; import { BackupRepositoryModalComponent } from './components/repositories/show-git-images/backup-repository-modal/backup-repository-modal.component'; +import { HardwareComponent } from './components/hardware/hardware.component'; +import { CreateHardwareComponent } from './components/hardware/create-hardware/create-hardware.component'; +import { HardwareProfileComponent } from './components/hardware-profile/hardware-profile.component'; +import { HardwareCollectionModalComponent } from './components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component'; +import { HardwareTypeComponent } from './components/hardware-type/hardware-type.component'; +import { ShowDetailsComponent } from './components/hardware-type/show-details/show-details.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); @@ -284,7 +290,13 @@ registerLocaleData(localeEs, 'es-ES'); CreateBranchModalComponent, ClientLogsModalComponent, SafePipe, - BackupRepositoryModalComponent + BackupRepositoryModalComponent, + HardwareComponent, + CreateHardwareComponent, + HardwareProfileComponent, + HardwareCollectionModalComponent, + HardwareTypeComponent, + ShowDetailsComponent ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts index 27ace1b..e8bf0bf 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts @@ -35,7 +35,7 @@ export class ExecuteCommandComponent implements OnInit { { translationKey: 'executeCommands.deleteImageCache', slug: 'remove-cache-image', disabled: false }, { translationKey: 'executeCommands.partition', slug: 'partition', disabled: false }, { translationKey: 'executeCommands.softwareInventory', slug: 'software-inventory', disabled: false }, - { translationKey: 'executeCommands.hardwareInventory', slug: 'hardware-inventory', disabled: true }, + { translationKey: 'executeCommands.hardwareInventory', slug: 'hardware-inventory', disabled: false }, { translationKey: 'executeCommands.runScript', slug: 'run-script', disabled: false }, ]; @@ -112,7 +112,7 @@ export class ExecuteCommandComponent implements OnInit { if (states[0] === 'off' || states[0] === 'disconnected') { command.disabled = !['power-on', 'create-image'].includes(command.slug); } else { - command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'remove-cache-image', 'partition', 'run-script', 'software-inventory'].includes(command.slug); + command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'remove-cache-image', 'partition', 'run-script', 'software-inventory', 'hardware-inventory'].includes(command.slug); } } else { if (command.slug === 'software-inventory') { diff --git a/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.css b/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.css new file mode 100644 index 0000000..594d6b1 --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.css @@ -0,0 +1,57 @@ +.dialog-content { + min-width: 600px; + max-width: 800px; + max-height: 500px; + overflow: auto; + padding: 24px; +} + +.no-data-message { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 20px; + color: #666; + text-align: center; +} + +.no-data-message mat-icon { + font-size: 48px; + width: 48px; + height: 48px; + margin-bottom: 16px; + color: #999; +} + +.hardware-table-container { + width: 100%; +} + +.hardware-table-container table { + width: 100%; +} + +.action-container { + display: flex; + justify-content: flex-end; + padding: 24px; +} + +.action-container button { + display: flex; + align-items: center; + gap: 8px; +} + +h2[mat-dialog-title] { + display: flex; + align-items: center; + gap: 12px; + margin: 0; + padding: 24px 24px 16px 24px; +} + +h2[mat-dialog-title] mat-icon { + color: #3f51b5; +} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.html b/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.html new file mode 100644 index 0000000..56401a9 --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.html @@ -0,0 +1,31 @@ +

+ {{ profileName }} +

+ + +
+ info +

No hay hardware asociado a este perfil

+
+ +
+ + + + + + + + +
{{ column.header }} + {{ column.cell(hardware) }} +
+
+
+ + + + \ No newline at end of file diff --git a/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.spec.ts b/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.spec.ts new file mode 100644 index 0000000..7fc9f49 --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { HardwareCollectionModalComponent } from './hardware-collection-modal.component'; + +describe('HardwareCollectionModalComponent', () => { + let component: HardwareCollectionModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [HardwareCollectionModalComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HardwareCollectionModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.ts b/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.ts new file mode 100644 index 0000000..00984da --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component.ts @@ -0,0 +1,64 @@ +import { Component, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatTableDataSource } from '@angular/material/table'; + +@Component({ + selector: 'app-hardware-collection-modal', + templateUrl: './hardware-collection-modal.component.html', + styleUrl: './hardware-collection-modal.component.css' +}) +export class HardwareCollectionModalComponent { + dataSource = new MatTableDataSource(); + profileName: string = ''; + + columns = [ + { + columnDef: 'id', + header: 'ID', + cell: (hardware: any) => hardware.id || hardware['@id']?.split('/').pop() || 'N/A' + }, + { + columnDef: 'name', + header: 'Nombre', + cell: (hardware: any) => hardware.name || 'N/A' + }, + { + columnDef: 'type', + header: 'Tipo', + cell: (hardware: any) => hardware.type || 'N/A' + }, + { + columnDef: 'description', + header: 'Descripción', + cell: (hardware: any) => hardware.description || 'N/A' + } + ]; + + displayedColumns = this.columns.map(column => column.columnDef); + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any + ) { + if (data) { + this.profileName = data.profileName || 'Perfil de Hardware'; + + // Si se pasa directamente la colección de hardware + if (data.hardwareCollection && Array.isArray(data.hardwareCollection)) { + this.dataSource.data = data.hardwareCollection; + } + // Si se pasa el perfil completo + else if (data.profile && data.profile.hardwareCollection) { + this.dataSource.data = data.profile.hardwareCollection; + } + // Si no hay datos + else { + this.dataSource.data = []; + } + } + } + + onClose(): void { + this.dialogRef.close(); + } +} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.css b/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.css new file mode 100644 index 0000000..cd19596 --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.css @@ -0,0 +1,80 @@ +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 10px; + border-bottom: 1px solid #ddd; + } + + .header-container-title { + flex-grow: 1; + text-align: left; + margin-left: 1em; + } + + .calendar-button-row { + display: flex; + gap: 15px; + } + + .lists-container { + padding: 16px; + } + + .card.unidad-card { + height: 100%; + box-sizing: border-box; + } + + table { + width: 100%; + } + + .search-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + margin: 1.5rem 0rem 1.5rem 0rem; + box-sizing: border-box; + } + + .search-string { + flex: 2; + padding: 5px; + } + + .search-boolean { + flex: 1; + padding: 5px; + } + + .mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); + } + + .paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} + +.client-info { + display: flex; + flex-direction: column; + +.client-name { + font-weight: 500; + color: #333; +} + +.client-details { + display: flex; + flex-direction: column; + font-size: 0.75em; + color: #666; +} + +.client-ip, .client-mac { + font-family: monospace; +} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.html b/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.html new file mode 100644 index 0000000..6672eff --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.html @@ -0,0 +1,88 @@ +
+ +
+

{{ 'manageHardwareProfiles' | translate }}

+
+
+ +
+ + Buscar nombre de hardware + + search + Pulsar 'enter' para buscar + + + + + +
+ {{ client.name }} + {{ client.ip }} — {{ client.mac }} +
+
+
+ + {{ 'enterClientName' | translate }} +
+
+ + + + + + + + + + + + + +
{{ column.header }} + + + {{ image[column.columnDef] ? 'check_circle' : 'cancel' }} + + + + +
+
{{ column.cell(image).name }}
+
+ {{ column.cell(image).ip }} +
+
+ + N/A + +
+ + + {{ column.cell(image) }} + +
Acciones + + +
+
+ + +
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.spec.ts b/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.spec.ts new file mode 100644 index 0000000..fc6d569 --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HardwareProfileComponent } from './hardware-profile.component'; + +describe('HardwareProfileComponent', () => { + let component: HardwareProfileComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [HardwareProfileComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HardwareProfileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.ts b/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.ts new file mode 100644 index 0000000..f16f0dd --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-profile/hardware-profile.component.ts @@ -0,0 +1,206 @@ +import { Component, signal } from '@angular/core'; +import { MatTableDataSource } from "@angular/material/table"; +import { DatePipe } from "@angular/common"; +import { MatDialog } 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 { PageEvent } from "@angular/material/paginator"; +import { JoyrideService } from 'ngx-joyride'; +import { ConfigService } from '@services/config.service'; +import { HardwareProfileService } from './hardware-profile.service'; +import { HardwareCollectionModalComponent } from './hardware-collection-modal/hardware-collection-modal.component'; +import { map, Observable, startWith } from 'rxjs'; +import { FormControl } from '@angular/forms'; + +@Component({ + selector: 'app-hardware-profile', + templateUrl: './hardware-profile.component.html', + styleUrl: './hardware-profile.component.css' +}) +export class HardwareProfileComponent { + baseUrl: string; + private apiUrl: string; + dataSource = new MatTableDataSource(); + length: number = 0; + itemsPerPage: number = 10; + page: number = 0; + pageSizeOptions: number[] = [5, 10, 20, 40, 100]; + loading: boolean = false; + filters: { [key: string]: string } = {}; + alertMessage: string | null = null; + readonly panelOpenState = signal(false); + datePipe: DatePipe = new DatePipe('es-ES'); + filteredClients!: Observable; + clientControl = new FormControl(); + clients: any[] = []; + columns = [ + { + columnDef: 'id', + header: 'ID', + cell: (hardware: any) => `${hardware.id}`, + }, + { + columnDef: 'description', + header: 'Descripción', + cell: (hardware: any) => hardware.description + }, + { + columnDef: 'client', + header: 'Cliente', + cell: (hardware: any) => hardware.client ? { + name: hardware.client.name || 'N/A', + ip: hardware.client.ip || 'N/A', + mac: hardware.client.mac || 'N/A' + } : 'N/A' + }, + { + columnDef: 'createdAt', + header: 'Fecha de creación', + cell: (hardware: any) => `${this.datePipe.transform(hardware.createdAt, 'dd/MM/yyyy hh:mm:ss')}`, + } + ]; + displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; + + constructor( + public dialog: MatDialog, + private http: HttpClient, + private toastService: ToastrService, + private joyrideService: JoyrideService, + private configService: ConfigService, + private hardwareProfileService: HardwareProfileService + ) { + this.baseUrl = this.configService.apiUrl; + this.apiUrl = `${this.baseUrl}/hardware-profiles`; + } + + ngOnInit(): void { + this.search(); + 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())) + ); + } + + search(): void { + this.http.get(`${this.apiUrl}?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe( + (data) => { + this.dataSource.data = data['hydra:member']; + this.length = data['hydra:totalItems']; + }, + (error) => { + console.error('Error fetching commands', error); + } + ); + } + + displayFnClient(client: any): string { + return client && client.name ? client.name : ''; + } + + onOptionClientSelected(selectedClient: any): void { + this.filters['client.id'] = selectedClient.id; + this.search(); + } + + clearClientFilter(event: Event, clientSearchClientInput: any): void { + clientSearchClientInput.value = ''; + this.filters['client.id'] = ''; + this.search(); + } + + private _filterClients(value: string): any[] { + const filterValue = value.toLowerCase(); + + return this.clients.filter(client => + client.name?.toLowerCase().includes(filterValue) || + client.ip?.toLowerCase().includes(filterValue) || + client.mac?.toLowerCase().includes(filterValue) + ); + } + + loadClients() { + this.http.get(`${this.baseUrl}/clients`).subscribe( + (data) => { + this.clients = data['hydra:member']; + } + ); + } + + deleteHardware(hardware: any): void { + const dialogRef = this.dialog.open(DeleteModalComponent, { + width: '400px', + data: { name: hardware.name } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + const apiUrl = `${this.baseUrl}${hardware['@id']}`; + + this.http.delete(apiUrl).subscribe({ + next: () => { + this.search(); + this.toastService.success('Hardware deleted successfully'); + }, + error: (error) => { + this.toastService.error('Error deleting hardware'); + } + }); + } else { + console.log('hardware deletion cancelled'); + } + }); + } + + onPageChange(event: PageEvent) { + this.page = event.pageIndex; + this.itemsPerPage = event.pageSize; + this.length = event.length; + this.search(); + } + + iniciarTour(): void { + this.joyrideService.startTour({ + steps: [ + 'titleStep', + 'addHardwareStep', + 'searchStep', + 'tableStep', + ], + showPrevButton: true, + themeColor: '#3f51b5' + }); + } + + viewHardwareCollection(hardware: any): void { + // Si el hardware ya tiene la colección cargada, abrir directamente + if (hardware.hardwareCollection) { + this.openHardwareCollectionModal(hardware); + } else { + // Si no, obtener los detalles completos del hardware profile + this.hardwareProfileService.getHardwareProfile(hardware['@id']).subscribe({ + next: (hardwareProfile) => { + this.openHardwareCollectionModal(hardwareProfile); + }, + error: (error) => { + console.error('Error fetching hardware profile details:', error); + this.toastService.error('Error al obtener los detalles del perfil de hardware'); + } + }); + } + } + + private openHardwareCollectionModal(hardwareProfile: any): void { + this.dialog.open(HardwareCollectionModalComponent, { + width: '800px', + maxWidth: '90vw', + data: { + profile: hardwareProfile, + profileName: hardwareProfile.description || `Perfil ID: ${hardwareProfile.id}` + } + }); + } + +} diff --git a/ogWebconsole/src/app/components/hardware-profile/hardware-profile.service.ts b/ogWebconsole/src/app/components/hardware-profile/hardware-profile.service.ts new file mode 100644 index 0000000..6c359cb --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-profile/hardware-profile.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { ConfigService } from '@services/config.service'; + +@Injectable({ + providedIn: 'root' +}) +export class HardwareProfileService { + private baseUrl: string; + private apiUrl: string; + + constructor( + private http: HttpClient, + private configService: ConfigService + ) { + this.baseUrl = this.configService.apiUrl; + this.apiUrl = `${this.baseUrl}/hardware-profiles`; + } + + getHardwareProfile(id: string): Observable { + return this.http.get(`${this.baseUrl}${id}`); + } + + getHardwareProfiles(page: number = 1, itemsPerPage: number = 10, filters: { [key: string]: string } = {}): Observable { + return this.http.get(`${this.apiUrl}?page=${page}&itemsPerPage=${itemsPerPage}`, { params: filters }); + } +} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/hardware-type/hardware-type.component.css b/ogWebconsole/src/app/components/hardware-type/hardware-type.component.css new file mode 100644 index 0000000..1d928ca --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-type/hardware-type.component.css @@ -0,0 +1,60 @@ +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 10px; + border-bottom: 1px solid #ddd; + } + + .header-container-title { + flex-grow: 1; + text-align: left; + margin-left: 1em; + } + + .calendar-button-row { + display: flex; + gap: 15px; + } + + .lists-container { + padding: 16px; + } + + .card.unidad-card { + height: 100%; + box-sizing: border-box; + } + + table { + width: 100%; + } + + .search-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + margin: 1.5rem 0rem 1.5rem 0rem; + box-sizing: border-box; + } + + .search-string { + flex: 2; + padding: 5px; + } + + .search-boolean { + flex: 1; + padding: 5px; + } + + .mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); + } + + .paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; + } \ No newline at end of file diff --git a/ogWebconsole/src/app/components/hardware-type/hardware-type.component.html b/ogWebconsole/src/app/components/hardware-type/hardware-type.component.html new file mode 100644 index 0000000..23131fe --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-type/hardware-type.component.html @@ -0,0 +1,69 @@ +
+ +
+

{{ 'manageHardwareTypes' | translate }}

+
+
+ +
+ + Buscar nombre de hardware + + search + Pulsar 'enter' para buscar + + + Buscar por tipo + + Servidor + Estación de trabajo + Portátil + Impresora + Escáner + Router + Switch + Firewall + Otro + + +
+ + + + + + + + + + + + + +
{{ column.header }} + + + {{ image[column.columnDef] ? 'check_circle' : 'cancel' }} + + + + + {{ column.cell(image) }} + + Acciones + +
+
+ + +
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/hardware-type/hardware-type.component.spec.ts b/ogWebconsole/src/app/components/hardware-type/hardware-type.component.spec.ts new file mode 100644 index 0000000..06f26bc --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-type/hardware-type.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HardwareTypeComponent } from './hardware-type.component'; + +describe('HardwareTypeComponent', () => { + let component: HardwareTypeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [HardwareTypeComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HardwareTypeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/hardware-type/hardware-type.component.ts b/ogWebconsole/src/app/components/hardware-type/hardware-type.component.ts new file mode 100644 index 0000000..e615a0b --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-type/hardware-type.component.ts @@ -0,0 +1,131 @@ +import { Component, signal } from '@angular/core'; +import { MatTableDataSource } from "@angular/material/table"; +import { DatePipe } from "@angular/common"; +import { MatDialog } 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 { PageEvent } from "@angular/material/paginator"; +import { JoyrideService } from 'ngx-joyride'; +import { ConfigService } from '@services/config.service'; + +@Component({ + selector: 'app-hardware-type', + templateUrl: './hardware-type.component.html', + styleUrl: './hardware-type.component.css' +}) +export class HardwareTypeComponent { + baseUrl: string; + private apiUrl: string; + dataSource = new MatTableDataSource(); + length: number = 0; + itemsPerPage: number = 10; + page: number = 0; + pageSizeOptions: number[] = [5, 10, 20, 40, 100]; + loading: boolean = false; + filters: { [key: string]: string } = {}; + alertMessage: string | null = null; + readonly panelOpenState = signal(false); + datePipe: DatePipe = new DatePipe('es-ES'); + columns = [ + { + columnDef: 'id', + header: 'ID', + cell: (hardware: any) => `${hardware.id}`, + }, + { + columnDef: 'name', + header: 'Nombre', + cell: (hardware: any) => hardware.name + }, + { + columnDef: 'code', + header: 'Código', + cell: (hardware: any) => hardware.code + }, + { + columnDef: 'description', + header: 'Descripción', + cell: (hardware: any) => hardware.description + }, + { + columnDef: 'createdAt', + header: 'Fecha de creación', + cell: (hardware: any) => `${this.datePipe.transform(hardware.createdAt, 'dd/MM/yyyy hh:mm:ss')}`, + } + ]; + displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; + + constructor( + public dialog: MatDialog, + private http: HttpClient, + private toastService: ToastrService, + private joyrideService: JoyrideService, + private configService: ConfigService + ) { + this.baseUrl = this.configService.apiUrl; + this.apiUrl = `${this.baseUrl}/hardware-types`; + } + + ngOnInit(): void { + this.search(); + } + + search(): void { + this.http.get(`${this.apiUrl}?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe( + (data) => { + this.dataSource.data = data['hydra:member']; + this.length = data['hydra:totalItems']; + }, + (error) => { + console.error('Error fetching commands', error); + } + ); + } + + deleteHardware(hardware: any): void { + const dialogRef = this.dialog.open(DeleteModalComponent, { + width: '400px', + data: { name: hardware.name } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + const apiUrl = `${this.baseUrl}${hardware['@id']}`; + + this.http.delete(apiUrl).subscribe({ + next: () => { + this.search(); + this.toastService.success('Hardware deleted successfully'); + }, + error: (error) => { + this.toastService.error('Error deleting hardware'); + } + }); + } else { + console.log('hardware deletion cancelled'); + } + }); + } + + onPageChange(event: PageEvent) { + this.page = event.pageIndex; + this.itemsPerPage = event.pageSize; + this.length = event.length; + this.search(); + } + + iniciarTour(): void { + this.joyrideService.startTour({ + steps: [ + 'titleStep', + 'addHardwareStep', + 'searchStep', + 'tableStep', + ], + showPrevButton: true, + themeColor: '#3f51b5' + }); + } + +} diff --git a/ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.css b/ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.css new file mode 100644 index 0000000..e69de29 diff --git a/ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.html b/ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.html new file mode 100644 index 0000000..10d4aed --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.html @@ -0,0 +1 @@ +

show-details works!

diff --git a/ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.spec.ts b/ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.spec.ts new file mode 100644 index 0000000..5d719fb --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ShowDetailsComponent } from './show-details.component'; + +describe('ShowDetailsComponent', () => { + let component: ShowDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ShowDetailsComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ShowDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.ts b/ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.ts new file mode 100644 index 0000000..830d6f4 --- /dev/null +++ b/ogWebconsole/src/app/components/hardware-type/show-details/show-details.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-show-details', + templateUrl: './show-details.component.html', + styleUrl: './show-details.component.css' +}) +export class ShowDetailsComponent { + +} diff --git a/ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.css b/ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.css new file mode 100644 index 0000000..e69de29 diff --git a/ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.html b/ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.html new file mode 100644 index 0000000..a46ec4c --- /dev/null +++ b/ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.html @@ -0,0 +1 @@ +

create-hardware works!

diff --git a/ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.spec.ts b/ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.spec.ts new file mode 100644 index 0000000..deb858c --- /dev/null +++ b/ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateHardwareComponent } from './create-hardware.component'; + +describe('CreateHardwareComponent', () => { + let component: CreateHardwareComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CreateHardwareComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CreateHardwareComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.ts b/ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.ts new file mode 100644 index 0000000..8c61d79 --- /dev/null +++ b/ogWebconsole/src/app/components/hardware/create-hardware/create-hardware.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-create-hardware', + templateUrl: './create-hardware.component.html', + styleUrl: './create-hardware.component.css' +}) +export class CreateHardwareComponent { + +} diff --git a/ogWebconsole/src/app/components/hardware/hardware.component.css b/ogWebconsole/src/app/components/hardware/hardware.component.css new file mode 100644 index 0000000..1d928ca --- /dev/null +++ b/ogWebconsole/src/app/components/hardware/hardware.component.css @@ -0,0 +1,60 @@ +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 10px; + border-bottom: 1px solid #ddd; + } + + .header-container-title { + flex-grow: 1; + text-align: left; + margin-left: 1em; + } + + .calendar-button-row { + display: flex; + gap: 15px; + } + + .lists-container { + padding: 16px; + } + + .card.unidad-card { + height: 100%; + box-sizing: border-box; + } + + table { + width: 100%; + } + + .search-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + margin: 1.5rem 0rem 1.5rem 0rem; + box-sizing: border-box; + } + + .search-string { + flex: 2; + padding: 5px; + } + + .search-boolean { + flex: 1; + padding: 5px; + } + + .mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); + } + + .paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; + } \ No newline at end of file diff --git a/ogWebconsole/src/app/components/hardware/hardware.component.html b/ogWebconsole/src/app/components/hardware/hardware.component.html new file mode 100644 index 0000000..356d9b8 --- /dev/null +++ b/ogWebconsole/src/app/components/hardware/hardware.component.html @@ -0,0 +1,69 @@ +
+ +
+

{{ 'manageHardware' | translate }}

+
+
+ +
+ + Buscar nombre de hardware + + search + Pulsar 'enter' para buscar + + + Buscar por tipo + + Servidor + Estación de trabajo + Portátil + Impresora + Escáner + Router + Switch + Firewall + Otro + + +
+ + + + + + + + + + + + + +
{{ column.header }} + + + {{ image[column.columnDef] ? 'check_circle' : 'cancel' }} + + + + + {{ column.cell(image) }} + + Acciones + +
+
+ + +
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/hardware/hardware.component.spec.ts b/ogWebconsole/src/app/components/hardware/hardware.component.spec.ts new file mode 100644 index 0000000..b6616bd --- /dev/null +++ b/ogWebconsole/src/app/components/hardware/hardware.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HardwareComponent } from './hardware.component'; + +describe('HardwareComponent', () => { + let component: HardwareComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [HardwareComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HardwareComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/hardware/hardware.component.ts b/ogWebconsole/src/app/components/hardware/hardware.component.ts new file mode 100644 index 0000000..be906b3 --- /dev/null +++ b/ogWebconsole/src/app/components/hardware/hardware.component.ts @@ -0,0 +1,127 @@ +import { Component, signal } from '@angular/core'; +import { MatTableDataSource } from "@angular/material/table"; +import { DatePipe } from "@angular/common"; +import { MatDialog } 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 { PageEvent } from "@angular/material/paginator"; +import { JoyrideService } from 'ngx-joyride'; +import { ConfigService } from '@services/config.service'; +import { CreateHardwareComponent } from './create-hardware/create-hardware.component'; + +@Component({ + selector: 'app-hardware', + templateUrl: './hardware.component.html', + styleUrl: './hardware.component.css' +}) +export class HardwareComponent { + baseUrl: string; + private apiUrl: string; + dataSource = new MatTableDataSource(); + length: number = 0; + itemsPerPage: number = 10; + page: number = 0; + pageSizeOptions: number[] = [5, 10, 20, 40, 100]; + loading: boolean = false; + filters: { [key: string]: string } = {}; + alertMessage: string | null = null; + readonly panelOpenState = signal(false); + datePipe: DatePipe = new DatePipe('es-ES'); + columns = [ + { + columnDef: 'id', + header: 'ID', + cell: (hardware: any) => `${hardware.id}`, + }, + { + columnDef: 'name', + header: 'Nombre', + cell: (hardware: any) => hardware.name + }, + { + columnDef: 'type', + header: 'Tipo', + cell: (hardware: any) => hardware.type?.name || 'N/A' + }, + { + columnDef: 'createdAt', + header: 'Fecha de creación', + cell: (hardware: any) => `${this.datePipe.transform(hardware.createdAt, 'dd/MM/yyyy hh:mm:ss')}`, + } + ]; + displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; + + constructor( + public dialog: MatDialog, + private http: HttpClient, + private toastService: ToastrService, + private joyrideService: JoyrideService, + private configService: ConfigService + ) { + this.baseUrl = this.configService.apiUrl; + this.apiUrl = `${this.baseUrl}/hardware`; + } + + ngOnInit(): void { + this.search(); + } + + search(): void { + this.http.get(`${this.apiUrl}?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe( + (data) => { + this.dataSource.data = data['hydra:member']; + this.length = data['hydra:totalItems']; + }, + (error) => { + console.error('Error fetching commands', error); + } + ); + } + + deleteHardware(hardware: any): void { + const dialogRef = this.dialog.open(DeleteModalComponent, { + width: '400px', + data: { name: hardware.name } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + const apiUrl = `${this.baseUrl}${hardware['@id']}`; + + this.http.delete(apiUrl).subscribe({ + next: () => { + this.search(); + this.toastService.success('Hardware deleted successfully'); + }, + error: (error) => { + this.toastService.error('Error deleting hardware'); + } + }); + } else { + console.log('hardware deletion cancelled'); + } + }); + } + + onPageChange(event: PageEvent) { + this.page = event.pageIndex; + this.itemsPerPage = event.pageSize; + this.length = event.length; + this.search(); + } + + iniciarTour(): void { + this.joyrideService.startTour({ + steps: [ + 'titleStep', + 'addHardwareStep', + 'searchStep', + 'tableStep', + ], + showPrevButton: true, + themeColor: '#3f51b5' + }); + } + +} diff --git a/ogWebconsole/src/app/layout/sidebar/sidebar.component.html b/ogWebconsole/src/app/layout/sidebar/sidebar.component.html index 3272552..0d6fc28 100644 --- a/ogWebconsole/src/app/layout/sidebar/sidebar.component.html +++ b/ogWebconsole/src/app/layout/sidebar/sidebar.component.html @@ -68,7 +68,6 @@ - @@ -109,7 +108,6 @@ - @@ -134,6 +132,40 @@ + + + + computer + {{ 'hardware' | translate }} + + + + + + + list + {{ 'hardwareList' | translate }} + + + + + folder_shared + {{ 'hardwareProfiles' | translate }} + + + + + computer + {{ 'hardwareTypes' | translate }} + + + + + diff --git a/ogWebconsole/src/app/layout/sidebar/sidebar.component.ts b/ogWebconsole/src/app/layout/sidebar/sidebar.component.ts index 7abcc59..7b8055b 100644 --- a/ogWebconsole/src/app/layout/sidebar/sidebar.component.ts +++ b/ogWebconsole/src/app/layout/sidebar/sidebar.component.ts @@ -17,7 +17,8 @@ export class SidebarComponent { showOgDhcpSub: boolean = false; showCommandSub: boolean = false; showSoftwareSub: boolean = false; - + showHardwareSub: boolean = false; + toggleOgBootSub() { this.showOgBootSub = !this.showOgBootSub; } @@ -31,6 +32,10 @@ export class SidebarComponent { this.showSoftwareSub = !this.showSoftwareSub; } + toggleHardwareSub() { + this.showHardwareSub = !this.showHardwareSub; + } + onItemClick() { if (this.isVisible && this.sidebarMode === 'over') { this.closeSidebar.emit(); diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index b3d5f92..4278ed1 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -582,5 +582,16 @@ "online": "Online", "busy": "Busy", "cancelTask": "Cancel task", - "clickStatsToFilter": "Click on the statistics cards to filter the traces" + "clickStatsToFilter": "Click on the statistics cards to filter the traces", + "hardware": "Hardware", + "manageHardware": "Manage Hardware", + "TOOLTIP_HARDWARE": "Manage hardware configurations", + "hardwareList": "List", + "TOOLTIP_HARDWARE_LIST": "View list of available hardware", + "hardwareProfiles": "Profiles", + "TOOLTIP_HARDWARE_PROFILES": "Manage hardware profiles", + "hardwareTypes": "Types", + "TOOLTIP_HARDWARE_TYPES": "Manage hardware types", + "manageHardwareProfiles": "Manage hardware profiles", + "manageHardwareTypes": "Manage hardware types" } diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 3ea8ea5..bfe04d7 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -587,6 +587,17 @@ "online": "Online", "busy": "Ocupado", "cancelTask": "Cancelar tarea", - "clickStatsToFilter": "Haz clic en las tarjetas de estadísticas para filtrar las trazas" + "clickStatsToFilter": "Haz clic en las tarjetas de estadísticas para filtrar las trazas", + "hardware": "Hardware", + "manageHardware": "Administrar Hardware", + "TOOLTIP_HARDWARE": "Gestionar configuraciones de hardware", + "hardwareList": "Listado", + "TOOLTIP_HARDWARE_LIST": "Ver lista de hardware disponible", + "hardwareProfiles": "Perfiles", + "TOOLTIP_HARDWARE_PROFILES": "Gestionar perfiles de hardware", + "hardwareTypes": "Tipos", + "TOOLTIP_HARDWARE_TYPES": "Gestionar tipos de hardware", + "manageHardwareProfiles": "Administrar perfiles de hardware", + "manageHardwareTypes": "Administrar tipos de hardware" } \ No newline at end of file -- 2.40.1 From 262b998a216fd164169ca552fb122bd18683b6e0 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 23 Sep 2025 08:41:30 +0200 Subject: [PATCH 2/2] refs #2836. Added new HW componnetes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e29efc6..caef9f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Changelog +## [0.24.0] - 2025-09-23 +### Added +- Se han añadido nuevos componentes de visualizacion de todos los apartados de hardware y hardware profile. + +--- ## [0.23.4] - 2025-09-22 ### Fixed - Se ha arreglado en error a la hora de mandar el ambito de ejecucion en lso asistentes -- 2.40.1