Implement data refresh functionality for organizational units and clients
testing/ogGui-multibranch/pipeline/head This commit looks good Details

pull/12/head
Lucas Lara García 2025-01-22 10:35:29 +01:00
parent 6f226d6da6
commit 46dd71889d
7 changed files with 192 additions and 164 deletions

View File

@ -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 {

View File

@ -278,7 +278,7 @@
<mat-icon>visibility</mat-icon>
<span>{{ 'viewDetails' | translate }}</span>
</button>
<button mat-menu-item (click)="onDeleteClick($event, client, selectedNode)">
<button mat-menu-item (click)="onDeleteClick($event, client)">
<mat-icon>delete</mat-icon>
<span>{{ 'delete' | translate }}</span>
</button>

View File

@ -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<UnidadOrganizativa> {
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<any>(`${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;
}

View File

@ -61,6 +61,7 @@ export interface ClientCollection {
export interface TreeNode {
id?: string
uuid?: string;
name: string;
type: string;
'@id'?: string;

View File

@ -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');
}
}

View File

@ -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<CreateMultipleClientComponent>,
@ -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');
}

View File

@ -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<any>(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');
},