From 46dd71889d7e9827e98e24e10e184c2625fe1a0b Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 22 Jan 2025 10:35:29 +0100 Subject: [PATCH] Implement data refresh functionality for organizational units and clients --- .../components/groups/groups.component.css | 2 +- .../components/groups/groups.component.html | 2 +- .../app/components/groups/groups.component.ts | 315 ++++++++++-------- .../src/app/components/groups/model/model.ts | 1 + .../create-client/create-client.component.ts | 14 +- .../create-multiple-client.component.ts | 16 +- .../create-organizational-unit.component.ts | 6 +- 7 files changed, 192 insertions(+), 164 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.css b/ogWebconsole/src/app/components/groups/groups.component.css index a5a5994..2f9f50a 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.css +++ b/ogWebconsole/src/app/components/groups/groups.component.css @@ -534,7 +534,7 @@ button[mat-raised-button] { .clients-title-name { font-size: x-large; display: block; - padding: 1rem 1rem 1rem 16px; + padding: 1rem 1rem 1rem 13px; } .no-clients-info { diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index ff58ad6..5d8ae2f 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -278,7 +278,7 @@ visibility {{ 'viewDetails' | translate }} - diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 10ad8ae..e273168 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -3,7 +3,6 @@ import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { MatBottomSheet } from '@angular/material/bottom-sheet'; -import { MatTabChangeEvent } from '@angular/material/tabs'; import { ToastrService } from 'ngx-toastr'; import { JoyrideService } from 'ngx-joyride'; import { FlatTreeControl } from '@angular/cdk/tree'; @@ -114,7 +113,7 @@ export class GroupsComponent implements OnInit, OnDestroy { this.search(); this.getFilters(); this.updateGridCols(); - this.loadOrganizationalUnits(); + this.refreshData(); window.addEventListener('resize', this.updateGridCols); this.selectedClients.filterPredicate = (client: Client, filter: string): boolean => { @@ -143,41 +142,7 @@ export class GroupsComponent implements OnInit, OnDestroy { ip: node.ip, '@id': node['@id'], }); - - private loadOrganizationalUnits(): void { - this.loading = true; - this.isLoadingClients = true; - this.dataService.getOrganizationalUnits().subscribe( - (data) => { - this.organizationalUnits = data; - this.loading = false; - - if (this.organizationalUnits.length > 0) { - const treeData = this.organizationalUnits.map((unidad) => this.convertToTreeData(unidad)); - this.treeDataSource.data = treeData.flat(); - - this.isTreeViewActive = true; - - const firstNode = this.treeDataSource.data[0]; - if (firstNode) { - this.selectedNode = firstNode; - this.fetchClientsForNode(firstNode); - } - } else { - this.toastr.info('No existen unidades organizativas'); - this.isTreeViewActive = false; - this.isLoadingClients = false; - return; - } - }, - (error) => { - console.error('Error fetching organizational units', error); - this.loading = false; - } - ); - } - - + toggleView(view: 'card' | 'list'): void { this.currentView = view; } @@ -239,49 +204,111 @@ export class GroupsComponent implements OnInit, OnDestroy { ); } - private async loadChildrenAndClients(id: string): Promise { - try { - const childrenData = await this.dataService.getChildren(id).toPromise(); - - const processHierarchy = (nodes: UnidadOrganizativa[]): UnidadOrganizativa[] => { - return nodes.map((node) => ({ - ...node, - children: node.children ? processHierarchy(node.children) : [], - })); - }; - - return { - ...this.selectedUnidad!, - children: childrenData ? processHierarchy(childrenData) : [], - }; - } catch (error) { - console.error('Error loading children:', error); - return this.selectedUnidad!; - } - } - - - private convertToTreeData(data: UnidadOrganizativa): TreeNode[] { + private convertToTreeData(data: UnidadOrganizativa): TreeNode { const processNode = (node: UnidadOrganizativa): TreeNode => ({ id: node.id, + uuid: node.uuid, name: node.name, type: node.type, '@id': node['@id'], children: node.children?.map(processNode) || [], hasClients: (node.clients?.length ?? 0) > 0, }); - return [processNode(data)]; + return processNode(data); } + private refreshData(selectedNodeIdOrUuid?: string): void { + this.loading = true; + this.isLoadingClients = !!selectedNodeIdOrUuid; + + this.dataService.getOrganizationalUnits().subscribe({ + next: (data) => { + this.treeDataSource.data = data.map((unidad) => this.convertToTreeData(unidad)); + + if (selectedNodeIdOrUuid) { + this.selectedNode = this.findNodeByIdOrUuid(this.treeDataSource.data, selectedNodeIdOrUuid); + + if (this.selectedNode) { + this.treeControl.collapseAll(); + this.expandPathToNode(this.selectedNode); + this.fetchClientsForNode(this.selectedNode); + } + } else { + this.treeControl.collapseAll(); + if (this.treeDataSource.data.length > 0) { + this.selectedNode = this.treeDataSource.data[0]; + this.fetchClientsForNode(this.selectedNode); + } else { + this.selectedNode = null; + this.selectedClients.data = []; + } + } + + this.loading = false; + this.isLoadingClients = false; + }, + error: (error) => { + console.error('Error fetching organizational units', error); + this.toastr.error('Ocurrió un error al cargar las unidades organizativas'); + this.loading = false; + this.isLoadingClients = false; + }, + }); + } + + private expandPathToNode(node: TreeNode): void { + const path: TreeNode[] = []; + let currentNode: TreeNode | null = node; + + while (currentNode) { + path.unshift(currentNode); + currentNode = currentNode.id ? this.findParentNode(this.treeDataSource.data, currentNode.id) : null; + } + + path.forEach((pathNode) => { + const flatNode = this.treeControl.dataNodes?.find((n) => n.id === pathNode.id); + if (flatNode) { + this.treeControl.expand(flatNode); + } + }); + } + + private findParentNode(treeData: TreeNode[], childId: string): TreeNode | null { + for (const node of treeData) { + if (node.children?.some((child) => child.id === childId)) { + return node; + } + + if (node.children && node.children.length > 0) { + const parent = this.findParentNode(node.children, childId); + if (parent) { + return parent; + } + } + } + return null; + } + + private findNodeByIdOrUuid(treeData: TreeNode[], identifier: string): TreeNode | null { + const search = (nodes: TreeNode[]): TreeNode | null => { + for (const node of nodes) { + if (node.id === identifier || node.uuid === identifier) return node; + if (node.children && node.children.length > 0) { + const found = search(node.children); + if (found) return found; + } + } + return null; + }; + return search(treeData); + } onNodeClick(node: TreeNode): void { - console.log('Node clicked:', node); this.selectedNode = node; this.fetchClientsForNode(node); } private fetchClientsForNode(node: TreeNode): void { - console.log('Node:', node); this.isLoadingClients = true; this.http.get(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}`).subscribe({ next: (response) => { @@ -317,87 +344,76 @@ export class GroupsComponent implements OnInit, OnDestroy { data: { parent }, width: '900px', }); - dialogRef.afterClosed().subscribe(() => { - this.refreshOrganizationalUnits(); + dialogRef.afterClosed().subscribe((newUnit) => { + if (newUnit?.uuid) { + console.log('Unidad organizativa creada:', newUnit); + this.refreshData(newUnit.uuid); + } }); } addClient(event: MouseEvent, organizationalUnit: TreeNode | null = null): void { event.stopPropagation(); + const targetNode = organizationalUnit || this.selectedNode; const dialogRef = this.dialog.open(CreateClientComponent, { - data: { organizationalUnit }, + data: { organizationalUnit: targetNode }, width: '900px', }); - dialogRef.afterClosed().subscribe(() => { - this.refreshOrganizationalUnits(); - if (organizationalUnit && organizationalUnit['@id']) { - this.refreshClientsForNode(organizationalUnit); + + dialogRef.afterClosed().subscribe((result) => { + if (result?.client && result?.organizationalUnit) { + const organizationalUnitUrl = result.organizationalUnit; + const uuid = organizationalUnitUrl.split('/')[2]; + const parentNode = this.findNodeByIdOrUuid(this.treeDataSource.data, uuid); + + if (parentNode) { + this.refreshData(parentNode.uuid); + } } }); } addMultipleClients(event: MouseEvent, organizationalUnit: TreeNode | null = null): void { event.stopPropagation(); + const targetNode = organizationalUnit || this.selectedNode; + const dialogRef = this.dialog.open(CreateMultipleClientComponent, { - data: { organizationalUnit }, + data: { organizationalUnit: targetNode }, width: '900px', }); - dialogRef.afterClosed().subscribe(() => { - this.refreshOrganizationalUnits(); - if (organizationalUnit && organizationalUnit['@id']) { - this.refreshClientsForNode(organizationalUnit); + dialogRef.afterClosed().subscribe((result) => { + if (result?.success) { + const organizationalUnitUrl = result.organizationalUnit; + const uuid = organizationalUnitUrl.split('/')[2]; + const parentNode = this.findNodeByIdOrUuid(this.treeDataSource.data, uuid); + + if (parentNode) { + console.log('Nodo padre encontrado para actualización:', parentNode); + this.refreshData(parentNode.uuid); + } else { + console.error('No se encontró el nodo padre después de la creación masiva.'); + } } }); } - private refreshOrganizationalUnits(): void { - const expandedNodeIds = this.treeControl.dataNodes - ? this.treeControl.dataNodes - .filter(node => this.treeControl.isExpanded(node)) - .map(node => this.extractUuid(node['@id'])) - : []; - - this.subscriptions.add( - this.dataService.getOrganizationalUnits().subscribe( - (data) => { - this.organizationalUnits = data; - if (this.selectedUnidad) { - this.loadChildrenAndClients(this.selectedUnidad?.id || '').then((updatedData) => { - this.selectedUnidad = updatedData; - const treeData = this.convertToTreeData(updatedData); - this.originalTreeData = treeData[0]?.children || []; - this.treeDataSource.data = [...this.originalTreeData]; - - setTimeout(() => { - this.treeControl.dataNodes.forEach(node => { - const nodeId = this.extractUuid(node['@id']); - if (nodeId && expandedNodeIds.includes(nodeId)) { - this.treeControl.expand(node); - } - }); - }); - }); - } - }, - (error) => console.error('Error fetching organizational units', error) - ) - ); - } - - onEditNode(event: MouseEvent, node: TreeNode | null): void { event.stopPropagation(); const uuid = node ? this.extractUuid(node['@id']) : null; if (!uuid) return; - if (node && node.type !== NodeType.Client) { - this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' }); - } else { - this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' }); - } + const dialogRef = node?.type !== NodeType.Client + ? this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' }) + : this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' }); + + dialogRef.afterClosed().subscribe(() => { + if (node) { + this.refreshData(node.id); + } + }); } - onDeleteClick(event: MouseEvent, node: TreeNode | null, clientNode?: TreeNode | null): void { + onDeleteClick(event: MouseEvent, node: TreeNode | null): void { event.stopPropagation(); const uuid = node ? this.extractUuid(node['@id']) : null; if (!uuid) return; @@ -410,44 +426,50 @@ export class GroupsComponent implements OnInit, OnDestroy { dialogRef.afterClosed().subscribe((result) => { if (result === true) { - this.deleteEntity(uuid, node.type, node); + this.deleteEntityorClient(uuid, node?.type); } }); } - private deleteEntity(uuid: string, type: string, node: TreeNode): void { - this.subscriptions.add( - this.dataService.deleteElement(uuid, type).subscribe( - () => { - this.refreshOrganizationalUnits(); - if (type === NodeType.Client) { - this.refreshClientsForNode(node); - } - this.toastr.success('Entidad eliminada exitosamente'); - }, - (error) => { - console.error('Error deleting entity:', error); - this.toastr.error('Error al eliminar la entidad', error.message); + private deleteEntityorClient(uuid: string, type: string): void { + if (!this.selectedNode) return; + + const parentNode = this.selectedNode?.id + ? this.findParentNode(this.treeDataSource.data, this.selectedNode.id) + : null; + + this.dataService.deleteElement(uuid, type).subscribe({ + next: () => { + const entityType = type === NodeType.Client ? 'Cliente' : 'Entidad'; + const verb = type === NodeType.Client ? 'eliminado' : 'eliminada'; + + this.toastr.success(`${entityType} ${verb} exitosamente`); + + if (type === NodeType.Client) { + this.refreshData(this.selectedNode?.id); + } else if (parentNode) { + this.refreshData(parentNode.id); + } else { + this.refreshData(); } - ) - ); - } - - private refreshClientsForNode(node: TreeNode): void { - if (!node['@id']) { - this.selectedClients.data = []; - return; - } - this.fetchClientsForNode(node); + }, + error: (error) => { + console.error('Error deleting entity:', error); + const entityType = type === NodeType.Client ? 'cliente' : 'entidad'; + this.toastr.error(`Error al eliminar el ${entityType}`); + }, + }); } onEditClick(event: MouseEvent, type: string, uuid: string): void { event.stopPropagation(); - if (type !== NodeType.Client) { - this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' }); - } else { - this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' }); - } + const dialogRef = type !== NodeType.Client + ? this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' }) + : this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' }); + + dialogRef.afterClosed().subscribe(() => { + this.refreshData(this.selectedNode?.id); + }); } onRoomMap(room: TreeNode | null): void { @@ -550,8 +572,6 @@ export class GroupsComponent implements OnInit, OnDestroy { this.treeDataSource.data = filteredData; } - - onTreeFilterInput(event: Event): void { const input = event.target as HTMLInputElement; const searchTerm = input?.value || ''; @@ -569,7 +589,6 @@ export class GroupsComponent implements OnInit, OnDestroy { this.selectedClients.filter = this.searchTerm; } - public setSelectedNode(node: TreeNode): void { this.selectedNode = node; } diff --git a/ogWebconsole/src/app/components/groups/model/model.ts b/ogWebconsole/src/app/components/groups/model/model.ts index 0bada34..aaa5b3e 100644 --- a/ogWebconsole/src/app/components/groups/model/model.ts +++ b/ogWebconsole/src/app/components/groups/model/model.ts @@ -61,6 +61,7 @@ export interface ClientCollection { export interface TreeNode { id?: string + uuid?: string; name: string; type: string; '@id'?: string; 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 138f0ad..e7228a0 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 @@ -157,15 +157,21 @@ export class CreateClientComponent implements OnInit { onSubmit(): void { if (this.clientForm.valid) { const formData = this.clientForm.value; + this.http.post(`${this.baseUrl}/clients`, formData).subscribe( - response => { + (response) => { this.toastService.success('Cliente creado exitosamente', 'Éxito'); - this.dialogRef.close(response); + this.dialogRef.close({ + client: response, + organizationalUnit: formData.organizationalUnit, + }); }, - error => { - this.toastService.error('Error al crear el cliente', 'Error'); + (error) => { + this.toastService.error(error.error['hydra:description'], 'Error al crear el cliente'); } ); + } else { + this.toastService.error('Formulario inválido. Por favor, revise los campos obligatorios.', 'Error'); } } diff --git a/ogWebconsole/src/app/components/groups/shared/clients/create-multiple-client/create-multiple-client.component.ts b/ogWebconsole/src/app/components/groups/shared/clients/create-multiple-client/create-multiple-client.component.ts index e07eaeb..86b51d2 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/create-multiple-client/create-multiple-client.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/clients/create-multiple-client/create-multiple-client.component.ts @@ -17,6 +17,7 @@ export class CreateMultipleClientComponent implements OnInit{ displayedColumns: string[] = ['name', 'ip', 'mac']; showTextarea: boolean = true; organizationalUnit: any; + regex: RegExp = /host\s+(\S+)\s+\{\s+hardware\s+ethernet\s+([a-zA-Z0-9]{2}(:[a-zA-Z0-9]{2}){5});\s+fixed-address\s+((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?));\s+\}/g; constructor( @Optional() private dialogRef: MatDialogRef, @@ -57,15 +58,14 @@ export class CreateMultipleClientComponent implements OnInit{ reader.onload = (e: any) => { 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) { + while ((match = this.regex.exec(textData)) !== null) { clients.push({ name: match[1], mac: match[2], - ip: match[3] + ip: match[4] }); } @@ -84,15 +84,14 @@ export class CreateMultipleClientComponent implements OnInit{ } 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) { + while ((match = this.regex.exec(text)) !== null) { clients.push({ name: match[1], mac: match[2], - ip: match[3] + ip: match[4] }); } @@ -126,7 +125,10 @@ export class CreateMultipleClientComponent implements OnInit{ ); }); this.uploadedClients = []; - this.dialogRef.close(); + this.dialogRef.close({ + success: true, + organizationalUnit: this.organizationalUnit, + }); } else { this.toastService.error('No hay clientes cargados para añadir', 'Error'); } diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.ts b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.ts index 7d01ce8..285a45d 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.ts @@ -41,7 +41,7 @@ export class CreateOrganizationalUnitComponent implements OnInit { selectedCalendarUuid: string | null = null; - @Output() unitAdded = new EventEmitter(); + @Output() unitAdded = new EventEmitter<{ uuid: string; name: string }>(); constructor( private _formBuilder: FormBuilder, @@ -160,8 +160,8 @@ export class CreateOrganizationalUnitComponent implements OnInit { this.http.post(postUrl, formData, { headers }).subscribe( response => { - this.unitAdded.emit(); - this.dialogRef.close(); + this.unitAdded.emit(response); + this.dialogRef.close(response); this.toastService.success('Unidad creada exitosamente', 'Éxito'); this.openSnackBar(false, 'Unidad creada exitosamente'); },