Refactor groups component
parent
d8dad3b14b
commit
398e0ffa57
|
@ -130,13 +130,18 @@ mat-card-actions {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.details-placeholder {
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
.details-wrapper {
|
||||
width: 95%;
|
||||
padding: 20px;
|
||||
/* Asegúrate de que no haya propiedades que centren el contenido */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.details-placeholder {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
/* Elimina max-width si existe */
|
||||
/* max-width: none; */
|
||||
/* Otros estilos existentes */
|
||||
}
|
||||
|
||||
button[mat-raised-button] {
|
||||
|
@ -184,24 +189,15 @@ button[mat-raised-button] {
|
|||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.details-placeholder {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
mat-tree {
|
||||
width: 100%;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
border-right: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
button{
|
||||
margin: 5px;
|
||||
}
|
||||
mat-tree mat-tree-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -262,15 +258,15 @@ mat-tree mat-tree-node mat-icon.node-icon.organizational-unit {
|
|||
}
|
||||
|
||||
mat-tree mat-tree-node mat-icon.node-icon.classroom {
|
||||
color: #388e3c; /* Verde para aulas */
|
||||
color: #757575; /* Verde para aulas */
|
||||
}
|
||||
|
||||
mat-tree mat-tree-node mat-icon.node-icon.client {
|
||||
color: #f57c00; /* Naranja para clientes */
|
||||
color: #757575; /* Naranja para clientes */
|
||||
}
|
||||
|
||||
mat-tree mat-tree-node mat-icon.node-icon.group {
|
||||
color: #d32f2f; /* Rojo para grupos */
|
||||
color: #757575; /* Rojo para grupos */
|
||||
}
|
||||
|
||||
|
||||
|
@ -288,27 +284,6 @@ mat-tree mat-tree-node button.mat-icon-button.disabled-toggle:hover {
|
|||
background-color: transparent; /* Desactiva hover */
|
||||
}
|
||||
|
||||
mat-tree mat-tree-node mat-icon {
|
||||
margin-right: 10px;
|
||||
color: #757575;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
mat-tree mat-tree-node mat-icon.node-icon.organizational-unit {
|
||||
color: #1976d2; /* Azul para unidades organizativas */
|
||||
}
|
||||
|
||||
mat-tree mat-tree-node mat-icon.node-icon.classroom {
|
||||
color: #388e3c; /* Verde para aulas */
|
||||
}
|
||||
|
||||
mat-tree mat-tree-node mat-icon.node-icon.client {
|
||||
color: #f57c00; /* Naranja para clientes */
|
||||
}
|
||||
|
||||
mat-tree mat-tree-node mat-icon.node-icon.group {
|
||||
color: #d32f2f; /* Rojo para grupos */
|
||||
}
|
||||
|
||||
mat-tree mat-tree-node:hover {
|
||||
background-color: #e3f2fd;
|
||||
|
@ -322,3 +297,208 @@ mat-tree mat-tree-node.disabled {
|
|||
mat-tree mat-tree-node.disabled:hover {
|
||||
background-color: transparent; /* Desactiva hover */
|
||||
}
|
||||
|
||||
.mat-menu-item .mat-menu-item-submenu-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.filters-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.filters-container mat-form-field {
|
||||
flex: 1 1 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
.filter-container {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pc-og-live {
|
||||
/* Estilo para clientes con status 'og-live' */
|
||||
color: #4caf50; /* Verde */
|
||||
}
|
||||
|
||||
.pc-busy {
|
||||
/* Estilo para clientes ocupados */
|
||||
color: #ff9800; /* Naranja */
|
||||
}
|
||||
|
||||
.pc-windows {
|
||||
/* Estilo para clientes con Windows */
|
||||
color: #0078d7; /* Azul Windows */
|
||||
}
|
||||
|
||||
.pc-linux {
|
||||
/* Estilo para clientes con Linux */
|
||||
color: #f0ad4e; /* Amarillo */
|
||||
}
|
||||
|
||||
.pc-macos {
|
||||
/* Estilo para clientes con macOS */
|
||||
color: #999999; /* Gris */
|
||||
}
|
||||
|
||||
.pc-off {
|
||||
/* Estilo para clientes apagados */
|
||||
color: #f44336; /* Rojo */
|
||||
}
|
||||
|
||||
.clients-card-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.classroom-item {
|
||||
flex: 1 1 calc(25% - 16px); /* Ajusta este valor para controlar cuántas tarjetas caben en una fila */
|
||||
max-width: calc(25% - 16px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.classroom-pc {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.classroom-pc .pc-image {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.pc-details {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.pc-details .client-name,
|
||||
.pc-details .client-ip,
|
||||
.pc-details .client-mac {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.pc-actions button {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.tree-container {
|
||||
width: 25%; /* El árbol ocupa el 25% del ancho */
|
||||
padding: 16px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; /* Scroll si hay muchos nodos */
|
||||
}
|
||||
|
||||
.clients-container {
|
||||
width: 75%; /* Los clientes ocupan el 75% del ancho */
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto; /* Scroll si hay muchos clientes */
|
||||
}
|
||||
|
||||
|
||||
.clients-container h3 {
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.5em;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.client-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.client-card {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.client-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.client-image {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
|
||||
.client-details {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.client-name {
|
||||
display: block;
|
||||
font-size: 1.2em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
.client-ip {
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
button[mat-raised-button] {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.clients-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.clients-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.client-item-list {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.client-details-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<mat-tab-group (selectedTabChange)="onTabChange($event)">
|
||||
<mat-tab label="{{ 'generalTabLabel' | translate }}">
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header-container" joyrideStep="tabsStep" text="{{ 'tabsStepText' | translate }}">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
|
@ -22,11 +24,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mostrar tarjetas si no hay unidad seleccionada -->
|
||||
<!-- LAS CARDS DE LAS FACULTADES -->
|
||||
|
||||
<div *ngIf="!selectedUnidad; else detailsTemplate" class="card-container">
|
||||
<mat-card *ngFor="let unidad of organizationalUnits"
|
||||
[ngClass]="{'selected-item': unidad === selectedUnidad, 'clickable-item': true}"
|
||||
(click)="onSelectUnidad(unidad)" class="unidad-card small-card">
|
||||
[ngClass]="{'selected-item': unidad === selectedUnidad, 'clickable-item': true}"
|
||||
(click)="onSelectUnidad(unidad)" class="unidad-card small-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title>
|
||||
<mat-icon>apartment</mat-icon> {{ unidad.name }}
|
||||
|
@ -48,155 +51,167 @@
|
|||
<mat-icon matTooltip="{{ 'editUnitTooltip' | translate }}" matTooltipHideDelay="0">edit</mat-icon>
|
||||
<span>{{ 'editUnitMenu' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onShowClick($event, unidad)">
|
||||
<button mat-menu-item (click)="onShowDetailsClick($event, unidad)">
|
||||
<mat-icon matTooltip="{{ 'viewUnitTooltip' | translate }}" matTooltipHideDelay="0">visibility</mat-icon>
|
||||
<span>{{ 'viewUnitMenu' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addOU($event, unidad)">
|
||||
<mat-icon matTooltip="{{ 'addInternalUnitTooltip' | translate }}" matTooltipHideDelay="0">add_home_work</mat-icon>
|
||||
<mat-icon matTooltip="{{ 'addInternalUnitTooltip' | translate }}"
|
||||
matTooltipHideDelay="0">add_home_work</mat-icon>
|
||||
<span>{{ 'addInternalUnitMenu' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addClient($event, unidad)">
|
||||
<mat-icon matTooltip="{{ 'addClientTooltip' | translate }}" matTooltipHideDelay="0">devices</mat-icon>
|
||||
<span>{{ 'addClientMenu' | translate }}</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="onDeleteClick($event, unidad)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
<!-- Plantilla para detalles -->
|
||||
<!-- TEMPLATE QUE SALE DDESPUES DE LAS CARDs CON EL ARBOL Y PCS -->
|
||||
|
||||
<ng-template #detailsTemplate>
|
||||
<button mat-raised-button color="primary" (click)="clearSelection()">
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
{{ 'Back' | translate }}
|
||||
</button>
|
||||
<div>
|
||||
<div class="details-placeholder">
|
||||
<h2>{{ 'Details of' | translate }} {{ selectedUnidad?.name }}</h2>
|
||||
<!-- Árbol jerárquico -->
|
||||
<mat-tree [dataSource]="treeDataSource" [treeControl]="treeControl" class="tree-container">
|
||||
<!-- Nodo expandible -->
|
||||
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
|
||||
<button
|
||||
mat-icon-button
|
||||
matTreeNodeToggle
|
||||
[disabled]="!node.expandable"
|
||||
<div class="main-container">
|
||||
<!-- Contenedor del árbol -->
|
||||
|
||||
<div class="tree-container">
|
||||
|
||||
<h2>{{ selectedUnidad?.name }}</h2>
|
||||
<mat-tree [dataSource]="treeDataSource" [treeControl]="treeControl">
|
||||
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding (click)="onNodeClick(node)">
|
||||
<button mat-icon-button matTreeNodeToggle [disabled]="!node.expandable"
|
||||
[ngClass]="{'disabled-toggle': !node.expandable}">
|
||||
<mat-icon>
|
||||
{{
|
||||
treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'
|
||||
}}
|
||||
{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
|
||||
</mat-icon>
|
||||
</button>
|
||||
<mat-icon class="node-icon {{ node.type }}">
|
||||
{{
|
||||
node.type === 'organizational-unit'
|
||||
? 'apartment'
|
||||
: node.type === 'classroom'
|
||||
? 'school'
|
||||
: node.type === 'client'
|
||||
? 'computer'
|
||||
: 'group'
|
||||
node.type === 'organizational-unit'
|
||||
? 'apartment'
|
||||
: node.type === 'classroom'
|
||||
? 'school'
|
||||
: node.type === 'client'
|
||||
? 'computer'
|
||||
: 'group'
|
||||
}}
|
||||
</mat-icon>
|
||||
<span>{{ node.name }} ({{ node.type }})</span>
|
||||
<span>{{ node.name }}</span>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu" (click)="setSelectedNode(node)">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-tree-node>
|
||||
|
||||
<mat-tree-node *matTreeNodeDef="let node; when: isLeafNode" matTreeNodePadding>
|
||||
<button
|
||||
mat-icon-button
|
||||
matTreeNodeToggle
|
||||
[disabled]="true"
|
||||
class="disabled-toggle">
|
||||
<mat-icon>
|
||||
{{
|
||||
node.type === 'client' ? '' : 'chevron_right'
|
||||
}}
|
||||
</mat-icon>
|
||||
|
||||
<mat-tree-node *matTreeNodeDef="let node; when: isLeafNode" matTreeNodePadding (click)="onNodeClick(node)">
|
||||
<button mat-icon-button matTreeNodeToggle [disabled]="true" class="disabled-toggle">
|
||||
|
||||
</button>
|
||||
<mat-icon class="node-icon {{ node.type }}">
|
||||
<mat-icon [ngClass]="{
|
||||
'pc-og-live': node.type === 'client' && node.status === 'og-live',
|
||||
'pc-busy': node.type === 'client' && node.status === 'busy',
|
||||
'pc-windows': node.type === 'client' && (node.status === 'windows' || node.status === 'windows-session'),
|
||||
'pc-linux': node.type === 'client' && (node.status === 'linux' || node.status === 'linux-session'),
|
||||
'pc-macos': node.type === 'client' && node.status === 'macos',
|
||||
'pc-off': node.type === 'client' && node.status === 'off'
|
||||
}">
|
||||
{{
|
||||
node.type === 'organizational-unit'
|
||||
? 'apartment'
|
||||
: node.type === 'classroom'
|
||||
? 'school'
|
||||
: node.type === 'client'
|
||||
? 'computer'
|
||||
: 'group'
|
||||
node.type === 'organizational-unit'
|
||||
? 'apartment'
|
||||
: node.type === 'classroom'
|
||||
? 'school'
|
||||
: node.type === 'client'
|
||||
? 'computer'
|
||||
: 'group'
|
||||
}}
|
||||
</mat-icon>
|
||||
<span>{{ node.name }} ({{ node.type }})</span>
|
||||
<span>{{ node.name }}</span>
|
||||
<ng-container *ngIf="node.type === 'client'">
|
||||
<span>- IP: {{ node.ip }}</span>
|
||||
</ng-container>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu" (click)="setSelectedNode(node)">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-tree-node>
|
||||
|
||||
|
||||
<mat-tree-node *matTreeNodeDef="let node; when: isLeafNode" matTreeNodePadding>
|
||||
<mat-icon class="node-icon {{ node.type }}">
|
||||
{{
|
||||
node.type === 'organizational-unit'
|
||||
? 'apartment'
|
||||
: node.type === 'classroom'
|
||||
? 'school'
|
||||
: node.type === 'client'
|
||||
? 'computer'
|
||||
: 'group'
|
||||
}}
|
||||
</mat-icon>
|
||||
<span>{{ node.name }} ({{ node.type }})</span>
|
||||
<ng-container *ngIf="node.type === 'client'">
|
||||
<span>- IP: {{ node.ip }}</span>
|
||||
</ng-container>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu" (click)="setSelectedNode(node)">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-tree-node>
|
||||
|
||||
|
||||
<!-- Nodo hoja -->
|
||||
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
|
||||
<mat-icon *ngIf="node.type === 'client'">computer</mat-icon>
|
||||
<mat-icon *ngIf="node.type !== 'client'">device_hub</mat-icon>
|
||||
<span>{{ node.name }} ({{ node.type }})</span>
|
||||
<ng-container *ngIf="node.type === 'client'">
|
||||
<span>- IP: {{ node.ip }}</span>
|
||||
<span> - IP: {{ node.ip }}</span>
|
||||
</ng-container>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu" (click)="setSelectedNode(node)">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
||||
|
||||
<!-- Menú desplegable -->
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item (click)="onEditNode($event, selectedNode)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDelete(selectedNode)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onCustomAction(selectedNode)">
|
||||
<mat-icon>settings</mat-icon>
|
||||
<span>Custom Action</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<mat-menu restoreFocus = fasle #commandMenu="matMenu">
|
||||
<button mat-menu-item *ngFor="let command of commands" (click)="executeCommand(command, selectedNode)">
|
||||
<span>{{ command.name }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
<!-- Menú desplegable -->
|
||||
<mat-menu #menu="matMenu">
|
||||
|
||||
<button *ngIf="selectedNode?.type === 'classroom'" mat-menu-item [matMenuTriggerFor]="commandMenu"
|
||||
(click)="fetchCommands()">
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
<span>Ejecutar Comando</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onShowDetailsClick($event, selectedNode)">
|
||||
<mat-icon matTooltip="{{ 'viewUnitTooltip' | translate }}" matTooltipHideDelay="0">visibility</mat-icon>
|
||||
<span>{{ 'viewUnitMenu' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addClient($event, selectedNode)">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span>Añadir clientes</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onEditNode($event, selectedNode)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, selectedNode)" >
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
<!-- Contenedor de clientes -->
|
||||
<div class="clients-container" *ngIf="selectedClients.length > 0">
|
||||
<h3>Clientes del {{ selectedNode?.name }}</h3>
|
||||
<div class="clients-grid">
|
||||
<div *ngFor="let client of selectedClients" class="client-item">
|
||||
<div class="client-card">
|
||||
<img src="assets/images/client.png" alt="Client Icon" class="client-image" />
|
||||
<div class="client-details">
|
||||
<span class="client-name">{{ client.name }}</span>
|
||||
<span class="client-ip">{{ client.ip }}</span>
|
||||
|
||||
<button mat-raised-button color="primary" [matMenuTriggerFor]="clientMenu">Acciones</button>
|
||||
<mat-menu #clientMenu="matMenu">
|
||||
<button mat-menu-item>
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, client, selectedNode)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
<button mat-menu-item>
|
||||
<mat-icon>visibility</mat-icon>
|
||||
<span>Ver detalles</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
|
||||
<!-- FIN DEL TAB -->
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="{{ 'advancedSearchTabLabel' | translate }}">
|
||||
|
|
|
@ -17,6 +17,11 @@ import { TreeViewComponent } from './shared/tree-view/tree-view.component';
|
|||
import { LegendComponent } from './shared/legend/legend.component';
|
||||
import { ClientTabViewComponent } from './components/client-tab-view/client-tab-view.component';
|
||||
import { OrganizationalUnitTabViewComponent } from './components/organizational-unit-tab-view/organizational-unit-tab-view.component';
|
||||
import { MatMenuTrigger } from '@angular/material/menu';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Router } from '@angular/router';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { DeleteModalComponent } from '../../shared/delete_modal/delete-modal/delete-modal.component';
|
||||
|
||||
interface TreeNode {
|
||||
name: string;
|
||||
|
@ -24,6 +29,7 @@ interface TreeNode {
|
|||
children?: TreeNode[];
|
||||
ip?: string;
|
||||
'@id'?: string;
|
||||
hasClients?: boolean;
|
||||
}
|
||||
|
||||
interface FlatNode {
|
||||
|
@ -32,6 +38,7 @@ interface FlatNode {
|
|||
level: number;
|
||||
expandable: boolean;
|
||||
ip?: string;
|
||||
hasClients?: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
@ -47,20 +54,26 @@ export class GroupsComponent implements OnInit {
|
|||
loading: boolean = false;
|
||||
loadingChildren: boolean = false;
|
||||
searchTerm: string = '';
|
||||
|
||||
treeControl: FlatTreeControl<FlatNode>;
|
||||
treeFlattener: MatTreeFlattener<TreeNode, FlatNode>;
|
||||
treeDataSource: MatTreeFlatDataSource<TreeNode, FlatNode>;
|
||||
selectedNode: TreeNode | null = null;
|
||||
commands: any[] = [];
|
||||
commandsLoading: boolean = false;
|
||||
selectedClients: any[] = [];
|
||||
cols: number = 4;
|
||||
|
||||
@ViewChild('clientTab') clientTabComponent!: ClientTabViewComponent;
|
||||
@ViewChild('organizationalUnitTab') organizationalUnitTabComponent!: OrganizationalUnitTabViewComponent;
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private router: Router,
|
||||
private dataService: DataService,
|
||||
public dialog: MatDialog,
|
||||
private _bottomSheet: MatBottomSheet,
|
||||
private joyrideService: JoyrideService
|
||||
private joyrideService: JoyrideService,
|
||||
private toastr: ToastrService
|
||||
) {
|
||||
this.treeFlattener = new MatTreeFlattener<TreeNode, FlatNode>(
|
||||
(node: TreeNode, level: number) => ({
|
||||
|
@ -68,6 +81,7 @@ export class GroupsComponent implements OnInit {
|
|||
type: node.type,
|
||||
level,
|
||||
expandable: !!node.children?.length,
|
||||
hasClients: node.hasClients,
|
||||
ip: node.ip,
|
||||
['@id']: node['@id']
|
||||
}),
|
||||
|
@ -87,6 +101,21 @@ export class GroupsComponent implements OnInit {
|
|||
ngOnInit(): void {
|
||||
this.search();
|
||||
this.getFilters();
|
||||
this.updateGridCols();
|
||||
window.addEventListener('resize', () => this.updateGridCols());
|
||||
}
|
||||
|
||||
updateGridCols(): void {
|
||||
const width = window.innerWidth;
|
||||
if (width <= 600) {
|
||||
this.cols = 1;
|
||||
} else if (width <= 960) {
|
||||
this.cols = 2;
|
||||
} else if (width <= 1280) {
|
||||
this.cols = 3;
|
||||
} else {
|
||||
this.cols = 4;
|
||||
}
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
|
@ -131,6 +160,7 @@ export class GroupsComponent implements OnInit {
|
|||
onSelectUnidad(unidad: UnidadOrganizativa): void {
|
||||
this.selectedUnidad = unidad;
|
||||
this.selectedDetail = unidad;
|
||||
console.log('Selected unidad:', unidad);
|
||||
|
||||
this.loadChildrenAndClients(unidad.id).then(fullData => {
|
||||
const treeData = this.convertToTreeData(fullData);
|
||||
|
@ -141,35 +171,24 @@ export class GroupsComponent implements OnInit {
|
|||
async loadChildrenAndClients(id: string): Promise<any> {
|
||||
try {
|
||||
const childrenData = await this.dataService.getChildren(id).toPromise();
|
||||
const clientsData = await this.dataService.getClients(id).toPromise();
|
||||
|
||||
|
||||
const processHierarchy = (nodes: UnidadOrganizativa[]): TreeNode[] => {
|
||||
return nodes.map(node => ({
|
||||
name: node.name,
|
||||
type: node.type,
|
||||
'@id': node['@id'],
|
||||
children: [
|
||||
...(node.children ? processHierarchy(node.children) : []),
|
||||
...(node.clients
|
||||
? node.clients.map((client: any) => ({
|
||||
name: client.name,
|
||||
type: 'client',
|
||||
'@id': client['@id'],
|
||||
ip: client.ip
|
||||
}))
|
||||
: [])
|
||||
],
|
||||
ip: node.type === 'client' ? (node as any).ip : undefined
|
||||
children: node.children ? processHierarchy(node.children) : [],
|
||||
clients: node.clients || []
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
|
||||
return {
|
||||
...this.selectedUnidad,
|
||||
children: childrenData ? processHierarchy(childrenData) : [],
|
||||
clients: clientsData || []
|
||||
children: childrenData ? processHierarchy(childrenData) : []
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error loading children and clients:', error);
|
||||
console.error('Error loading children:', error);
|
||||
return this.selectedUnidad;
|
||||
}
|
||||
}
|
||||
|
@ -179,23 +198,43 @@ export class GroupsComponent implements OnInit {
|
|||
name: node.name,
|
||||
type: node.type,
|
||||
'@id': node['@id'],
|
||||
children: [
|
||||
...(node.children?.map(processNode) || []),
|
||||
...(node.clients
|
||||
? node.clients.map((client: any) => ({
|
||||
name: client.name,
|
||||
type: 'client',
|
||||
'@id': client['@id'],
|
||||
ip: client.ip
|
||||
}))
|
||||
: [])
|
||||
],
|
||||
ip: node.type === 'client' ? (node as any).ip : undefined
|
||||
children: node.children?.map(processNode) || [],
|
||||
hasClients: node.clients && node.clients.length > 0,
|
||||
});
|
||||
|
||||
return [processNode(data)];
|
||||
}
|
||||
|
||||
onNodeClick(node: TreeNode): void {
|
||||
console.log('Node clicked:', node);
|
||||
this.selectedNode = node;
|
||||
|
||||
if (node.hasClients) {
|
||||
const url = `${this.baseUrl}${node['@id']}`;
|
||||
this.http.get(url).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Clients fetched:', data);
|
||||
this.selectedClients = data.clients || [];
|
||||
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching clients:', error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.selectedClients = [];
|
||||
}
|
||||
}
|
||||
|
||||
getNodeIcon(node: any): string {
|
||||
switch (node.type) {
|
||||
case 'organizational-unit': return 'apartment';
|
||||
case 'classroom': return 'school';
|
||||
case 'client': return 'computer';
|
||||
default: return 'group';
|
||||
}
|
||||
}
|
||||
|
||||
addOU(event: MouseEvent, parent: any = null): void {
|
||||
event.stopPropagation();
|
||||
const dialogRef = this.dialog.open(CreateOrganizationalUnitComponent, { data: { parent }, width: '900px' });
|
||||
|
@ -245,13 +284,86 @@ export class GroupsComponent implements OnInit {
|
|||
}
|
||||
|
||||
onDelete(node: TreeNode | null): void {
|
||||
if (!node) return;
|
||||
// Additional logic for deleting
|
||||
console.log('Deleting node:', node);
|
||||
}
|
||||
|
||||
onCustomAction(node: TreeNode | null): void {
|
||||
if (!node) return;
|
||||
// Logic for custom actions
|
||||
onDeleteClick(event: MouseEvent, node: TreeNode | null, clientNode?: TreeNode | null): void {
|
||||
console.log('Deleting node or client:', node);
|
||||
|
||||
const uuid = node && node['@id'] ? node['@id'].split('/').pop() || '' : '';
|
||||
const name = node?.name || 'Elemento desconocido';
|
||||
const type = node?.type || '';
|
||||
|
||||
event.stopPropagation();
|
||||
|
||||
const dialogRef = this.dialog.open(DeleteModalComponent, {
|
||||
width: '400px',
|
||||
data: { name }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result === true) {
|
||||
this.dataService.deleteElement(uuid, type).subscribe(
|
||||
() => {
|
||||
console.log('Entity deleted successfully:', uuid);
|
||||
|
||||
this.loadChildrenAndClients(this.selectedUnidad?.id || '').then(updatedData => {
|
||||
const treeData = this.convertToTreeData(updatedData);
|
||||
this.treeDataSource.data = treeData[0]?.children || [];
|
||||
});
|
||||
|
||||
if (type === 'client' && clientNode) {
|
||||
console.log('Refreshing clients for node:', clientNode);
|
||||
this.refreshClients(clientNode);
|
||||
}
|
||||
|
||||
this.dataService.getOrganizationalUnits().subscribe(
|
||||
data => {
|
||||
this.organizationalUnits = data;
|
||||
},
|
||||
error => console.error('Error fetching unidades organizativas:', error)
|
||||
);
|
||||
|
||||
this.toastr.success('Entidad eliminada exitosamente');
|
||||
},
|
||||
error => {
|
||||
console.error('Error deleting entity:', error);
|
||||
this.toastr.error('Error al eliminar la entidad', error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private refreshClients(node: TreeNode): void {
|
||||
if (!node || !node['@id']) {
|
||||
console.warn('Node or @id is missing, clearing clients.');
|
||||
this.selectedClients = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `${this.baseUrl}${node['@id']}`;
|
||||
console.log('Fetching clients for node with URL:', url);
|
||||
|
||||
this.http.get(url).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Response data:', data);
|
||||
if (data && Array.isArray(data.clients)) {
|
||||
this.selectedClients = data.clients;
|
||||
console.log('Clients updated successfully:', this.selectedClients);
|
||||
} else {
|
||||
console.warn('No "clients" field found in response, clearing clients.');
|
||||
this.selectedClients = [];
|
||||
}
|
||||
},
|
||||
error => {
|
||||
console.error('Error refreshing clients:', error);
|
||||
const errorMessage = error.status === 404
|
||||
? 'No se encontraron clientes para este nodo.'
|
||||
: 'Error al comunicarse con el servidor.';
|
||||
this.toastr.error(errorMessage);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onEditClick(event: MouseEvent, type: any, uuid: string): void {
|
||||
|
@ -263,11 +375,37 @@ export class GroupsComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
onShowClick(event: MouseEvent, data: any): void {
|
||||
fetchCommands(): void {
|
||||
this.commandsLoading = true;
|
||||
this.http.get('https://127.0.0.1:8443/commands?page=1&itemsPerPage=30').subscribe(
|
||||
(response: any) => {
|
||||
this.commands = response['hydra:member'];
|
||||
this.commandsLoading = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching commands:', error);
|
||||
this.commandsLoading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
executeCommand(command: any, selectedNode: any): void {
|
||||
console.log('Executing command:', command.name);
|
||||
this.toastr.success('Ejecutando comando: ' + command.name+ " en "+ selectedNode.name ) ;
|
||||
}
|
||||
|
||||
onClientActions(client: any): void {
|
||||
console.log('Client actions:', client);
|
||||
}
|
||||
|
||||
onShowDetailsClick(event: MouseEvent, data: any): void {
|
||||
event.stopPropagation();
|
||||
if (data.type != 'client') {
|
||||
this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '700px' });
|
||||
}
|
||||
if (data.type == 'client') {
|
||||
this.router.navigate(['clients', data['@id'].split('/').pop()], { state: { clientData: data } });
|
||||
}
|
||||
}
|
||||
|
||||
onTreeClick(event: MouseEvent, data: any): void {
|
||||
|
@ -291,5 +429,4 @@ export class GroupsComponent implements OnInit {
|
|||
|
||||
hasChild = (_: number, node: FlatNode): boolean => node.expandable;
|
||||
isLeafNode = (_: number, node: FlatNode): boolean => !node.expandable;
|
||||
|
||||
}
|
||||
|
|
|
@ -90,6 +90,8 @@ export class DataService {
|
|||
const url = type === 'client'
|
||||
? `${this.baseUrl}/clients/${uuid}`
|
||||
: `${this.baseUrl}/organizational-units/${uuid}`;
|
||||
|
||||
console.log('DELETE URL:', url); // Depuración
|
||||
return this.http.delete<void>(url).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error deleting element', error);
|
||||
|
@ -97,6 +99,7 @@ export class DataService {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
changeParent(uuid: string): Observable<void> {
|
||||
const url = `${this.baseUrl}/organizational-units/${uuid}/change-parent`;
|
||||
|
|
|
@ -1,121 +1,168 @@
|
|||
h1 {
|
||||
text-align: center;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 400;
|
||||
color: #3f51b5;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.network-form {
|
||||
/* Contenedor principal */
|
||||
.create-client-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
padding: 16px;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
/* Títulos */
|
||||
h1, h3, h4 {
|
||||
margin: 0 0 16px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
padding-left: 50px;
|
||||
padding-right: 20px;
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
text-transform: none;
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mat-slide-toggle {
|
||||
margin-top: 20px;
|
||||
/* Espaciado entre contenedores */
|
||||
.inputs-container {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
mat-option .unit-name {
|
||||
display: block;
|
||||
/* Contenedor de cada sección */
|
||||
.mat-dialog-content {
|
||||
flex: 1;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
mat-option .unit-path {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
color: gray;
|
||||
.create-multiple-client-container {
|
||||
flex: 1;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.create-client-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.grid-form {
|
||||
/* Campos del formulario */
|
||||
.client-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.inputs-container {
|
||||
display: inline-flex;
|
||||
gap: 20px;
|
||||
padding-left: 50px;
|
||||
padding-right: 20px;
|
||||
.mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Tabla */
|
||||
.scrollable-table {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
margin-top: 16px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f1f1f1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
/* Botones */
|
||||
button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
button:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
button.mat-raised-button {
|
||||
text-transform: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Carga */
|
||||
.loading-spinner {
|
||||
margin: 16px auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Alternar formularios */
|
||||
.toggle-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #007BFF;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.toggle-button:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Mejoras generales */
|
||||
.mat-divider {
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.upload-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
button[mat-raised-button] {
|
||||
font-size: 14px;
|
||||
text-transform: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button[mat-raised-button]:hover {
|
||||
background-color: #303f9f;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
/* Responsivo */
|
||||
@media (max-width: 768px) {
|
||||
.inputs-container {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* Contenedor para la tabla con scroll */
|
||||
.scrollable-table {
|
||||
max-height: 300px; /* Altura máxima de la tabla */
|
||||
overflow-y: auto; /* Habilitar scroll vertical */
|
||||
overflow-x: hidden; /* Ocultar scroll horizontal si no es necesario */
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.mat-dialog-content, .create-multiple-client-container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/* Tabla */
|
||||
.mat-elevation-z8 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.client-table th, .client-table td {
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.client-table th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
z-index: 2;
|
||||
.scrollable-table {
|
||||
max-height: 150px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
<div class="create-client-container">
|
||||
<h1 mat-dialog-title>{{ 'addClientDialogTitle' | translate }}</h1>
|
||||
|
||||
<div class="inputs-container">
|
||||
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
||||
<h3>Añadir un cliente</h3>
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
|
||||
<form [formGroup]="clientForm" class="client-form grid-form" *ngIf="!loading">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'organizationalUnitLabel' | translate }}</mat-label>
|
||||
|
@ -15,117 +12,123 @@
|
|||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'nameLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="name">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'ogLiveLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="ogLive">
|
||||
<mat-option *ngFor="let oglive of ogLives" [value]="oglive['@id']">
|
||||
{{ oglive.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'serialNumberLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="serialNumber">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'netifaceLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="netiface">
|
||||
<mat-option *ngFor="let type of netifaceTypes" [value]="type.value">
|
||||
{{ type.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'netDriverLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="netDriver">
|
||||
<mat-option *ngFor="let type of netDriverTypes" [value]="type.value">
|
||||
{{ type.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'macLabel' | translate }}</mat-label>
|
||||
<mat-hint>{{ 'macHint' | translate }}</mat-hint>
|
||||
<input matInput formControlName="mac">
|
||||
<mat-error>{{ 'macError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'ipLabel' | translate }}</mat-label>
|
||||
<mat-hint>{{ 'ipHint' | translate }}</mat-hint>
|
||||
<input matInput formControlName="ip">
|
||||
<mat-error>{{ 'ipError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'templateLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="template">
|
||||
<mat-option *ngFor="let template of templates" [value]="template['@id']">
|
||||
{{ template.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'hardwareProfileLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="hardwareProfile">
|
||||
<mat-option *ngFor="let unit of hardwareProfiles" [value]="unit['@id']">
|
||||
{{ unit.description }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>{{ 'hardwareProfileError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<mat-divider vertical></mat-divider>
|
||||
<div *ngIf="!isSingleClientForm; else singleClientForm">
|
||||
<h3>Añadir múltiples clientes</h3>
|
||||
<div class="upload-container">
|
||||
<button mat-raised-button color="primary" (click)="fileInput.click()">Subir fichero</button>
|
||||
<input #fileInput type="file" (change)="onFileUpload($event)" accept=".csv" hidden>
|
||||
<p>o añadelos manualmente:</p>
|
||||
<textarea #textarea matInput placeholder="host bbaa-it1-1..." rows="20" cols="100"></textarea>
|
||||
<button mat-raised-button color="primary" (click)="onTextarea(textarea.value)">cargar</button>
|
||||
</div>
|
||||
|
||||
<div class="create-multiple-client-container">
|
||||
<h3>Añadir multiples clientes</h3>
|
||||
<div class="upload-container">
|
||||
<button mat-raised-button color="primary" (click)="fileInput.click()">Subir fichero</button>
|
||||
<input #fileInput type="file" (change)="onFileUpload($event)" accept=".csv" hidden>
|
||||
<h4 *ngIf="uploadedClients.length > 0">Clientes importados:</h4>
|
||||
<div class="scrollable-table">
|
||||
<table mat-table [dataSource]="uploadedClients" class="mat-elevation-z8" *ngIf="uploadedClients.length > 0">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> Nombre </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.name }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="ip">
|
||||
<th mat-header-cell *matHeaderCellDef> IP </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.ip }} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h4 *ngIf="uploadedClients.length > 0">Clientes importados:</h4>
|
||||
<div class="scrollable-table">
|
||||
<table mat-table [dataSource]="uploadedClients" class="mat-elevation-z8" *ngIf="uploadedClients.length > 0">
|
||||
|
||||
<!-- Añadir uon cliente -->
|
||||
|
||||
<ng-template #singleClientForm>
|
||||
<h3>Añadir un cliente</h3>
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
<form [formGroup]="clientForm" class="client-form grid-form" *ngIf="!loading">
|
||||
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> Nombre </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.name }} </td>
|
||||
</ng-container>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'nameLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="name">
|
||||
</mat-form-field>
|
||||
|
||||
<ng-container matColumnDef="ip">
|
||||
<th mat-header-cell *matHeaderCellDef> IP </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.ip }} </td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'ogLiveLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="ogLive">
|
||||
<mat-option *ngFor="let oglive of ogLives" [value]="oglive['@id']">
|
||||
{{ oglive.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'serialNumberLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="serialNumber">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'netifaceLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="netiface">
|
||||
<mat-option *ngFor="let type of netifaceTypes" [value]="type.value">
|
||||
{{ type.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'netDriverLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="netDriver">
|
||||
<mat-option *ngFor="let type of netDriverTypes" [value]="type.value">
|
||||
{{ type.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'macLabel' | translate }}</mat-label>
|
||||
<mat-hint>{{ 'macHint' | translate }}</mat-hint>
|
||||
<input matInput formControlName="mac">
|
||||
<mat-error>{{ 'macError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'ipLabel' | translate }}</mat-label>
|
||||
<mat-hint>{{ 'ipHint' | translate }}</mat-hint>
|
||||
<input matInput formControlName="ip">
|
||||
<mat-error>{{ 'ipError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'templateLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="template">
|
||||
<mat-option *ngFor="let template of templates" [value]="template['@id']">
|
||||
{{ template.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'hardwareProfileLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="hardwareProfile">
|
||||
<mat-option *ngFor="let unit of hardwareProfiles" [value]="unit['@id']">
|
||||
{{ unit.description }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>{{ 'hardwareProfileError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button (click)="toggleClientForm()">
|
||||
{{ isSingleClientForm ? 'Añadir múltiples clientes' : 'Añadir un único cliente' }}
|
||||
</button>
|
||||
<button mat-button (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||
<button mat-button (click)="onSubmit()">{{ 'addButton' | translate }}</button>
|
||||
<button mat-button (click)="onSubmit()">{{ 'addButton' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -22,6 +22,7 @@ export class CreateClientComponent implements OnInit {
|
|||
uploadedClients: any[] = [];
|
||||
loading: boolean = false;
|
||||
displayedColumns: string[] = ['name', 'ip'];
|
||||
isSingleClientForm: boolean = false;
|
||||
protected netifaceTypes = [
|
||||
{ name: 'Eth0', value: 'eth0' },
|
||||
{ name: 'Eth1', value: 'eth1' },
|
||||
|
@ -128,46 +129,109 @@ export class CreateClientComponent implements OnInit {
|
|||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
|
||||
// Leer archivo como texto
|
||||
|
||||
reader.onload = (e: any) => {
|
||||
const csvData = e.target.result;
|
||||
|
||||
// Usar PapaParse para convertir CSV a JSON
|
||||
Papa.parse(csvData, {
|
||||
header: true, // Utilizar la primera fila como encabezados
|
||||
skipEmptyLines: true, // Ignorar líneas vacías
|
||||
complete: (result) => {
|
||||
this.uploadedClients = result.data;
|
||||
this.toastService.success('Archivo CSV cargado correctamente', 'Éxito');
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al procesar el archivo CSV:', error);
|
||||
this.toastService.error('Error al procesar el archivo CSV', 'Error');
|
||||
}
|
||||
});
|
||||
const textData = e.target.result;
|
||||
const regex = /host\s+(\S+)\s+\{\s+hardware\s+ethernet\s+([\da-fA-F:]+);\s+fixed-address\s+([\d.]+);\s+\}/g;
|
||||
let match;
|
||||
const clients = [];
|
||||
|
||||
while ((match = regex.exec(textData)) !== null) {
|
||||
clients.push({
|
||||
name: match[1],
|
||||
mac: match[2],
|
||||
ip: match[3]
|
||||
});
|
||||
}
|
||||
|
||||
if (clients.length > 0) {
|
||||
this.uploadedClients = clients;
|
||||
this.toastService.success('Archivo cargado correctamente, los datos están listos para enviarse.', 'Éxito');
|
||||
} else {
|
||||
this.toastService.error('No se encontraron datos válidos', 'Error');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.clientForm.valid) {
|
||||
const formData = this.clientForm.value;
|
||||
this.http.post(`${this.baseUrl}/clients`, formData).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Cliente creado exitosamente', 'Éxito');
|
||||
this.dialogRef.close(response);
|
||||
},
|
||||
error => {
|
||||
console.error('Error during POST:', error);
|
||||
this.toastService.error('Error al crear el cliente', 'Error');
|
||||
}
|
||||
);
|
||||
|
||||
onTextarea(text: string): void {
|
||||
const regex = /host\s+(\S+)\s+\{\s+hardware\s+ethernet\s+([\da-fA-F:]+);\s+fixed-address\s+([\d.]+);\s+\}/g;
|
||||
let match;
|
||||
const clients = [];
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
clients.push({
|
||||
name: match[1],
|
||||
mac: match[2],
|
||||
ip: match[3]
|
||||
});
|
||||
}
|
||||
|
||||
if (clients.length > 0) {
|
||||
this.uploadedClients = clients;
|
||||
this.toastService.success('Datos cargados correctamente, los datos están listos para enviarse.', 'Éxito');
|
||||
} else {
|
||||
this.toastService.error('No se encontraron datos válidos', 'Error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.isSingleClientForm) {
|
||||
if (this.clientForm.valid) {
|
||||
const formData = this.clientForm.value;
|
||||
console.log('Form data:', formData);
|
||||
this.http.post(`${this.baseUrl}/clients`, formData).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Cliente creado exitosamente', 'Éxito');
|
||||
this.dialogRef.close(response);
|
||||
},
|
||||
error => {
|
||||
console.error('Error durante POST:', error);
|
||||
this.toastService.error('Error al crear el cliente', 'Error');
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (this.uploadedClients.length > 0) {
|
||||
this.uploadedClients.forEach(client => {
|
||||
const formData = {
|
||||
organizationalUnit: this.clientForm.value.organizationalUnit || null,
|
||||
name: client.name || null,
|
||||
mac: client.mac || null,
|
||||
ip: client.ip || null,
|
||||
template: this.clientForm.value.template || null,
|
||||
hardwareProfile: this.clientForm.value.hardwareProfile || null,
|
||||
ogLive: this.clientForm.value.ogLive || null,
|
||||
serialNumber: null,
|
||||
netiface: null,
|
||||
netDriver: null
|
||||
};
|
||||
|
||||
this.http.post(`${this.baseUrl}/clients`, formData).subscribe(
|
||||
response => {
|
||||
this.toastService.success(`Cliente ${client.name} creado exitosamente`, 'Éxito');
|
||||
},
|
||||
error => {
|
||||
console.error(`Error al crear el cliente ${client.name}:`, error);
|
||||
this.toastService.error(`Error al crear el cliente ${client.name}`, 'Error');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.uploadedClients = [];
|
||||
this.dialogRef.close();
|
||||
} else {
|
||||
this.toastService.error('No hay clientes cargados para añadir', 'Error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleClientForm(): void {
|
||||
this.isSingleClientForm = !this.isSingleClientForm;
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
|
|
@ -419,5 +419,6 @@
|
|||
"menus": "Menus",
|
||||
"TOOLTIP_MENUS": "Menu management (option disabled)",
|
||||
"search": "Search",
|
||||
"TOOLTIP_SEARCH": "Search function (option disabled)"
|
||||
"TOOLTIP_SEARCH": "Search function (option disabled)",
|
||||
"detailsOf": "Details of"
|
||||
}
|
||||
|
|
|
@ -420,5 +420,6 @@
|
|||
"menus": "Menús",
|
||||
"TOOLTIP_MENUS": "Gestión de menús (opción deshabilitada)",
|
||||
"search": "Buscar",
|
||||
"TOOLTIP_SEARCH": "Función de búsqueda (opción deshabilitada)"
|
||||
"TOOLTIP_SEARCH": "Función de búsqueda (opción deshabilitada)",
|
||||
"detailsOf": "Detalles de"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue