refs #1931 Add ClientDetailsComponent for enhanced client information display and management
testing/ogGui-multibranch/pipeline/head This commit looks good
Details
testing/ogGui-multibranch/pipeline/head This commit looks good
Details
parent
88aaf39b65
commit
ca0140e275
|
@ -143,6 +143,7 @@ import {
|
|||
import { EditImageComponent } from './components/repositories/edit-image/edit-image.component';
|
||||
import { ShowGitImagesComponent } from './components/repositories/show-git-images/show-git-images.component';
|
||||
import { RenameImageComponent } from './components/repositories/rename-image/rename-image.component';
|
||||
import { ClientDetailsComponent } from './components/groups/shared/client-details/client-details.component';
|
||||
|
||||
export function HttpLoaderFactory(http: HttpClient) {
|
||||
return new TranslateHttpLoader(http, './locale/', '.json');
|
||||
|
@ -243,7 +244,8 @@ registerLocaleData(localeEs, 'es-ES');
|
|||
SaveScriptComponent,
|
||||
EditImageComponent,
|
||||
ShowGitImagesComponent,
|
||||
RenameImageComponent
|
||||
RenameImageComponent,
|
||||
ClientDetailsComponent
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [BrowserModule,
|
||||
|
|
|
@ -55,49 +55,41 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
|
||||
private updateCommandStates(): void {
|
||||
let states: string[] = [];
|
||||
|
||||
// Obtener los estados de los clientes
|
||||
|
||||
if (this.clientData.length > 0) {
|
||||
states = this.clientData.map(client => client.status);
|
||||
} else if (this.clientState) {
|
||||
states = [this.clientState];
|
||||
}
|
||||
|
||||
|
||||
const allOffOrDisconnected = states.every(state => state === 'off' || state === 'disconnected');
|
||||
const allSameState = states.every(state => state === states[0]);
|
||||
const multipleClients = this.clientData.length > 1;
|
||||
|
||||
|
||||
this.arrayCommands = this.arrayCommands.map(command => {
|
||||
if (allOffOrDisconnected) {
|
||||
// Si todos los clientes están apagados o desconectados, solo habilitar "Encender"
|
||||
command.disabled = command.slug !== 'power-on';
|
||||
} else if (allSameState) {
|
||||
// Si todos los clientes tienen el mismo estado
|
||||
if (states[0] === 'off' || states[0] === 'disconnected') {
|
||||
command.disabled = command.slug !== 'power-on';
|
||||
} else {
|
||||
// Habilitar comandos específicos para un cliente que no está apagado ni desconectado
|
||||
command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'partition', 'run-script'].includes(command.slug);
|
||||
}
|
||||
} else {
|
||||
// Si los estados son distintos
|
||||
if (command.slug === 'create-image') {
|
||||
// "Crear imagen" solo está habilitado para un único cliente
|
||||
command.disabled = multipleClients;
|
||||
} else if (
|
||||
['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug)
|
||||
) {
|
||||
// Habilitar los comandos permitidos cuando los estados son distintos
|
||||
command.disabled = false;
|
||||
} else {
|
||||
// Deshabilitar otros comandos
|
||||
command.disabled = true;
|
||||
}
|
||||
}
|
||||
return command;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onCommandSelect(action: any): void {
|
||||
if (action === 'partition') {
|
||||
this.openPartitionAssistant();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Component, OnInit, OnDestroy, ViewChild, QueryList, ViewChildren, ChangeDetectorRef} from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ViewChild, QueryList, ViewChildren, ChangeDetectorRef } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
@ -26,6 +26,7 @@ import { Subject } from 'rxjs';
|
|||
import { ConfigService } from '@services/config.service';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
import { MatMenuTrigger } from '@angular/material/menu';
|
||||
import { ClientDetailsComponent } from './shared/client-details/client-details.component';
|
||||
|
||||
enum NodeType {
|
||||
OrganizationalUnit = 'organizational-unit',
|
||||
|
@ -85,7 +86,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
{ value: 'windows-session', name: 'Windows Session' },
|
||||
{ value: 'busy', name: 'Ocupado' },
|
||||
{ value: 'mac', name: 'Mac' },
|
||||
{ value: 'disconnected', name: 'Desconectado'}
|
||||
{ value: 'disconnected', name: 'Desconectado' }
|
||||
];
|
||||
|
||||
displayedColumns: string[] = ['select', 'status', 'ip', 'firmwareType', 'name', 'oglive', 'subnet', 'pxeTemplate', 'actions'];
|
||||
|
@ -182,7 +183,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
|
||||
const index = this.arrayClients.findIndex(client => client['@id'] === clientUuid);
|
||||
if (index !== -1) {
|
||||
const updatedClient = {...this.arrayClients[index], status};
|
||||
const updatedClient = { ...this.arrayClients[index], status };
|
||||
this.arrayClients = [
|
||||
...this.arrayClients.slice(0, index),
|
||||
updatedClient,
|
||||
|
@ -609,7 +610,10 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
|
||||
onShowClientDetail(event: MouseEvent, client: Client): void {
|
||||
event.stopPropagation();
|
||||
this.router.navigate(['clients', client.uuid], { state: { clientData: client } });
|
||||
this.dialog.open(ClientDetailsComponent, {
|
||||
width: '600px',
|
||||
data: { clientData: client },
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,326 @@
|
|||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.client-container {
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0rem 1rem 0rem 0.5rem;
|
||||
}
|
||||
|
||||
.client-icon {
|
||||
flex-shrink: 0;
|
||||
margin-right: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 120px;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.row-container {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.charts-wrapper {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.charts-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.disk-usage {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.circular-chart {
|
||||
max-width: 150px;
|
||||
max-height: 150px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.chart {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon-pc {
|
||||
font-size: 25px;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.client-title h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.client-title p {
|
||||
margin: 2px 0;
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.client-info {
|
||||
margin: 20px 0;
|
||||
border-radius: 12px;
|
||||
background-color: #f5f7fa;
|
||||
padding: 20px;
|
||||
border: 2px solid #d1d9e6;
|
||||
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.info-section {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.two-column-table {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.column.property {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.column.value {
|
||||
text-align: right;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.mat-tab-group {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.mat-tab-body-wrapper {
|
||||
min-height: inherit;
|
||||
}
|
||||
|
||||
.info-section h2 {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 10px;
|
||||
color: #0056b3;
|
||||
}
|
||||
|
||||
.info-section p {
|
||||
font-size: 1rem;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.second-section {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.client-button-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.buttons-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.buttons-row button {
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.circular-chart {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
.circle-bg {
|
||||
fill: none;
|
||||
stroke: #f0f0f0;
|
||||
stroke-width: 3.8;
|
||||
}
|
||||
|
||||
.circle {
|
||||
fill: none;
|
||||
stroke-width: 3.8;
|
||||
stroke: #00bfa5;
|
||||
stroke-linecap: round;
|
||||
animation: progress 1s ease-out forwards;
|
||||
}
|
||||
|
||||
.percentage {
|
||||
fill: #333;
|
||||
font-size: 0.7rem;
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
.disk-usage h3 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 1.2rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
@keyframes progress {
|
||||
0% {
|
||||
stroke-dasharray: 0, 100;
|
||||
}
|
||||
}
|
||||
|
||||
.assistants-container {
|
||||
background-color: #fff;
|
||||
margin-top: 10px;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.circular-chart {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
.circle-bg {
|
||||
fill: none;
|
||||
stroke: #f0f0f0;
|
||||
stroke-width: 3.8;
|
||||
}
|
||||
|
||||
.circle {
|
||||
fill: none;
|
||||
stroke-width: 3.8;
|
||||
stroke-linecap: round;
|
||||
animation: progress 1s ease-out forwards;
|
||||
}
|
||||
|
||||
.partition-0 {
|
||||
stroke: #00bfa5;
|
||||
}
|
||||
|
||||
.partition-1 {
|
||||
stroke: #ff6f61;
|
||||
}
|
||||
|
||||
.partition-2 {
|
||||
stroke: #ffb400;
|
||||
}
|
||||
|
||||
.partition-3 {
|
||||
stroke: #3498db;
|
||||
}
|
||||
|
||||
.percentage {
|
||||
fill: #333;
|
||||
font-size: 0.7rem;
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
.disk-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
background-color: #f5f7fa;
|
||||
border: 2px solid #d1d9e6;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
flex: 3;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
table.mat-elevation-z8 {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mat-header-cell {
|
||||
background-color: #d1d9e6 !important;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mat-cell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mat-chip {
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.charts-container {
|
||||
flex: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.disk-usage {
|
||||
background-color: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
justify-self: center;
|
||||
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chart {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div class="client-container">
|
||||
<div class="header-container">
|
||||
<h2 class="title">{{ 'clientDetailsTitle' | translate }} {{ clientData.name }}</h2>
|
||||
</div>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<div *ngIf="!loading" class="client-info">
|
||||
<div class="info-section">
|
||||
<div class="two-column-table">
|
||||
<div class="table-row" *ngFor="let clientData of generalData">
|
||||
<div class="column property">{{ clientData?.property }}</div>
|
||||
<div class="column value">{{ clientData?.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="two-column-table">
|
||||
<div class="table-row" *ngFor="let clientData of networkData">
|
||||
<div class="column property">{{ clientData?.property }}</div>
|
||||
<div class="column value">{{ clientData?.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-container">
|
||||
<h2 class="title" i18n="@@adminImagesTitle">Discos/Particiones</h2>
|
||||
</div>
|
||||
|
||||
<div class="disk-container">
|
||||
<div class="table-container">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<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 !== 'size'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'size'">
|
||||
<mat-chip color="primary">
|
||||
{{ (image.size / 1024).toFixed(2) }} GB
|
||||
</mat-chip>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="charts-container">
|
||||
<ng-container *ngIf="diskUsageData && diskUsageData.length > 0">
|
||||
<div *ngFor="let disk of chartDisk" class="disk-usage">
|
||||
<ngx-charts-pie-chart class="chart" [view]="view" [results]="disk.chartData" [doughnut]="true">
|
||||
</ngx-charts-pie-chart>
|
||||
|
||||
<h3>Disco {{ disk.diskNumber }}</h3>
|
||||
<p>Usado: {{ (disk.used).toFixed(2) }} GB ({{ disk.percentage }}%)</p>
|
||||
<p>Total: {{ disk.total }} GB</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,52 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { ClientDetailsComponent } from './client-details.component';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { LoadingComponent } from 'src/app/shared/loading/loading.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
|
||||
describe('ClientDetailsComponent', () => {
|
||||
let component: ClientDetailsComponent;
|
||||
let fixture: ComponentFixture<ClientDetailsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockConfigService = {
|
||||
apiUrl: 'http://mock-api-url',
|
||||
mercureUrl: 'http://mock-mercure-url'
|
||||
};
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ClientDetailsComponent, LoadingComponent],
|
||||
imports: [
|
||||
MatDialogModule,
|
||||
HttpClientTestingModule,
|
||||
ToastrModule.forRoot(),
|
||||
MatDividerModule,
|
||||
TranslateModule.forRoot(),
|
||||
CommonModule,
|
||||
MatTableModule,
|
||||
MatProgressSpinnerModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } },
|
||||
{ provide: ConfigService, useValue: mockConfigService }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ClientDetailsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,320 @@
|
|||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { MatTableDataSource } from "@angular/material/table";
|
||||
import { MatDialog } from "@angular/material/dialog";
|
||||
import { Router } from "@angular/router";
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { ManageClientComponent } from "../../shared/clients/manage-client/manage-client.component";
|
||||
import { ConfigService } from '@services/config.service';
|
||||
|
||||
interface ClientInfo {
|
||||
property: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-client-details',
|
||||
templateUrl: './client-details.component.html',
|
||||
styleUrl: './client-details.component.css'
|
||||
})
|
||||
export class ClientDetailsComponent {
|
||||
baseUrl: string;
|
||||
@ViewChild('assistantContainer') assistantContainer!: ElementRef;
|
||||
clientUuid: string;
|
||||
clientData: any = {};
|
||||
isPartitionAssistantVisible: boolean = false;
|
||||
isBootImageVisible: boolean = false;
|
||||
isDiskUsageVisible: boolean = true;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
generalData: ClientInfo[] = [];
|
||||
networkData: ClientInfo[] = [];
|
||||
classroomData: ClientInfo[] = [];
|
||||
diskUsageData: any[] = [];
|
||||
partitions: any[] = [];
|
||||
commands: any[] = [];
|
||||
chartDisk: any[] = [];
|
||||
view: [number, number] = [300, 200];
|
||||
showLegend: boolean = true;
|
||||
|
||||
arrayCommands: any[] = [
|
||||
{ name: 'Enceder', slug: 'power-on' },
|
||||
{ name: 'Apagar', slug: 'power-off' },
|
||||
{ name: 'Reiniciar', slug: 'reboot' },
|
||||
{ name: 'Iniciar Sesión', slug: 'login' },
|
||||
{ name: 'Crear imagen', slug: 'create-image' },
|
||||
{ name: 'Clonar/desplegar imagen', slug: 'deploy-image' },
|
||||
{ name: 'Eliminar Imagen Cache', slug: 'delete-image-cache' },
|
||||
{ name: 'Particionar y Formatear', slug: 'partition' },
|
||||
{ name: 'Inventario Software', slug: 'software-inventory' },
|
||||
{ name: 'Inventario Hardware', slug: 'hardware-inventory' },
|
||||
{ name: 'Ejecutar comando', slug: 'run-script' },
|
||||
];
|
||||
|
||||
datePipe: DatePipe = new DatePipe('es-ES');
|
||||
columns = [
|
||||
{
|
||||
columnDef: 'diskNumber',
|
||||
header: 'Disco',
|
||||
cell: (partition: any) => `${partition.diskNumber}`,
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionNumber',
|
||||
header: 'Particion',
|
||||
cell: (partition: any) => `${partition.partitionNumber}`
|
||||
},
|
||||
{
|
||||
columnDef: 'description',
|
||||
header: 'Sistema de ficheros',
|
||||
cell: (partition: any) => `${partition.filesystem}`
|
||||
},
|
||||
{
|
||||
columnDef: 'size',
|
||||
header: 'Tamaño',
|
||||
cell: (partition: any) => `${(partition.size / 1024).toFixed(2)} GB`
|
||||
},
|
||||
{
|
||||
columnDef: 'memoryUsage',
|
||||
header: 'Uso',
|
||||
cell: (partition: any) => `${partition.memoryUsage} %`
|
||||
},
|
||||
{
|
||||
columnDef: 'operativeSystem',
|
||||
header: 'SO',
|
||||
cell: (partition: any) => `${partition.operativeSystem?.name}`
|
||||
},
|
||||
];
|
||||
displayedColumns = [...this.columns.map(column => column.columnDef)];
|
||||
isDiskUsageEmpty: boolean = true;
|
||||
loading: boolean = true;
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private dialog: MatDialog,
|
||||
private configService: ConfigService,
|
||||
private router: Router,
|
||||
private toastService: ToastrService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { clientData: any }
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
const url = window.location.href;
|
||||
const segments = url.split('/');
|
||||
this.clientUuid = segments[segments.length - 1];
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.data && this.data.clientData) {
|
||||
this.clientData = this.data.clientData;
|
||||
this.updateGeneralData();
|
||||
this.updateNetworkData();
|
||||
this.calculateDiskUsage();
|
||||
this.loading = false;
|
||||
} else {
|
||||
console.error('No se recibieron datos del cliente.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
loadClient = (uuid: string) => {
|
||||
this.http.get<any>(`${this.baseUrl}${uuid}`).subscribe({
|
||||
next: data => {
|
||||
this.clientData = data;
|
||||
this.updateGeneralData();
|
||||
this.updateNetworkData();
|
||||
this.loadPartitions()
|
||||
this.loading = false;
|
||||
},
|
||||
error: error => {
|
||||
console.error('Error al obtener el cliente:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateGeneralData() {
|
||||
this.generalData = [
|
||||
{ property: 'Nombre', value: this.clientData?.name || '' },
|
||||
{ property: 'IP', value: this.clientData?.ip || '' },
|
||||
{ property: 'MAC', value: this.clientData?.mac || '' },
|
||||
{ property: 'Nº de serie', value: this.clientData?.serialNumber || '' },
|
||||
{ property: 'Netiface', value: this.clientData?.netiface || this.clientData?.organizationalUnit?.networkSettings?.netiface || '' },
|
||||
{ property: 'Perfil hardware', value: this.clientData?.hardwareProfile?.description || '' },
|
||||
];
|
||||
}
|
||||
|
||||
updateNetworkData() {
|
||||
this.networkData = [
|
||||
{ property: 'Padre', value: this.clientData?.organizationalUnit?.name || '' },
|
||||
{ property: 'Pxe', value: this.clientData?.template?.name || '' },
|
||||
{ property: 'Remote Pc', value: this.clientData.remotePc || '' },
|
||||
{ property: 'Subred', value: this.clientData?.subnet || '' },
|
||||
{ property: 'OGlive', value: this.clientData?.ogLive?.name || '' },
|
||||
{ property: 'Repositorio', value: this.clientData?.repository?.name || '' },
|
||||
];
|
||||
}
|
||||
|
||||
calculateDiskUsage() {
|
||||
const diskUsageMap = new Map<number, { total: number, used: number, partitions: any[] }>();
|
||||
|
||||
this.partitions.forEach((partition: any) => {
|
||||
const diskNumber = partition.diskNumber;
|
||||
|
||||
if (!diskUsageMap.has(diskNumber)) {
|
||||
diskUsageMap.set(diskNumber, { total: 0, used: 0, partitions: [] });
|
||||
}
|
||||
|
||||
const diskData = diskUsageMap.get(diskNumber);
|
||||
|
||||
if (partition.partitionNumber === 0) {
|
||||
diskData!.total = Number((partition.size / 1024).toFixed(2));
|
||||
} else {
|
||||
diskData!.used += Number((partition.size / 1024).toFixed(2));
|
||||
diskData!.partitions.push(partition);
|
||||
}
|
||||
});
|
||||
|
||||
this.chartDisk = Array.from(diskUsageMap.entries()).map(([diskNumber, { total, used, partitions }]) => {
|
||||
const partitionData = partitions.map(partition => ({
|
||||
name: `Partición ${partition.partitionNumber}`,
|
||||
value: Number((partition.size / 1024).toFixed(2))
|
||||
}));
|
||||
|
||||
const freeSpace = total - used;
|
||||
if (freeSpace > 0) {
|
||||
partitionData.push({
|
||||
name: 'Espacio libre',
|
||||
value: Number(freeSpace.toFixed(2))
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
diskNumber,
|
||||
chartData: partitionData,
|
||||
total,
|
||||
used,
|
||||
percentage: total > 0 ? Math.round((used / total) * 100) : 0
|
||||
};
|
||||
});
|
||||
|
||||
this.diskUsageData = this.chartDisk;
|
||||
this.isDiskUsageEmpty = this.diskUsageData.length === 0;
|
||||
}
|
||||
|
||||
onEditClick(event: MouseEvent, uuid: string): void {
|
||||
event.stopPropagation();
|
||||
const dialogRef = this.dialog.open(ManageClientComponent, { data: { uuid }, width: '900px' });
|
||||
dialogRef.afterClosed().subscribe();
|
||||
}
|
||||
|
||||
loadPartitions(): void {
|
||||
if (!this.clientData?.id) {
|
||||
console.error('El ID del cliente no está disponible.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.http.get<any>(`${this.baseUrl}/partitions?client.id=${this.clientData.id}&order[diskNumber, partitionNumber]=ASC`).subscribe({
|
||||
next: data => {
|
||||
const filteredPartitions = data['hydra:member'].filter((partition: any) => partition.partitionNumber !== 0);
|
||||
this.dataSource = filteredPartitions;
|
||||
this.partitions = filteredPartitions;
|
||||
this.calculateDiskUsage();
|
||||
},
|
||||
error: error => {
|
||||
console.error('Error al obtener las particiones:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadCommands(): void {
|
||||
this.http.get<any>(`${this.baseUrl}/commands?`).subscribe({
|
||||
next: data => {
|
||||
this.commands = data['hydra:member'];
|
||||
},
|
||||
error: error => {
|
||||
console.error('Error al obtener las particiones:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onCommandSelect(action: any): void {
|
||||
if (action === 'partition') {
|
||||
this.openPartitionAssistant();
|
||||
}
|
||||
|
||||
if (action === 'create-image') {
|
||||
this.openCreateImageAssistant();
|
||||
}
|
||||
|
||||
if (action === 'deploy-image') {
|
||||
this.openDeployImageAssistant();
|
||||
}
|
||||
|
||||
if (action === 'reboot') {
|
||||
this.rebootClient();
|
||||
}
|
||||
|
||||
if (action === 'power-off') {
|
||||
this.powerOffClient();
|
||||
}
|
||||
|
||||
if (action === 'power-on') {
|
||||
this.powerOnClient();
|
||||
}
|
||||
}
|
||||
|
||||
rebootClient(): void {
|
||||
this.http.post(`${this.baseUrl}/clients/server/${this.clientData.uuid}/reboot`, {}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Cliente actualizado correctamente');
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error de conexión con el cliente');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
powerOnClient(): void {
|
||||
const payload = {
|
||||
client: this.clientData['@id']
|
||||
}
|
||||
|
||||
this.http.post(`${this.baseUrl}${this.clientData.repository['@id']}/wol`, payload).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Cliente actualizado correctamente');
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error de conexión con el cliente');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
powerOffClient(): void {
|
||||
this.http.post(`${this.baseUrl}/clients/server/${this.clientData.uuid}/power-off`, {}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Cliente actualizado correctamente');
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error de conexión con el cliente');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
openPartitionAssistant(): void {
|
||||
this.router.navigate([`/clients/${this.clientData.uuid}/partition-assistant`]).then(r => {
|
||||
console.log('navigated', r);
|
||||
});
|
||||
}
|
||||
|
||||
openCreateImageAssistant(): void {
|
||||
this.router.navigate([`/clients/${this.clientData.uuid}/create-image`]).then(r => {
|
||||
console.log('navigated', r);
|
||||
});
|
||||
}
|
||||
|
||||
openDeployImageAssistant(): void {
|
||||
this.router.navigate([`/clients/deploy-image`]).then(r => {
|
||||
console.log('navigated', r);
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue