develop #47
|
@ -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
|
||||
|
|
|
@ -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'] } },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<h2 mat-dialog-title>
|
||||
{{ profileName }}
|
||||
</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<div *ngIf="dataSource.data.length === 0" class="no-data-message">
|
||||
<mat-icon>info</mat-icon>
|
||||
<p>No hay hardware asociado a este perfil</p>
|
||||
</div>
|
||||
|
||||
<div *ngIf="dataSource.data.length > 0" class="hardware-table-container">
|
||||
<table mat-table [dataSource]="dataSource">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let hardware">
|
||||
{{ column.cell(hardware) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button mat-button color="primary" (click)="onClose()">
|
||||
<mat-icon>close</mat-icon>
|
||||
Cerrar
|
||||
</button>
|
||||
</mat-dialog-actions>
|
|
@ -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<HardwareCollectionModalComponent>;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -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<any>();
|
||||
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<HardwareCollectionModalComponent>,
|
||||
@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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<div class="header-container-title">
|
||||
<h2 i18n="@@adminImagesTitle" joyrideStep="titleStep"
|
||||
text="Administra los perfiles de hardware disponibles desde este componente.">{{ 'manageHardwareProfiles' | translate }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-container" joyrideStep="searchStep"
|
||||
text="Utiliza los filtros para buscar entre el hardware listado.">
|
||||
<mat-form-field appearance="fill" class="search-string" style="width: 50%; margin-right: 8px;">
|
||||
<mat-label i18n="@@searchLabel">Buscar nombre de hardware</mat-label>
|
||||
<input matInput name="searchBar" placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()"
|
||||
i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-select" style="width: 50%; margin-left: 8px;">
|
||||
<input type="text" matInput [formControl]="clientControl" [matAutocomplete]="clientAuto" #commandClientInput
|
||||
placeholder="{{ 'filterClientPlaceholder' | translate }}">
|
||||
<mat-autocomplete #clientAuto="matAutocomplete" [displayWith]="displayFnClient"
|
||||
(optionSelected)="onOptionClientSelected($event.option.value)">
|
||||
<mat-option *ngFor="let client of filteredClients | async" [value]="client">
|
||||
<div class="client-option">
|
||||
<span class="client-name">{{ client.name }}</span>
|
||||
<span class="client-details">{{ client.ip }} — {{ client.mac }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
<button *ngIf="commandClientInput.value" mat-icon-button matSuffix aria-label="Clear input search"
|
||||
(click)="clearClientFilter($event, commandClientInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
<mat-hint>{{ 'enterClientName' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyride="tableStep"
|
||||
text="Aquí se mostrará todo el hardware disponible y sus características.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef === 'isDefault' || column.columnDef === 'installed'">
|
||||
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="column.columnDef === 'client'">
|
||||
<div *ngIf="column.cell(image) !== 'N/A'; else noClientData" class="client-info">
|
||||
<div class="client-name">{{ column.cell(image).name }}</div>
|
||||
<div class="client-details">
|
||||
<span class="client-ip">{{ column.cell(image).ip }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #noClientData>
|
||||
<span>N/A</span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container
|
||||
*ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl' && column.columnDef !== 'client'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let hardware" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="viewHardwareCollection(hardware)"
|
||||
matTooltip="Ver colección de hardware" i18n-matTooltip="@@viewHardwareCollection">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="deleteHardware(hardware)"
|
||||
i18n="@@buttonDelete"><mat-icon>delete</mat-icon></button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
|
@ -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<HardwareProfileComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [HardwareProfileComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HardwareProfileComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -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<any>();
|
||||
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<any[]>;
|
||||
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<any>(`${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<any>(`${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}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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<any> {
|
||||
return this.http.get<any>(`${this.baseUrl}${id}`);
|
||||
}
|
||||
|
||||
getHardwareProfiles(page: number = 1, itemsPerPage: number = 10, filters: { [key: string]: string } = {}): Observable<any> {
|
||||
return this.http.get<any>(`${this.apiUrl}?page=${page}&itemsPerPage=${itemsPerPage}`, { params: filters });
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<div class="header-container-title">
|
||||
<h2 i18n="@@adminImagesTitle" joyrideStep="titleStep"
|
||||
text="Administra los tipos de hardware disponibles desde este componente.">{{ 'manageHardwareTypes' | translate }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-container" joyrideStep="searchStep"
|
||||
text="Utiliza los filtros para buscar entre el hardware listado.">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label i18n="@@searchLabel">Buscar nombre de hardware</mat-label>
|
||||
<input matInput name="searchBar" placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()"
|
||||
i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-boolean">
|
||||
<mat-label i18n="@@searchLabel">Buscar por tipo</mat-label>
|
||||
<mat-select name="searchType" [(ngModel)]="filters['type']" (selectionChange)="search()"
|
||||
placeholder="Seleccionar opción">
|
||||
<mat-option [value]="'server'">Servidor</mat-option>
|
||||
<mat-option [value]="'workstation'">Estación de trabajo</mat-option>
|
||||
<mat-option [value]="'laptop'">Portátil</mat-option>
|
||||
<mat-option [value]="'printer'">Impresora</mat-option>
|
||||
<mat-option [value]="'scanner'">Escáner</mat-option>
|
||||
<mat-option [value]="'router'">Router</mat-option>
|
||||
<mat-option [value]="'switch'">Switch</mat-option>
|
||||
<mat-option [value]="'firewall'">Firewall</mat-option>
|
||||
<mat-option [value]="'other'">Otro</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyride="tableStep"
|
||||
text="Aquí se mostrará todo el hardware disponible y sus características.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef === 'isDefault' || column.columnDef === 'installed'">
|
||||
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<ng-container
|
||||
*ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let hardware" style="text-align: center;">
|
||||
<button mat-icon-button color="warn" (click)="deleteHardware(hardware)"
|
||||
i18n="@@buttonDelete"><mat-icon>delete</mat-icon></button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
|
@ -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<HardwareTypeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [HardwareTypeComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HardwareTypeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -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<any>();
|
||||
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<any>(`${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'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<p>show-details works!</p>
|
|
@ -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<ShowDetailsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ShowDetailsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ShowDetailsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<p>create-hardware works!</p>
|
|
@ -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<CreateHardwareComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [CreateHardwareComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CreateHardwareComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<div class="header-container-title">
|
||||
<h2 i18n="@@adminImagesTitle" joyrideStep="titleStep"
|
||||
text="Administra el hardware deisponible desde este componente.">{{ 'manageHardware' | translate }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-container" joyrideStep="searchStep"
|
||||
text="Utiliza los filtros para buscar entre el hardware listado.">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label i18n="@@searchLabel">Buscar nombre de hardware</mat-label>
|
||||
<input matInput name="searchBar" placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()"
|
||||
i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-boolean">
|
||||
<mat-label i18n="@@searchLabel">Buscar por tipo</mat-label>
|
||||
<mat-select name="searchType" [(ngModel)]="filters['type']" (selectionChange)="search()"
|
||||
placeholder="Seleccionar opción">
|
||||
<mat-option [value]="'server'">Servidor</mat-option>
|
||||
<mat-option [value]="'workstation'">Estación de trabajo</mat-option>
|
||||
<mat-option [value]="'laptop'">Portátil</mat-option>
|
||||
<mat-option [value]="'printer'">Impresora</mat-option>
|
||||
<mat-option [value]="'scanner'">Escáner</mat-option>
|
||||
<mat-option [value]="'router'">Router</mat-option>
|
||||
<mat-option [value]="'switch'">Switch</mat-option>
|
||||
<mat-option [value]="'firewall'">Firewall</mat-option>
|
||||
<mat-option [value]="'other'">Otro</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyride="tableStep"
|
||||
text="Aquí se mostrará todo el hardware disponible y sus características.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef === 'isDefault' || column.columnDef === 'installed'">
|
||||
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<ng-container
|
||||
*ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let hardware" style="text-align: center;">
|
||||
<button mat-icon-button color="warn" (click)="deleteHardware(hardware)"
|
||||
i18n="@@buttonDelete"><mat-icon>delete</mat-icon></button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HardwareComponent } from './hardware.component';
|
||||
|
||||
describe('HardwareComponent', () => {
|
||||
let component: HardwareComponent;
|
||||
let fixture: ComponentFixture<HardwareComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [HardwareComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HardwareComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -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<any>();
|
||||
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<any>(`${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'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -68,7 +68,6 @@
|
|||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
<!-- Submenu items for Boot -->
|
||||
<mat-nav-list *ngIf="showOgBootSub" style="padding-left: 20px;">
|
||||
<mat-list-item routerLink="/pxe-images" (click)="onItemClick()" matTooltip="{{ 'TOOLTIP_PXE_IMAGES' | translate }}"
|
||||
matTooltipShowDelay="1000">
|
||||
|
@ -109,7 +108,6 @@
|
|||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
<!-- Submenu items for Software -->
|
||||
<mat-nav-list *ngIf="showSoftwareSub" style="padding-left: 20px;">
|
||||
<mat-list-item routerLink="/software" (click)="onItemClick()" matTooltip="{{ 'TOOLTIP_SOFTWARE_LIST' | translate }}"
|
||||
matTooltipShowDelay="1000">
|
||||
|
@ -134,6 +132,40 @@
|
|||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
|
||||
|
||||
<mat-list-item (click)="toggleHardwareSub()" matTooltip="{{ 'TOOLTIP_HARDWARE' | translate }}"
|
||||
matTooltipShowDelay="1000" *ngIf="auth.userCategory !== 'ou-operator' && auth.userCategory !== 'ou-minimal'">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">computer</mat-icon>
|
||||
<span>{{ 'hardware' | translate }}</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-nav-list *ngIf="showHardwareSub" style="padding-left: 20px;">
|
||||
<mat-list-item routerLink="/hardware" (click)="onItemClick()" matTooltip="{{ 'TOOLTIP_SOFTWARE_LIST' | translate }}"
|
||||
matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">list</mat-icon>
|
||||
<span>{{ 'hardwareList' | translate }}</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/hardware-profiles" (click)="onItemClick()"
|
||||
matTooltip="{{ 'TOOLTIP_HARDWARE_PROFILES' | translate }}" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">folder_shared</mat-icon>
|
||||
<span>{{ 'hardwareProfiles' | translate }}</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/hardware-types" (click)="onItemClick()"
|
||||
matTooltip="{{ 'TOOLTIP_HARDWARE_TYPES' | translate }}" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">computer</mat-icon>
|
||||
<span>{{ 'hardwareTypes' | translate }}</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
|
||||
|
||||
<mat-list-item routerLink="/repositories" (click)="onItemClick()"
|
||||
matTooltip="{{ 'TOOLTIP_REPOSITORIES' | translate }}" matTooltipShowDelay="1000"
|
||||
*ngIf="auth.userCategory !== 'ou-operator' && auth.userCategory !== 'ou-minimal'">
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
Loading…
Reference in New Issue