531 lines
17 KiB
TypeScript
531 lines
17 KiB
TypeScript
import { Component, OnInit, 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 { MatMenuTrigger } from '@angular/material/menu';
|
|
import { ToastrService } from 'ngx-toastr';
|
|
import { JoyrideService } from 'ngx-joyride';
|
|
import { FlatTreeControl } from '@angular/cdk/tree';
|
|
import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
|
|
|
|
import { DataService } from './services/data.service';
|
|
import { UnidadOrganizativa } 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';
|
|
|
|
interface TreeNode {
|
|
name: string;
|
|
type: string;
|
|
children?: TreeNode[];
|
|
ip?: string;
|
|
'@id'?: string;
|
|
hasClients?: boolean;
|
|
status?: string ;
|
|
}
|
|
|
|
interface FlatNode {
|
|
name: string;
|
|
type: string;
|
|
level: number;
|
|
expandable: boolean;
|
|
ip?: string;
|
|
hasClients?: boolean;
|
|
}
|
|
|
|
@Component({
|
|
selector: 'app-groups',
|
|
templateUrl: './groups.component.html',
|
|
styleUrls: ['./groups.component.css']
|
|
})
|
|
export class GroupsComponent implements OnInit {
|
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
|
organizationalUnits: UnidadOrganizativa[] = [];
|
|
selectedUnidad: UnidadOrganizativa | null = null;
|
|
selectedDetail: any | null = null;
|
|
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;
|
|
selectedClientsOriginal: any[] = [];
|
|
currentView: 'card' | 'list' = 'list';
|
|
isTreeViewActive: boolean = false;
|
|
savedFilterNames: any[] = [];
|
|
|
|
@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 toastr: ToastrService
|
|
) {
|
|
this.treeFlattener = new MatTreeFlattener<TreeNode, FlatNode>(
|
|
(node: TreeNode, level: number) => ({
|
|
name: node.name,
|
|
type: node.type,
|
|
level,
|
|
expandable: !!node.children?.length,
|
|
hasClients: node.hasClients,
|
|
ip: node.ip,
|
|
['@id']: node['@id']
|
|
}),
|
|
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());
|
|
}
|
|
toggleView(view: 'card' | 'list') {
|
|
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 = [];
|
|
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.dataService.getFilters().subscribe(
|
|
data => {
|
|
this.savedFilterNames = data.map((filter: any) => [filter.name, filter.uuid]);
|
|
},
|
|
error => {
|
|
console.error('Error fetching filters:', error);
|
|
}
|
|
);
|
|
}
|
|
|
|
loadSelectedFilter(savedFilter: any) {
|
|
const url = `${this.baseUrl}/views/` + savedFilter[1];
|
|
console.log('llamando a:', url);
|
|
|
|
this.dataService.getFilter(savedFilter[1]).subscribe(response => {
|
|
console.log('Response from server:', response.filters);
|
|
if (response) {
|
|
console.log('Filter1:', response.filters);
|
|
}
|
|
}, error => {
|
|
console.error('Error:', error);
|
|
});
|
|
}
|
|
|
|
|
|
search(): void {
|
|
this.loading = true;
|
|
this.dataService.getOrganizationalUnits(this.searchTerm).subscribe(
|
|
data => {
|
|
this.organizationalUnits = data;
|
|
this.loading = false;
|
|
},
|
|
error => {
|
|
console.error('Error fetching unidades organizativas', error);
|
|
this.loading = false;
|
|
}
|
|
);
|
|
}
|
|
|
|
onSelectUnidad(unidad: UnidadOrganizativa): void {
|
|
this.selectedUnidad = unidad;
|
|
this.selectedDetail = unidad;
|
|
|
|
this.loadChildrenAndClients(unidad.id).then(fullData => {
|
|
const treeData = this.convertToTreeData(fullData);
|
|
this.treeDataSource.data = treeData[0]?.children || [];
|
|
});
|
|
this.isTreeViewActive = true;
|
|
}
|
|
|
|
async loadChildrenAndClients(id: string): Promise<any> {
|
|
try {
|
|
const childrenData = await this.dataService.getChildren(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) : [],
|
|
clients: node.clients || []
|
|
}));
|
|
};
|
|
|
|
return {
|
|
...this.selectedUnidad,
|
|
children: childrenData ? processHierarchy(childrenData) : []
|
|
};
|
|
} catch (error) {
|
|
console.error('Error loading children:', error);
|
|
return this.selectedUnidad;
|
|
}
|
|
}
|
|
|
|
convertToTreeData(data: any): TreeNode[] {
|
|
const processNode = (node: UnidadOrganizativa): TreeNode => ({
|
|
name: node.name,
|
|
type: node.type,
|
|
'@id': node['@id'],
|
|
children: node.children?.map(processNode) || [],
|
|
hasClients: node.clients && node.clients.length > 0,
|
|
});
|
|
|
|
return [processNode(data)];
|
|
}
|
|
|
|
onNodeClick(node: TreeNode): void {
|
|
this.selectedNode = node;
|
|
|
|
if (node.hasClients) {
|
|
const url = `${this.baseUrl}${node['@id']}`;
|
|
this.http.get(url).subscribe(
|
|
(data: any) => {
|
|
this.selectedClientsOriginal = [...data.clients];
|
|
this.selectedClients = data.clients || [];
|
|
},
|
|
(error) => {
|
|
console.error('Error fetching clients:', error);
|
|
}
|
|
);
|
|
} else {
|
|
this.selectedClients = [];
|
|
this.selectedClientsOriginal = [];
|
|
}
|
|
}
|
|
|
|
getNodeIcon(node: any): string {
|
|
console.log('Node:', node);
|
|
switch (node.type) {
|
|
case 'organizational-unit': return 'apartment';
|
|
case 'classrooms-group': return 'doors';
|
|
case 'classroom': return 'school';
|
|
case 'clients-group': return 'lan';
|
|
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' });
|
|
dialogRef.afterClosed().subscribe(() => {
|
|
this.dataService.getOrganizationalUnits().subscribe(
|
|
data => {
|
|
this.organizationalUnits = data;
|
|
},
|
|
error => console.error('Error fetching unidades organizativas', error)
|
|
);
|
|
});
|
|
}
|
|
|
|
addClient(event: MouseEvent, organizationalUnit: any = null): void {
|
|
event.stopPropagation();
|
|
const dialogRef = this.dialog.open(CreateClientComponent, { data: { organizationalUnit }, width: '900px' });
|
|
dialogRef.afterClosed().subscribe(() => {
|
|
this.dataService.getOrganizationalUnits().subscribe(
|
|
data => {
|
|
this.organizationalUnits = data;
|
|
if (organizationalUnit && organizationalUnit.id) {
|
|
this.loadChildrenAndClients(organizationalUnit.id);
|
|
}
|
|
},
|
|
error => console.error('Error fetching unidades organizativas', error)
|
|
);
|
|
});
|
|
}
|
|
|
|
setSelectedNode(node: TreeNode): void {
|
|
this.selectedNode = node;
|
|
}
|
|
|
|
onEditNode(event: MouseEvent, node: TreeNode | null): void {
|
|
if (!node) return;
|
|
|
|
const uuid = node['@id'] ? node['@id'].split('/').pop() : '';
|
|
const type = node.type;
|
|
|
|
event.stopPropagation();
|
|
|
|
if (type !== 'client') {
|
|
this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' });
|
|
} else {
|
|
this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
|
}
|
|
}
|
|
|
|
onDelete(node: TreeNode | null): void {
|
|
console.log('Deleting node:', node);
|
|
}
|
|
|
|
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 {
|
|
event.stopPropagation();
|
|
if (type != 'client') {
|
|
this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' });
|
|
} else {
|
|
this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
|
}
|
|
}
|
|
|
|
onRoomMap(room: any): void {
|
|
this.http.get('https://127.0.0.1:8443' + room['@id']).subscribe(
|
|
(response: any) => {
|
|
this.dialog.open(ClassroomViewDialogComponent, {
|
|
width: '90vw',
|
|
data: { clients: response.clients }
|
|
});
|
|
},
|
|
(error: any) => {
|
|
console.error('Error en la solicitud HTTP:', error);
|
|
}
|
|
);
|
|
}
|
|
|
|
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 {
|
|
this.toastr.success('Ejecutando comando: ' + command.name + " en " + selectedNode.name);
|
|
}
|
|
|
|
onClientActions(client: any): void {
|
|
console.log('Client actions:', client);
|
|
}
|
|
|
|
onShowClientDetail(event: MouseEvent, client: any): void {
|
|
event.stopPropagation();
|
|
this.router.navigate(['clients', client.uuid], { state: { clientData: 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 {
|
|
event.stopPropagation();
|
|
if (data.type != '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;
|
|
|
|
/* filtros */
|
|
selectedTreeFilter: string = '';
|
|
filterTree(searchTerm: string, filterType: string): void {
|
|
const filterNodes = (nodes: any[]): any[] => {
|
|
return nodes
|
|
.map(node => {
|
|
const matchesName = node.name.toLowerCase().includes(searchTerm.toLowerCase());
|
|
const matchesType = filterType ? node.type.toLowerCase() === filterType.toLowerCase() : true;
|
|
|
|
// Filtrar hijos recursivamente
|
|
const filteredChildren = node.children ? filterNodes(node.children) : [];
|
|
|
|
// Si el nodo o algún hijo coincide, incluirlo
|
|
if (matchesName && matchesType || filteredChildren.length > 0) {
|
|
return { ...node, children: filteredChildren };
|
|
}
|
|
return null; // Excluir nodos que no coinciden
|
|
})
|
|
.filter(node => node !== null); // Eliminar nodos excluidos
|
|
};
|
|
|
|
// Aplicar filtro sobre el árbol completo
|
|
const filteredData = filterNodes(this.treeDataSource.data);
|
|
|
|
// Actualizar el origen de datos del árbol
|
|
this.treeDataSource.data = filteredData;
|
|
}
|
|
|
|
onTreeFilterInput(event: Event): void {
|
|
const input = event.target as HTMLInputElement;
|
|
const searchTerm = input?.value || ''; // Maneja el caso de nulo o indefinido
|
|
this.filterTree(searchTerm, this.selectedTreeFilter);
|
|
}
|
|
|
|
onClientFilterInput(event: Event): void {
|
|
const input = event.target as HTMLInputElement;
|
|
const searchTerm = input?.value || ''; // Maneja valores nulos o indefinidos
|
|
this.filterClients(searchTerm);
|
|
}
|
|
|
|
|
|
filterClients(searchTerm: string): void {
|
|
if (!searchTerm) {
|
|
// Restaurar los datos originales si no hay filtro
|
|
this.selectedClients = [...this.selectedClientsOriginal];
|
|
return;
|
|
}
|
|
|
|
const lowerTerm = searchTerm.toLowerCase();
|
|
|
|
this.selectedClients = this.selectedClientsOriginal.filter(client => {
|
|
const matchesName = client.name.toLowerCase().includes(lowerTerm);
|
|
const matchesIP = client.ip?.toLowerCase().includes(lowerTerm) || false;
|
|
const matchesStatus = client.status?.toLowerCase().includes(lowerTerm) || false;
|
|
const matchesMac = client.mac?.toLowerCase().includes(lowerTerm) || false;
|
|
|
|
return matchesName || matchesIP || matchesStatus || matchesMac;
|
|
});
|
|
}
|
|
|
|
|
|
|
|
}
|