oggui/ogWebconsole/src/app/components/groups/groups.component.ts

593 lines
19 KiB
TypeScript

import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
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';
import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
import { Subscription } from 'rxjs';
import { DataService } from './services/data.service';
import { UnidadOrganizativa, Client, TreeNode, FlatNode, Command } from './model/model';
import { CreateOrganizationalUnitComponent } from './shared/organizational-units/create-organizational-unit/create-organizational-unit.component';
import { CreateClientComponent } from './shared/clients/create-client/create-client.component';
import { EditOrganizationalUnitComponent } from './shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component';
import { EditClientComponent } from './shared/clients/edit-client/edit-client.component';
import { ShowOrganizationalUnitComponent } from './shared/organizational-units/show-organizational-unit/show-organizational-unit.component';
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 { DeleteModalComponent } from '../../shared/delete_modal/delete-modal/delete-modal.component';
import { ClassroomViewDialogComponent } from './shared/classroom-view/classroom-view-modal';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
enum NodeType {
OrganizationalUnit = 'organizational-unit',
ClassroomsGroup = 'classrooms-group',
Classroom = 'classroom',
ClientsGroup = 'clients-group',
Client = 'client',
}
@Component({
selector: 'app-groups',
templateUrl: './groups.component.html',
styleUrls: ['./groups.component.css'],
})
export class GroupsComponent implements OnInit, OnDestroy {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
organizationalUnits: UnidadOrganizativa[] = [];
selectedUnidad: UnidadOrganizativa | null = null;
selectedDetail: UnidadOrganizativa | null = null;
loading = false;
searchTerm = '';
treeControl: FlatTreeControl<FlatNode>;
treeFlattener: MatTreeFlattener<TreeNode, FlatNode>;
treeDataSource: MatTreeFlatDataSource<TreeNode, FlatNode>;
selectedNode: TreeNode | null = null;
commands: Command[] = [];
commandsLoading = false;
selectedClients = new MatTableDataSource<Client>([]);
cols = 4;
selectedClientsOriginal: Client[] = [];
currentView: 'card' | 'list' = 'list';
isTreeViewActive = false;
savedFilterNames: [string, string][] = [];
selectedTreeFilter = '';
syncStatus = false;
syncingClientId: string | null = null;
private originalTreeData: TreeNode[] = [];
displayedColumns: string[] = ['name', 'oglive', 'status', 'maintenace', 'subnet', 'pxeTemplate', 'parentName', 'actions'];
private _sort!: MatSort;
private _paginator!: MatPaginator;
@ViewChild(MatSort)
set matSort(ms: MatSort) {
this._sort = ms;
if (this.selectedClients) {
this.selectedClients.sort = this._sort;
}
}
@ViewChild(MatPaginator)
set matPaginator(mp: MatPaginator) {
this._paginator = mp;
if (this.selectedClients) {
this.selectedClients.paginator = this._paginator;
}
}
@ViewChild('clientTab') clientTabComponent!: ClientTabViewComponent;
@ViewChild('organizationalUnitTab') organizationalUnitTabComponent!: OrganizationalUnitTabViewComponent;
private subscriptions: Subscription = new Subscription();
constructor(
private http: HttpClient,
private router: Router,
private dataService: DataService,
public dialog: MatDialog,
private bottomSheet: MatBottomSheet,
private joyrideService: JoyrideService,
private toastr: ToastrService
) {
this.treeFlattener = new MatTreeFlattener<TreeNode, FlatNode>(
this.transformer,
(node) => node.level,
(node) => node.expandable,
(node) => node.children
);
this.treeControl = new FlatTreeControl<FlatNode>(
(node) => node.level,
(node) => node.expandable
);
this.treeDataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
}
ngOnInit(): void {
this.search();
this.getFilters();
this.updateGridCols();
window.addEventListener('resize', this.updateGridCols);
this.selectedClients.filterPredicate = (client: Client, filter: string): boolean => {
const lowerTerm = filter.toLowerCase();
return (
client.name.toLowerCase().includes(lowerTerm) ||
client.ip?.toLowerCase().includes(lowerTerm) ||
client.status?.toLowerCase().includes(lowerTerm) ||
client.mac?.toLowerCase().includes(lowerTerm)
);
};
}
ngOnDestroy(): void {
window.removeEventListener('resize', this.updateGridCols);
this.subscriptions.unsubscribe();
}
private transformer = (node: TreeNode, level: number): FlatNode => ({
name: node.name,
type: node.type,
level,
expandable: !!node.children?.length,
hasClients: node.hasClients,
ip: node.ip,
'@id': node['@id'],
});
toggleView(view: 'card' | 'list'): void {
this.currentView = view;
}
updateGridCols = (): void => {
const width = window.innerWidth;
this.cols = width <= 600 ? 1 : width <= 960 ? 2 : width <= 1280 ? 3 : 4;
};
clearSelection(): void {
this.selectedUnidad = null;
this.selectedDetail = null;
this.selectedClients.data = [];
this.isTreeViewActive = false;
}
onTabChange(event: MatTabChangeEvent): void {
if (event.index === 2) {
this.clientTabComponent.search();
} else if (event.index === 3) {
this.organizationalUnitTabComponent.search();
}
}
getFilters(): void {
this.subscriptions.add(
this.dataService.getFilters().subscribe(
(data) => {
this.savedFilterNames = data.map((filter: { name: string; uuid: string; }) => [filter.name, filter.uuid]);
},
(error) => {
console.error('Error fetching filters:', error);
}
)
);
}
loadSelectedFilter(savedFilter: [string, string]): void {
this.subscriptions.add(
this.dataService.getFilter(savedFilter[1]).subscribe(
(response) => {
if (response) {
console.log('Filter:', response.filters);
}
},
(error) => {
console.error('Error:', error);
}
)
);
}
search(): void {
this.loading = true;
this.subscriptions.add(
this.dataService.getOrganizationalUnits(this.searchTerm).subscribe(
(data) => {
this.organizationalUnits = data;
this.loading = false;
},
(error) => {
console.error('Error fetching organizational units', error);
this.loading = false;
}
)
);
}
onSelectUnidad(unidad: UnidadOrganizativa): void {
this.selectedUnidad = unidad;
this.selectedDetail = unidad;
this.selectedClients.data = this.collectAllClients(unidad);
this.selectedClientsOriginal = [...this.selectedClients.data];
this.loadChildrenAndClients(unidad.id).then((fullData) => {
const treeData = this.convertToTreeData(fullData);
this.treeDataSource.data = treeData[0]?.children || [];
});
this.isTreeViewActive = true;
console.log('Selected unidad:', unidad);
}
private collectAllClients(node: UnidadOrganizativa): Client[] {
let clients = (node.clients || []).map(client => ({
...client,
parentName: node.name
}));
if (node.children) {
node.children.forEach((child) => {
clients = clients.concat(this.collectAllClients(child).map(client => ({
...client,
parentName: client.parentName || ''
})));
});
}
return clients;
}
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[] {
const processNode = (node: UnidadOrganizativa): TreeNode => ({
name: node.name,
type: node.type,
'@id': node['@id'],
children: node.children?.map(processNode) || [],
hasClients: (node.clients?.length ?? 0) > 0,
});
return [processNode(data)];
}
onNodeClick(node: TreeNode): void {
this.selectedNode = node;
this.fetchClientsForNode(node);
}
private fetchClientsForNode(node: TreeNode): void {
if (node.hasClients && node['@id']) {
this.subscriptions.add(
this.http.get<{ clients: Client[] }>(`${this.baseUrl}${node['@id']}`).subscribe(
(data) => {
const clientsWithParentName = (data.clients || []).map(client => ({
...client,
parentName: node.name
}));
this.selectedClients.data = clientsWithParentName;
this.selectedClients._updateChangeSubscription();
if (this._paginator) {
this._paginator.firstPage();
}
},
(error) => {
console.error('Error fetching clients:', error);
}
)
);
} else {
this.selectedClients.data = [];
this.selectedClients._updateChangeSubscription();
}
}
getNodeIcon(node: TreeNode): string {
switch (node.type) {
case NodeType.OrganizationalUnit:
return 'apartment';
case NodeType.ClassroomsGroup:
return 'doors';
case NodeType.Classroom:
return 'school';
case NodeType.ClientsGroup:
return 'lan';
case NodeType.Client:
return 'computer';
default:
return 'group';
}
}
addOU(event: MouseEvent, parent: TreeNode | null = null): void {
event.stopPropagation();
const dialogRef = this.dialog.open(CreateOrganizationalUnitComponent, {
data: { parent },
width: '900px',
});
dialogRef.afterClosed().subscribe(() => {
this.refreshOrganizationalUnits();
});
}
addClient(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
event.stopPropagation();
const dialogRef = this.dialog.open(CreateClientComponent, {
data: { organizationalUnit },
width: '900px',
});
dialogRef.afterClosed().subscribe(() => {
this.refreshOrganizationalUnits();
if (organizationalUnit && organizationalUnit['@id']) {
this.refreshClientsForNode(organizationalUnit);
}
});
}
private refreshOrganizationalUnits(): void {
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];
});
}
},
(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' });
}
}
onDeleteClick(event: MouseEvent, node: TreeNode | null, clientNode?: TreeNode | null): void {
event.stopPropagation();
const uuid = node ? this.extractUuid(node['@id']) : null;
if (!uuid) return;
if (!node) return;
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '400px',
data: { name: node.name },
});
dialogRef.afterClosed().subscribe((result) => {
if (result === true) {
this.deleteEntity(uuid, node.type, node);
}
});
}
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 refreshClientsForNode(node: TreeNode): void {
if (!node['@id']) {
this.selectedClients.data = [];
return;
}
this.fetchClientsForNode(node);
}
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' });
}
}
onRoomMap(room: TreeNode | null): void {
if (!room || !room['@id']) return;
this.subscriptions.add(
this.http.get<{ clients: Client[] }>(`${this.baseUrl}${room['@id']}`).subscribe(
(response) => {
this.dialog.open(ClassroomViewDialogComponent, {
width: '90vw',
data: { clients: response.clients },
});
},
(error) => {
console.error('Error fetching room data:', error);
}
)
);
}
fetchCommands(): void {
this.commandsLoading = true;
this.subscriptions.add(
this.http.get<{ 'hydra:member': Command[] }>(`${this.baseUrl}/commands?page=1&itemsPerPage=30`).subscribe(
(response) => {
this.commands = response['hydra:member'];
this.commandsLoading = false;
},
(error) => {
console.error('Error fetching commands:', error);
this.commandsLoading = false;
}
)
);
}
executeCommand(command: Command, selectedNode: TreeNode | null): void {
if (!selectedNode) {
this.toastr.error('No hay un nodo seleccionado.');
return;
} else {
this.toastr.success(`Ejecutando comando: ${command.name} en ${selectedNode.name}`);
}
}
executeClientCommand(command: Command, client: Client): void {
this.toastr.success(`Ejecutando comando: ${command.name} en ${client.name}`);
}
onShowClientDetail(event: MouseEvent, client: Client): void {
event.stopPropagation();
this.router.navigate(['clients', client.uuid], { state: { clientData: client } });
}
onShowDetailsClick(event: MouseEvent, data: TreeNode | null): void {
event.stopPropagation();
if (data && data.type !== NodeType.Client) {
this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '700px' });
} else {
if (data) {
this.router.navigate(['clients', this.extractUuid(data['@id'])], { state: { clientData: data } });
}
}
}
onTreeClick(event: MouseEvent, data: TreeNode): void {
event.stopPropagation();
if (data.type !== NodeType.Client) {
this.dialog.open(TreeViewComponent, { data: { data }, width: '800px' });
}
}
openBottomSheet(): void {
this.bottomSheet.open(LegendComponent);
}
iniciarTour(): void {
this.joyrideService.startTour({
steps: ['groupsTitleStepText', 'addStep', 'keyStep', 'unitStep', 'elementsStep', 'tabsStep'],
showPrevButton: true,
themeColor: '#3f51b5',
});
}
hasChild = (_: number, node: FlatNode): boolean => node.expandable;
isLeafNode = (_: number, node: FlatNode): boolean => !node.expandable;
filterTree(searchTerm: string, filterType: string): void {
const filterNodes = (nodes: TreeNode[]): TreeNode[] => {
const filteredNodes: TreeNode[] = [];
for (const node of nodes) {
const matchesName = node.name.toLowerCase().includes(searchTerm.toLowerCase());
const matchesType = filterType ? node.type.toLowerCase() === filterType.toLowerCase() : true;
const filteredChildren = node.children ? filterNodes(node.children) : [];
if ((matchesName && matchesType) || filteredChildren.length > 0) {
filteredNodes.push({ ...node, children: filteredChildren });
}
}
return filteredNodes;
};
const filteredData = filterNodes(this.originalTreeData);
this.treeDataSource.data = filteredData;
}
onTreeFilterInput(event: Event): void {
const input = event.target as HTMLInputElement;
const searchTerm = input?.value || '';
this.filterTree(searchTerm, this.selectedTreeFilter);
}
onClientFilterInput(event: Event): void {
const input = event.target as HTMLInputElement;
const searchTerm = input?.value || '';
this.filterClients(searchTerm);
}
filterClients(searchTerm: string): void {
this.searchTerm = searchTerm.trim().toLowerCase();
this.selectedClients.filter = this.searchTerm;
}
public setSelectedNode(node: TreeNode): void {
this.selectedNode = node;
}
getStatus(client: Client): void {
if (!client.uuid || !client['@id']) return;
this.syncingClientId = client.uuid;
this.syncStatus = true;
this.subscriptions.add(
this.http.post(`${this.baseUrl}${client['@id']}/agent/status`, {}).subscribe(
() => {
this.toastr.success('Cliente actualizado correctamente');
this.search();
this.syncStatus = false;
this.syncingClientId = null;
},
() => {
this.toastr.error('Error de conexión con el cliente');
this.syncStatus = false;
this.syncingClientId = null;
}
)
);
}
private extractUuid(idPath: string | undefined): string | null {
return idPath ? idPath.split('/').pop() || null : null;
}
}