From 398e0ffa57166b01a62c7e57e72513319643e9e0 Mon Sep 17 00:00:00 2001 From: apuente Date: Sun, 1 Dec 2024 00:55:38 +0100 Subject: [PATCH] Refactor groups component --- .../components/groups/groups.component.css | 264 +++++++++++++++--- .../components/groups/groups.component.html | 225 ++++++++------- .../app/components/groups/groups.component.ts | 215 +++++++++++--- .../groups/services/data.service.ts | 3 + .../create-client/create-client.component.css | 219 +++++++++------ .../create-client.component.html | 209 +++++++------- .../create-client/create-client.component.ts | 130 ++++++--- ogWebconsole/src/locale/en.json | 3 +- ogWebconsole/src/locale/es.json | 3 +- 9 files changed, 861 insertions(+), 410 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.css b/ogWebconsole/src/app/components/groups/groups.component.css index f7b4608..f40ad86 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.css +++ b/ogWebconsole/src/app/components/groups/groups.component.css @@ -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; +} diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index d2d2d6f..a4c529c 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -1,5 +1,7 @@ + +
- + +
+ [ngClass]="{'selected-item': unidad === selectedUnidad, 'clickable-item': true}" + (click)="onSelectUnidad(unidad)" class="unidad-card small-card"> apartment {{ unidad.name }} @@ -48,155 +51,167 @@ edit {{ 'editUnitMenu' | translate }} - + +
- + + -
-
-

{{ 'Details of' | translate }} {{ selectedUnidad?.name }}

- - - - - {{ - 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' }} - {{ node.name }} ({{ node.type }}) + {{ node.name }} - - - - + {{ - 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' }} - {{ node.name }} ({{ node.type }}) + {{ node.name }} - - IP: {{ node.ip }} - - - - - - - - {{ - node.type === 'organizational-unit' - ? 'apartment' - : node.type === 'classroom' - ? 'school' - : node.type === 'client' - ? 'computer' - : 'group' - }} - - {{ node.name }} ({{ node.type }}) - - - IP: {{ node.ip }} - - - - - - - - computer - device_hub - {{ node.name }} ({{ node.type }}) - - - IP: {{ node.ip }} + - IP: {{ node.ip }} - - - - - - - - - +
+ + + + + + + + + + + + + + + +
+

Clientes del {{ selectedNode?.name }}

+
+
+
+ Client Icon +
+ {{ client.name }} + {{ client.ip }} + + + + + + + +
+
+
+
- + +
diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index ecd89a0..31f9535 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -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; treeFlattener: MatTreeFlattener; treeDataSource: MatTreeFlatDataSource; 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( (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 { 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; - } diff --git a/ogWebconsole/src/app/components/groups/services/data.service.ts b/ogWebconsole/src/app/components/groups/services/data.service.ts index 1946670..e27c39f 100644 --- a/ogWebconsole/src/app/components/groups/services/data.service.ts +++ b/ogWebconsole/src/app/components/groups/services/data.service.ts @@ -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(url).pipe( catchError(error => { console.error('Error deleting element', error); @@ -97,6 +99,7 @@ export class DataService { }) ); } + changeParent(uuid: string): Observable { const url = `${this.baseUrl}/organizational-units/${uuid}/change-parent`; diff --git a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.css b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.css index d32557e..b978520 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.css +++ b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.css @@ -1,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; + } } diff --git a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.html b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.html index e44a9d7..707c348 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.html +++ b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.html @@ -1,10 +1,7 @@
-

{{ 'addClientDialogTitle' | translate }}

-
-

Añadir un cliente

- +
{{ 'organizationalUnitLabel' | translate }} @@ -15,117 +12,123 @@ - - - {{ 'nameLabel' | translate }} - - - - - {{ 'ogLiveLabel' | translate }} - - - {{ oglive.name }} - - - - - - {{ 'serialNumberLabel' | translate }} - - - - - {{ 'netifaceLabel' | translate }} - - - {{ type.name }} - - - - - - {{ 'netDriverLabel' | translate }} - - - {{ type.name }} - - - - - - {{ 'macLabel' | translate }} - {{ 'macHint' | translate }} - - {{ 'macError' | translate }} - - - - {{ 'ipLabel' | translate }} - {{ 'ipHint' | translate }} - - {{ 'ipError' | translate }} - - - - {{ 'templateLabel' | translate }} - - - {{ template.name }} - - - - - - {{ 'hardwareProfileLabel' | translate }} - - - {{ unit.description }} - - - {{ 'hardwareProfileError' | translate }} -
-
- +
+

Añadir múltiples clientes

+
+ + +

o añadelos manualmente:

+ + +
-
-

Añadir multiples clientes

-
- - +

Clientes importados:

+
+ + + + + + + + + + + + + +
Nombre {{ client.name }} IP {{ client.ip }}
+
- -

Clientes importados:

-
- - + + + +

Añadir un cliente

+ +
- -
- - + + {{ 'nameLabel' | translate }} + + - - - - - - - - + + {{ 'ogLiveLabel' | translate }} + + + {{ oglive.name }} + + + - -
Nombre {{ client.name }} IP {{ client.ip }}
-
+ + {{ 'serialNumberLabel' | translate }} + + + + + {{ 'netifaceLabel' | translate }} + + + {{ type.name }} + + + + + + {{ 'netDriverLabel' | translate }} + + + {{ type.name }} + + + + + + {{ 'macLabel' | translate }} + {{ 'macHint' | translate }} + + {{ 'macError' | translate }} + + + + {{ 'ipLabel' | translate }} + {{ 'ipHint' | translate }} + + {{ 'ipError' | translate }} + + + + {{ 'templateLabel' | translate }} + + + {{ template.name }} + + + + + + {{ 'hardwareProfileLabel' | translate }} + + + {{ unit.description }} + + + {{ 'hardwareProfileError' | translate }} + + +
-
+ - +
diff --git a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.ts b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.ts index 671a45e..5c781ea 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/clients/create-client/create-client.component.ts @@ -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(); } diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index 4bb7273..59fe4cd 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -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" } diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index bec0ac1..fe202b0 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -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" }