From 6e83a9f8272b8301ce89fbc8d8b6e7e842d062f3 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Thu, 16 Jan 2025 13:02:52 +0100 Subject: [PATCH 01/28] Refactor group component styles and update success messages for unit creation --- .../components/groups/groups.component.css | 52 ------------------- .../create-organizational-unit.component.ts | 8 +-- 2 files changed, 4 insertions(+), 56 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.css b/ogWebconsole/src/app/components/groups/groups.component.css index 1120910..1acd52d 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.css +++ b/ogWebconsole/src/app/components/groups/groups.component.css @@ -33,58 +33,6 @@ button[mat-raised-button] { font-size: 16px; } -mat-card { - background-color: #ffffff; - border-radius: 10px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - transition: transform 0.2s, box-shadow 0.2s; - overflow: hidden; - align-items: center; -} - -mat-card:hover { - transform: translateY(-4px); - box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15); -} - -.unidad-card { - cursor: pointer; - padding: 16px; - font-size: 14px; - display: flex; - flex-direction: column; - justify-content: space-between; -} - -.unidad-card.selected-item { - border: 2px solid #1976d2; -} - -mat-card-title { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - display: flex; - align-items: center; - font-size: 16px; - font-weight: bold; - color: #333; - margin: 0; -} - -mat-card-title mat-icon { - font-size: 20px; - margin-right: 8px; - color: #1976d2; -} - -mat-card-actions { - display: flex; - justify-content: flex-end; - gap: 10px; - margin-top: 16px; -} - .actions mat-icon { color: #757575; cursor: pointer; 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 5790568..7d01ce8 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 @@ -162,8 +162,8 @@ export class CreateOrganizationalUnitComponent implements OnInit { response => { this.unitAdded.emit(); this.dialogRef.close(); - this.toastService.success('Cliente creado exitosamente', 'Éxito'); - this.openSnackBar(false, 'Cliente creado exitosamente'); + this.toastService.success('Unidad creada exitosamente', 'Éxito'); + this.openSnackBar(false, 'Unidad creada exitosamente'); }, error => { console.error('Error al realizar POST:', error); @@ -218,9 +218,9 @@ export class CreateOrganizationalUnitComponent implements OnInit { openSnackBar(isError: boolean, message: string) { if (isError) { - this.toastService.error('Error al crear el cliente: ' + message, 'Error'); + this.toastService.error('Error al crear la unidad: ' + message, 'Error'); } else { - this.toastService.success('Cliente creado exitosamente', 'Éxito'); + this.toastService.success('Unidad creada exitosamente', 'Éxito'); } } } -- 2.40.1 From 6f226d6da6edb85fe1df67812feca5da1af962ee Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Mon, 20 Jan 2025 13:51:09 +0100 Subject: [PATCH 02/28] Update padding and margin in group component styles; adjust icon placement in no clients message --- ogWebconsole/src/app/components/groups/groups.component.css | 3 ++- ogWebconsole/src/app/components/groups/groups.component.html | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.css b/ogWebconsole/src/app/components/groups/groups.component.css index 1acd52d..a5a5994 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 15px; + padding: 1rem 1rem 1rem 16px; } .no-clients-info { @@ -542,4 +542,5 @@ button[mat-raised-button] { align-items: center; gap: 10px; margin-top: 1.5rem; + margin-left: 16px; } \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index acb8172..ff58ad6 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -299,8 +299,8 @@
- error_outline {{ 'noClients' | translate }} + error_outline
-- 2.40.1 From 46dd71889d7e9827e98e24e10e184c2625fe1a0b Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 22 Jan 2025 10:35:29 +0100 Subject: [PATCH 03/28] 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'); }, -- 2.40.1 From 8d6649445863050f85c482558f15571225d082b8 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Thu, 23 Jan 2025 13:49:43 +0100 Subject: [PATCH 04/28] refs #1373 Refactor styles and filters in groups component. --- .../components/groups/groups.component.css | 39 ++++--------------- .../components/groups/groups.component.html | 9 ++--- .../app/components/groups/groups.component.ts | 2 - 3 files changed, 10 insertions(+), 40 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.css b/ogWebconsole/src/app/components/groups/groups.component.css index 2f9f50a..a4ff9cd 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.css +++ b/ogWebconsole/src/app/components/groups/groups.component.css @@ -3,8 +3,7 @@ display: flex; justify-content: space-between; align-items: center; - padding: 20px; - background-color: #f5f5f5; + padding: 0px 10px; border-bottom: 1px solid #ddd; } @@ -134,7 +133,7 @@ button[mat-raised-button] { mat-tree { background-color: #f9f9f9; - padding: 10px; + padding: 0px 10px 10px 10px; } mat-tree mat-tree-node { @@ -246,18 +245,14 @@ mat-tree mat-tree-node.disabled:hover { .filters-container { display: flex; - flex-wrap: wrap; - gap: 16px; - margin-bottom: 16px; + justify-content: start; + gap: 1rem; + margin: 2rem 0px 0.7rem 10px; } .filters-container mat-form-field { flex: 1 1 100%; - max-width: 300px; -} - -.filter-container { - margin-bottom: 16px; + max-width: 250px; } .chip-busy { @@ -342,24 +337,17 @@ mat-tree mat-tree-node.disabled:hover { .tree-container { width: 25%; - padding: 16px; overflow-x: hidden; overflow-y: auto; } .clients-container { width: 75%; - padding: 16px; + padding: 0px 16px 16px 16px; box-sizing: border-box; overflow-y: auto; } -.clients-container h3 { - margin-bottom: 15px; - font-size: 1.5em; - color: #333; -} - .client-item { display: flex; justify-content: center; @@ -464,23 +452,10 @@ button[mat-raised-button] { flex-shrink: 0; } -.filters-container { - display: flex; - flex-wrap: wrap; - gap: 16px; - margin: 16px 0; - padding: 0 16px; -} - .mat-elevation-z8 { box-shadow: 0px 0px 0px rgba(0,0,0,0.2); } -.filters-container mat-form-field { - flex: 1 1 300px; - max-width: 300px; -} - .client-info { 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 5d8ae2f..4bcee04 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -27,14 +27,11 @@ - - - {{ 'filters' | translate }} - +
{{ 'searchClient' | translate }} - + @@ -57,7 +54,7 @@
- +
diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index e273168..e7d2a76 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -55,7 +55,6 @@ export class GroupsComponent implements OnInit, OnDestroy { cols = 4; selectedClientsOriginal: Client[] = []; currentView: 'card' | 'list' = 'list'; - isTreeViewActive = false; savedFilterNames: [string, string][] = []; selectedTreeFilter = ''; syncStatus = false; @@ -156,7 +155,6 @@ export class GroupsComponent implements OnInit, OnDestroy { this.selectedUnidad = null; this.selectedDetail = null; this.selectedClients.data = []; - this.isTreeViewActive = false; this.selectedNode = null; } -- 2.40.1 From c063e0f50fd7425f84601b430ae30dc504058a08 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Thu, 23 Jan 2025 14:27:44 +0100 Subject: [PATCH 05/28] refs #1375 Remove TreeViewComponent and associated files from groups component. --- ogWebconsole/src/app/app.module.ts | 2 - .../components/groups/groups.component.html | 4 -- .../app/components/groups/groups.component.ts | 8 --- .../shared/tree-view/tree-view.component.css | 40 ----------- .../shared/tree-view/tree-view.component.html | 54 -------------- .../shared/tree-view/tree-view.component.ts | 70 ------------------- 6 files changed, 178 deletions(-) delete mode 100644 ogWebconsole/src/app/components/groups/shared/tree-view/tree-view.component.css delete mode 100644 ogWebconsole/src/app/components/groups/shared/tree-view/tree-view.component.html delete mode 100644 ogWebconsole/src/app/components/groups/shared/tree-view/tree-view.component.ts diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 633da32..da98482 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -52,7 +52,6 @@ import { DragDropModule } from '@angular/cdk/drag-drop'; import { ToastrModule } from 'ngx-toastr'; import { ShowOrganizationalUnitComponent } from './components/groups/shared/organizational-units/show-organizational-unit/show-organizational-unit.component'; import { MatGridList, MatGridTile } from "@angular/material/grid-list"; -import { TreeViewComponent } from './components/groups/shared/tree-view/tree-view.component'; import { MatNestedTreeNode, MatTree, @@ -152,7 +151,6 @@ export function HttpLoaderFactory(http: HttpClient) { ClassroomViewComponent, ClientViewComponent, ShowOrganizationalUnitComponent, - TreeViewComponent, LegendComponent, ClassroomViewDialogComponent, SaveFiltersDialogComponent, diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index 4bcee04..d1634f8 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -132,10 +132,6 @@ edit {{ 'edit' | translate }} - -
- - apartment - meeting_room - school - computer - lan - help_outline - - {{ node.name }} -
-
- -
- - - - computer - {{ client.name }} - {{ client.ip }} | {{ client.mac }} - - -
- - - - - - - diff --git a/ogWebconsole/src/app/components/groups/shared/tree-view/tree-view.component.ts b/ogWebconsole/src/app/components/groups/shared/tree-view/tree-view.component.ts deleted file mode 100644 index 4f6d76e..0000000 --- a/ogWebconsole/src/app/components/groups/shared/tree-view/tree-view.component.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {Component, Inject, OnInit} from '@angular/core'; -import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; -import {NestedTreeControl} from "@angular/cdk/tree"; -import {MatTreeNestedDataSource} from "@angular/material/tree"; - -interface OrganizationalUnit { - id: number; - name: string; - type: string; - clients?: Client[]; - children?: OrganizationalUnit[]; -} - -interface Client { - id: number; - name: string; - ip: string; - mac: string; - serialNumber: string; -} - -@Component({ - selector: 'app-tree-view', - templateUrl: './tree-view.component.html', - styleUrl: './tree-view.component.css' -}) -export class TreeViewComponent implements OnInit { - treeControl = new NestedTreeControl(node => node.children); - dataSource = new MatTreeNestedDataSource(); - - constructor( - private dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: any - ) { - } - ngOnInit() { - this.dataSource.data = [this.mapData(this.data.data)]; - } - - hasChild = (_: number, node: OrganizationalUnit) => (!!node.children && node.children.length > 0 || !!node.clients && node.clients.length > 0); - - private mapData(data: any): OrganizationalUnit { - const mapClients = (clients: any[]): Client[] => { - console.log(clients) - return clients.map(client => ({ - id: client.id, - name: client.name, - ip: client.ip, - mac: client.mac, - serialNumber: client.serialNumber, - })); - }; - - const mapChildren = (children: any[]): OrganizationalUnit[] => { - return children.map(child => this.mapData(child)); - }; - - return { - id: data.id, - name: data.name, - type: data.type, - clients: data.clients ? mapClients(data.clients) : [], - children: data.children ? mapChildren(data.children) : [] - }; - } - - close(): void { - this.dialogRef.close(); - } -} -- 2.40.1 From 37460cb01d21ef1dc26c9066e1e2e8ceb7d91f72 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 24 Jan 2025 14:24:48 +0100 Subject: [PATCH 06/28] refs #1376 Extend adding multiple client option to OU's tree --- .../src/app/components/groups/groups.component.css | 2 +- .../src/app/components/groups/groups.component.html | 12 ++++++++---- .../create-multiple-client.component.html | 2 +- .../create-multiple-client.component.ts | 6 ++++++ .../app/layout/main-layout/main-layout.component.css | 4 ++-- ogWebconsole/src/locale/en.json | 2 ++ ogWebconsole/src/locale/es.json | 2 ++ 7 files changed, 22 insertions(+), 8 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.css b/ogWebconsole/src/app/components/groups/groups.component.css index a4ff9cd..ae08699 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.css +++ b/ogWebconsole/src/app/components/groups/groups.component.css @@ -3,7 +3,7 @@ display: flex; justify-content: space-between; align-items: center; - padding: 0px 10px; + padding: 10px 10px; border-bottom: 1px solid #ddd; } diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index d1634f8..d609b1a 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -15,8 +15,8 @@ - - + + + -

{{ 'adminCommandsTitle' | translate }}

+ +
+

{{ 'adminCommandsTitle' | translate }}

+
+
+ + - - + + + + - diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts index 15066ba..4e685f7 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts @@ -1,4 +1,4 @@ -import {Component, Inject, Input, OnInit} from '@angular/core'; +import {Component, Inject, Input, OnInit, SimpleChanges} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog'; import { HttpClient } from '@angular/common/http'; import { FormBuilder, FormGroup } from '@angular/forms'; @@ -11,7 +11,10 @@ import {ToastrService} from "ngx-toastr"; styleUrls: ['./execute-command.component.css'] }) export class ExecuteCommandComponent implements OnInit { - @Input() clientData: any = {}; + @Input() clientData: any[] = []; + @Input() buttonType: 'icon' | 'text' = 'icon'; + @Input() buttonText: string = 'Ejecutar Comandos'; + @Input() icon: string = 'terminal'; baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; loading: boolean = true; @@ -29,6 +32,8 @@ export class ExecuteCommandComponent implements OnInit { {name: 'Ejecutar script', slug: 'run-script', disabled: true}, ]; + client: any = {}; + constructor( private dialog: MatDialog, private http: HttpClient, @@ -39,20 +44,14 @@ export class ExecuteCommandComponent implements OnInit { } ngOnInit(): void { - this.clientData = this.clientData || {}; - this.loadClient(this.clientData) + this.clientData = this.clientData || []; } - loadClient = (uuid: string) => { - this.http.get(`${this.baseUrl}${uuid}`).subscribe({ - next: data => { - this.clientData = data; - this.loading = false; - }, - error: error => { - console.error('Error al obtener el cliente:', error); - } - }); + ngOnChanges(changes: SimpleChanges): void { + if (changes['clientData']) { + console.log(this.clientData.length) + console.log('clientData ha cambiado:', changes['clientData'].currentValue); + } } onCommandSelect(action: any): void { @@ -82,7 +81,9 @@ export class ExecuteCommandComponent implements OnInit { } rebootClient(): void { - this.http.post(`${this.baseUrl}/clients/server/${this.clientData.uuid}/reboot`, {}).subscribe( + this.http.post(`${this.baseUrl}/clients/server/reboot`, { + clients: this.clientData.map((client: any) => client['@id']) + }).subscribe( response => { this.toastService.success('Cliente actualizado correctamente'); }, @@ -94,10 +95,10 @@ export class ExecuteCommandComponent implements OnInit { powerOnClient(): void { const payload = { - client: this.clientData['@id'] + client: '' } - this.http.post(`${this.baseUrl}${this.clientData.repository['@id']}/wol`, payload).subscribe( + this.http.post('', payload).subscribe( response => { this.toastService.success('Cliente actualizado correctamente'); }, @@ -108,7 +109,9 @@ export class ExecuteCommandComponent implements OnInit { } powerOffClient(): void { - this.http.post(`${this.baseUrl}/clients/server/${this.clientData.uuid}/power-off`, {}).subscribe( + this.http.post(`${this.baseUrl}/clients/server/power-off`, { + clients: this.clientData.map((client: any) => client['@id']) + }).subscribe( response => { this.toastService.success('Cliente actualizado correctamente'); }, @@ -119,21 +122,24 @@ export class ExecuteCommandComponent implements OnInit { } openPartitionAssistant(): void { - this.router.navigate([`/clients/${this.clientData.uuid}/partition-assistant`]).then(r => { - console.log('navigated', r); + this.router.navigate(['/clients/partition-assistant'], { + state: { clientData: this.clientData }, + }).then(r => { + console.log('Navigated to partition assistant with data:', this.clientData); }); } openCreateImageAssistant(): void { - this.router.navigate([`/clients/${this.clientData.uuid}/create-image`]).then(r => { + this.router.navigate([`/clients/${this.clientData[0].uuid}/create-image`]).then(r => { console.log('navigated', r); }); } openDeployImageAssistant(): void { - this.router.navigate([`/clients/${this.clientData.uuid}/deploy-image`]).then(r => { - console.log('navigated', r); + this.router.navigate(['/clients/deploy-image'], { + state: { clientData: this.clientData }, + }).then(r => { + console.log('Navigated to deploy image with data:', this.clientData); }); } - } diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts index 138af09..431c5b6 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts @@ -302,7 +302,7 @@ export class ClientMainViewComponent implements OnInit { } openDeployImageAssistant(): void { - this.router.navigate([`/clients/${this.clientData.uuid}/deploy-image`]).then(r => { + this.router.navigate([`/clients/deploy-image`]).then(r => { console.log('navigated', r); }); } diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html index be7a033..b885c6f 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html @@ -1,8 +1,11 @@
- -

Crear Imagen desde {{ clientName }}

+
+

+ Crear imagen desde {{ clientName }} +

+
- +
@@ -12,14 +15,6 @@ Nombre canónico - - - Seleccione imagen creada previamente - - -- - {{ image.name }} - -
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts index 28f0f70..88a478e 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts @@ -22,38 +22,42 @@ import {MatFormField, MatLabel} from "@angular/material/form-field"; import {MatOption} from "@angular/material/autocomplete"; import {MatSelect} from "@angular/material/select"; import {MatInput} from "@angular/material/input"; +import {JoyrideModule} from "ngx-joyride"; +import {TranslatePipe} from "@ngx-translate/core"; @Component({ selector: 'app-create-image', templateUrl: './create-image.component.html', standalone: true, - imports: [ - MatButton, - MatDivider, - NgForOf, - NgIf, - ReactiveFormsModule, - MatTable, - MatColumnDef, - MatHeaderCell, - MatHeaderCellDef, - MatCell, - MatCellDef, - MatChip, - MatHeaderRow, - MatRow, - MatHeaderRowDef, - MatRowDef, - MatCheckbox, - MatRadioGroup, - MatRadioButton, - MatFormField, - MatLabel, - MatOption, - MatSelect, - MatInput, - FormsModule - ], + imports: [ + MatButton, + MatDivider, + NgForOf, + NgIf, + ReactiveFormsModule, + MatTable, + MatColumnDef, + MatHeaderCell, + MatHeaderCellDef, + MatCell, + MatCellDef, + MatChip, + MatHeaderRow, + MatRow, + MatHeaderRowDef, + MatRowDef, + MatCheckbox, + MatRadioGroup, + MatRadioButton, + MatFormField, + MatLabel, + MatOption, + MatSelect, + MatInput, + FormsModule, + JoyrideModule, + TranslatePipe + ], styleUrl: './create-image.component.css' }) export class CreateImageComponent { @@ -65,7 +69,6 @@ export class CreateImageComponent { partitions: any[] = []; images: any[] = []; clientName: string = ''; - selectedImage: string | null = null; selectedPartition: any = null; name: string = ''; client: any = null; @@ -147,15 +150,10 @@ export class CreateImageComponent { ); } - back() { - this.router.navigate(['clients', this.clientId], { state: { clientData: this.client} }); - } - save(): void { const payload = { client: `/clients/${this.clientId}`, name: this.name, - image: this.selectedImage, partition: this.selectedPartition['@id'], source: 'assistant' }; @@ -168,7 +166,6 @@ export class CreateImageComponent { this.router.navigate(['/images']); }, error: (error) => { - console.error('Error:', error); this.toastService.error(error.error['hydra:description']); } } diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.css index 6f2e1d9..f952a3d 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.css +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.css @@ -37,6 +37,7 @@ table { width: 100%; padding: 0 5px; box-sizing: border-box; + padding-left: 1em; } .input-group { @@ -70,7 +71,6 @@ table { display: flex; justify-content: space-between; align-items: center; - padding: 10px; } .mat-elevation-z8 { @@ -82,3 +82,47 @@ table { justify-content: end; margin-bottom: 30px; } + +.clients-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 8px; +} + +.client-item { + position: relative; +} + +.client-card { + background: #ffffff; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; + position: relative; + padding: 8px; + text-align: center; +} + +.client-details { + margin-top: 4px; +} + +.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; +} + +.header-container-title { + flex-grow: 1; + text-align: left; + padding-left: 1em; +} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html index dd8e4aa..472670e 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html @@ -1,12 +1,43 @@
- -

Desplegar imagen en {{ clientName }}

+
+

+ Despliegue de imagen +

+
- +
+
+ + + Clientes + Listado de clientes donde se desplegará la imagen + + +
+
+
+ Client Icon + +
+ {{ client.name }} + {{ client.ip }} + {{ client.mac }} +
+
+
+
+
+
+ + +
@@ -84,12 +115,12 @@ Máximo Clientes - + Tiempo Máximo de Espera - +
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts index e5dc0c5..4d127bf 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts @@ -1,4 +1,4 @@ -import {Component, EventEmitter, Output} from '@angular/core'; +import {Component, EventEmitter, Input, Output} from '@angular/core'; import {MatTableDataSource} from "@angular/material/table"; import {SelectionModel} from "@angular/cdk/collections"; import {HttpClient} from "@angular/common/http"; @@ -33,6 +33,7 @@ export class DeployImageComponent { p2pTime: Number = 0; name: string = ''; client: any = null; + clientData: any = []; protected p2pModeOptions = [ { name: 'Leecher', value: 'p2p-mode-leecher' }, @@ -47,7 +48,6 @@ export class DeployImageComponent { allMethods = [ 'uftp', 'udpcast', - 'multicast-direct', 'unicast', 'unicast-direct', 'p2p' @@ -56,7 +56,6 @@ export class DeployImageComponent { updateCacheMethods = [ 'uftp', 'udpcast', - 'multicast', 'unicast', 'p2p' ]; @@ -98,13 +97,12 @@ export class DeployImageComponent { private toastService: ToastrService, private route: ActivatedRoute, private router: Router, - ) {} - - ngOnInit() { - this.clientId = this.route.snapshot.paramMap.get('id'); - this.selectedOption = 'deploy-image'; - this.loadPartitions(); + ) { + const navigation = this.router.getCurrentNavigation(); + this.clientData = navigation?.extras?.state?.['clientData']; + this.clientId = this.clientData[0]['@id']; this.loadImages(); + this.loadPartitions() } get deployMethods() { @@ -116,7 +114,7 @@ export class DeployImageComponent { } loadPartitions() { - const url = `${this.baseUrl}/clients/${this.clientId}`; + const url = `${this.baseUrl}${this.clientId}`; this.http.get(url).subscribe( (response: any) => { if (response.partitions) { @@ -151,10 +149,6 @@ export class DeployImageComponent { ); } - back() { - this.router.navigate(['clients', this.clientId], { state: { clientData: this.client} }); - } - save(): void { if (!this.selectedImage) { this.toastService.error('Debe seleccionar una imagen'); @@ -171,26 +165,42 @@ export class DeployImageComponent { return; } + this.toastService.info('Preparando petición de despliegue'); + + const payload = { - client: `/clients/${this.clientId}`, + clients: this.clientData.map((client: any) => client['@id']), method: this.selectedMethod, - partition: this.selectedPartition['@id'], + // partition: this.selectedPartition['@id'], + diskNumber: this.selectedPartition.diskNumber, + partitionNumber: this.selectedPartition.partitionNumber, p2pMode: this.p2pMode, p2pTime: this.p2pTime, mcastIp: this.mcastIp, mcastPort: this.mcastPort, mcastMode: this.mcastMode, mcastSpeed: this.mcastSpeed, + maxTime: this.mcastMaxTime, + maxClients: this.mcastMaxClients, }; this.http.post(`${this.baseUrl}${this.selectedImage}/deploy-image`, payload) .subscribe({ next: (response) => { this.toastService.success('Petición de despliegue enviada correctamente'); + this.router.navigate(['/commands-logs']); }, error: (error) => { console.error('Error:', error); - this.toastService.error(error.error['hydra:description']); + this.toastService.error(error.error['hydra:description'], 'Se ha detectado un error en el despliegue de imágenes.', { + "closeButton": true, + "newestOnTop": false, + "progressBar": false, + "positionClass": "toast-bottom-right", + "timeOut": 0, + "extendedTimeOut": 0, + "tapToDismiss": false + }); } } ); diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css index 385218d..782c974 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css @@ -167,3 +167,58 @@ button.remove-btn:hover { padding: 20px; margin: 10px auto; } + +.clients-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 8px; +} + +.client-item { + position: relative; +} + +.client-card { + background: #ffffff; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; + position: relative; + padding: 8px; + text-align: center; +} + +.client-details { + margin-top: 4px; +} + +.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; +} + +.header-container-title { + flex-grow: 1; + text-align: left; + padding-left: 1em; +} + +.select-container { + margin-top: 20px; + align-items: center; + width: 100%; + padding: 0 5px; + box-sizing: border-box; + padding-left: 1em; +} + + diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html index 736e158..c254e55 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html @@ -1,12 +1,43 @@
- -

Asistente de particionado

+
+

+ Asistente de particionado +

+
+
+ + + Clientes + Listado de clientes donde se realizará el particionado + + +
+
+
+ Client Icon + +
+ {{ client.name }} + {{ client.ip }} + {{ client.mac }} +
+
+
+
+
+
+ + +
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts index 80ab9af..308658d 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts @@ -26,7 +26,7 @@ interface Partition { templateUrl: './partition-assistant.component.html', styleUrls: ['./partition-assistant.component.css'] }) -export class PartitionAssistantComponent implements OnInit { +export class PartitionAssistantComponent { baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; @Output() dataChange = new EventEmitter(); partitionTypes = PARTITION_TYPES; @@ -39,6 +39,7 @@ export class PartitionAssistantComponent implements OnInit { updateRequests: any[] = []; data: any = {}; disks: { diskNumber: number; totalDiskSize: number; partitions: Partition[]; chartData: any[]; used: number; percentage: number }[] = []; + clientData: any = []; private apiUrl: string = this.baseUrl + '/partitions'; @@ -51,11 +52,12 @@ export class PartitionAssistantComponent implements OnInit { private toastService: ToastrService, private route: ActivatedRoute, private router: Router, - ) {} - - ngOnInit() { - this.clientId = this.route.snapshot.paramMap.get('id'); + ) { + const navigation = this.router.getCurrentNavigation(); + this.clientData = navigation?.extras?.state?.['clientData']; + this.clientId = this.clientData[0]['@id']; this.loadPartitions(); + } get selectedDisk():any { @@ -63,7 +65,7 @@ export class PartitionAssistantComponent implements OnInit { } loadPartitions() { - const url = `${this.baseUrl}/clients/${this.clientId}`; + const url = `${this.baseUrl}${this.clientId}`; this.http.get(url).subscribe( (response) => { this.data = response; @@ -250,10 +252,6 @@ export class PartitionAssistantComponent implements OnInit { return modifiedPartitions; } - back() { - this.router.navigate(['clients', this.data.uuid], { state: { clientData: this.data } }); - } - save() { if (!this.selectedDisk) { this.errorMessage = 'Por favor selecciona un disco antes de guardar.'; @@ -283,14 +281,16 @@ export class PartitionAssistantComponent implements OnInit { size: partition.size, partitionCode: partition.partitionCode, filesystem: partition.filesystem, - client: `/clients/${this.clientId}`, uuid: partition.uuid, removed: partition.removed || false, format: partition.format || false, })); if (newPartitions.length > 0) { - const bulkPayload = { partitions: newPartitions }; + const bulkPayload = { + partitions: newPartitions, + clients: this.clientData.map((client: any) => client['@id']), + }; this.http.post(this.apiUrl, bulkPayload).subscribe( (response) => { diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.html deleted file mode 100644 index 2bd354f..0000000 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.html +++ /dev/null @@ -1,39 +0,0 @@ -

{{ 'diskImageAssistantTitle' | translate }}

-
-
- -
- -
- - - - - - - - - - - - - - -
{{ 'partitionColumn' | translate }}{{ 'isoImageColumn' | translate }}{{ 'ogliveColumn' | translate }}
{{ partition.partitionNumber }} - - - -
- - -
- -
diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index 2e8ce09..606eee9 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -147,10 +147,15 @@
- {{ 'clients' | translate }} + {{ 'clients' | translate }} {{ selectedNode?.name }} - +
+ @@ -192,7 +197,11 @@ - +
@@ -201,6 +210,23 @@
+ + + + - - diff --git a/ogWebconsole/src/app/components/images/images.component.ts b/ogWebconsole/src/app/components/images/images.component.ts index ca36c01..4d9e6c7 100644 --- a/ogWebconsole/src/app/components/images/images.component.ts +++ b/ogWebconsole/src/app/components/images/images.component.ts @@ -11,6 +11,7 @@ import {ServerInfoDialogComponent} from "../ogdhcp/og-dhcp-subnets/server-info-d import {Observable} from "rxjs"; import {InfoImageComponent} from "../ogboot/pxe-images/info-image/info-image/info-image.component"; import { JoyrideService } from 'ngx-joyride'; +import {ExportImageComponent} from "./export-image/export-image.component"; @Component({ selector: 'app-images', @@ -216,6 +217,14 @@ export class ImagesComponent implements OnInit { } }); break; + case 'export': + this.dialog.open(ExportImageComponent, { + width: '600px', + data: { + image: image + } + }); + break; default: console.error('Acción no soportada:', action); break; diff --git a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.css b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.css new file mode 100644 index 0000000..2239479 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.css @@ -0,0 +1,22 @@ +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100px; +} + +mat-form-field { + width: 100%; +} + +mat-dialog-actions { + display: flex; + justify-content: flex-end; +} + +.checkbox-group { + display: flex; + flex-direction: column; + gap: 10px; +} diff --git a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.html b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.html new file mode 100644 index 0000000..4d834d0 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.html @@ -0,0 +1,15 @@ +

Importar imagenes a {{data.repository?.name}}

+ + + + Seleccione imagenes a importar + + {{ image.name }} + + + + + + + + diff --git a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.spec.ts b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.spec.ts new file mode 100644 index 0000000..03aa01d --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.spec.ts @@ -0,0 +1,59 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ImportImageComponent } from './import-image.component'; +import {FormBuilder, ReactiveFormsModule} from "@angular/forms"; +import {ToastrModule, ToastrService} from "ngx-toastr"; +import {DataService} from "../../calendar/data.service"; +import {provideHttpClient} from "@angular/common/http"; +import {provideHttpClientTesting} from "@angular/common/http/testing"; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {TranslateModule} from "@ngx-translate/core"; +import {MatButtonModule} from "@angular/material/button"; +import {MatInputModule} from "@angular/material/input"; +import {MatSelectModule} from "@angular/material/select"; + +describe('ImportImageComponent', () => { + let component: ImportImageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ImportImageComponent], + imports: [ + ReactiveFormsModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatSelectModule, + BrowserAnimationsModule, + ToastrModule.forRoot(), + TranslateModule.forRoot() + ], + providers: [ + FormBuilder, + ToastrService, + provideHttpClient(), + provideHttpClientTesting(), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: {} + }] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ImportImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts new file mode 100644 index 0000000..3ffd0df --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts @@ -0,0 +1,58 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {ToastrService} from "ngx-toastr"; + +@Component({ + selector: 'app-import-image', + templateUrl: './import-image.component.html', + styleUrl: './import-image.component.css' +}) +export class ImportImageComponent implements OnInit{ + baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; + loading: boolean = true; + images: any[] = []; + selectedClients: any[] = []; + + constructor( + private http: HttpClient, + public dialogRef: MatDialogRef, + private toastService: ToastrService, + @Inject(MAT_DIALOG_DATA) public data: { repository: any } + ) { + + } + + ngOnInit(): void { + this.loading = true; + this.loadImages(); + } + + loadImages() { + this.http.get(`${this.baseUrl}/images?page=1&itemsPerPage=50`).subscribe( + response => { + this.images = response['hydra:member']; + this.loading = false; + }, + error => console.error('Error fetching organizational units:', error) + ); + } + + save() { + this.http.post(`${this.baseUrl}${this.data.repository['@id']}/import-image`, { + images: this.selectedClients + }).subscribe({ + next: (response) => { + this.toastService.success('Peticion de importacion de imagen enviada correctamente'); + this.dialogRef.close(); + }, + error: error => { + this.toastService.error('Error al importar imagenes'); + } + }); + } + + close() { + this.dialogRef.close(); + } +} diff --git a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.css b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.css index 66ff364..326ce6a 100644 --- a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.css +++ b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.css @@ -135,6 +135,10 @@ min-height: 400px; } +.main-container { + margin-top: 15px; +} + .mat-tab-body-wrapper { min-height: inherit; } @@ -210,13 +214,6 @@ p { align-items: center; } -.status-led { - width: 10px; - height: 10px; - border-radius: 50%; - display: inline-block; - margin-right: 10px; -} .status-led.active { background-color: green; @@ -304,4 +301,85 @@ table { } +.dashboard { + padding: 20px; + display: flex; + flex-direction: column; + gap: 20px; +} + +.grid-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; +} + +.row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 20px; +} + +.top-row { + display: flex; + justify-content: center; + gap: 20px; +} + +.top-row .card { + flex: 1; +} + +.card { + background: white; + padding: 15px; + border-radius: 10px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + flex: 1; + min-width: 300px; +} + +.status-led { + width: 10px; + height: 10px; + display: inline-block; + border-radius: 50%; + margin-right: 5px; +} + +.active { + background-color: green; +} + +.inactive { + background-color: red; +} + +.cpu-usage-bar { + background: lightgray; + width: 100%; + height: 20px; + border-radius: 5px; + overflow: hidden; +} + +.cpu-bar { + height: 100%; + background: green; +} + +.cpu-bar.high { + background: red; +} + +@media (max-width: 900px) { + .top-row { + flex-direction: column; + } + + .top-row .card { + max-width: 100%; + } +} diff --git a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html index 5dea2dd..d6baad7 100644 --- a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html +++ b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html @@ -1,51 +1,91 @@ - +
-

OgRepository server Status

-
-
-

Uso de disco

+

OgRepository Server Status

+ +
+
+

Uso de Disco

+ [labels]="showLabels" > -
+

Total: {{ diskUsage.total }}

Ocupado: {{ diskUsage.used }}

Disponible: {{ diskUsage.available }}

-

Ocupado ( % ): {{ diskUsage.percentage }}

+

Ocupado (%): {{ diskUsage.percentage }}

-
+
+

Uso de RAM

+ + +
+

Total: {{ ramUsage.total }}

+

Ocupado: {{ ramUsage.used }}

+

Disponible: {{ ramUsage.available }}

+

Ocupado (%): {{ ramUsage.percentage }}

+
+
+
+ +
+
+

Uso de CPU

+
+
+
+

Usado: {{ cpuUsage.percentage }}

+
+ +

Servicios

  • - + {{ service.name }}: - Activo - Detenido - No accesible - {{ service.status }} - + Activo + Detenido + No accesible + {{ service.status }} + +
  • +
+
+ +
+

Procesos

+
    +
  • + + {{ process.name }}: {{ process.status }}
+ +
@@ -83,59 +123,6 @@ -
-

Imágenes

-
- - Buscar nombre de imagen - - search - Pulsar 'enter' para buscar - -
- -
+ + + + + + {{ 'status' | translate }} @@ -267,7 +293,11 @@ - + {{ 'searchClient' | translate }} - + + diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index 08b7283..543de21 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -628,4 +628,15 @@ export class GroupsComponent implements OnInit, OnDestroy { private extractUuid(idPath: string | undefined): string | null { return idPath ? idPath.split('/').pop() || null : null; } + + clearTreeSearch(inputElement: HTMLInputElement): void { + inputElement.value = ''; + this.filterTree(''); + } + + clearClientSearch(inputElement: HTMLInputElement): void { + inputElement.value = ''; + this.filterClients(''); + } + } -- 2.40.1 From e119ce867d51a34c65ea042aed5cf53d2e91e023 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Tue, 28 Jan 2025 16:18:36 +0100 Subject: [PATCH 12/28] Fix selected node identification in tree view component --- ogWebconsole/src/app/components/groups/groups.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index 7de63db..96d5902 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -81,7 +81,7 @@
- + @@ -100,7 +100,7 @@ more_vert - + {{ -- 2.40.1 From 4e45f1b5524d9e46cc102cfb65f1c61022605a99 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Wed, 29 Jan 2025 14:19:01 +0100 Subject: [PATCH 13/28] Add tooltip displaying client path in client list view --- .../src/app/components/groups/groups.component.html | 6 ++++-- .../src/app/components/groups/groups.component.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index 96d5902..13b0b78 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -247,7 +247,8 @@
{{ 'status' | translate }} + Client Icon {{ 'name' | translate }} +
{{ 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 543de21..a6e7486 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -625,6 +625,18 @@ export class GroupsComponent implements OnInit, OnDestroy { this.arrayClients = [...this.selection.selected]; } + getClientPath(client: Client): string { + const path: string[] = []; + let currentNode: TreeNode | null = this.findNodeByIdOrUuid(this.treeDataSource.data, client.organizationalUnit.uuid); + + while (currentNode) { + path.unshift(currentNode.name); + currentNode = currentNode.id ? this.findParentNode(this.treeDataSource.data, currentNode.id) : null; + } + + return path.join(' / '); + } + private extractUuid(idPath: string | undefined): string | null { return idPath ? idPath.split('/').pop() || null : null; } -- 2.40.1 From b85ed5ad5cda5000ad01c943cf1f7afdd7d51dfa Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 29 Jan 2025 17:40:32 +0100 Subject: [PATCH 14/28] Changes in torrent p2p --- .../deploy-image/deploy-image.component.ts | 6 +++--- .../create-organizational-unit.component.ts | 6 +++--- .../edit-organizational-unit.component.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts index 50684eb..aec624a 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts @@ -36,9 +36,9 @@ export class DeployImageComponent { clientData: any = []; protected p2pModeOptions = [ - { name: 'Leecher', value: 'p2p-mode-leecher' }, - { name: 'Peer', value: 'p2p-mode-peer' }, - { name: 'Seeder', value: 'p2p-mode-seeder' }, + { name: 'Leecher', value: 'leecher' }, + { name: 'Peer', value: 'peer' }, + { name: 'Seeder', value: 'seeder' }, ]; protected multicastModeOptions = [ { name: 'Half duplex', value: "half"}, 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 285a45d..c9b45c1 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 @@ -25,9 +25,9 @@ export class CreateOrganizationalUnitComponent implements OnInit { 'clients-group': 'Grupo de clientes' }; protected p2pModeOptions = [ - { name: 'Leecher', value: 'p2p-mode-leecher' }, - { name: 'Peer', value: 'p2p-mode-peer' }, - { name: 'Seeder', value: 'p2p-mode-seeder' }, + { name: 'Leecher', value: 'leecher' }, + { name: 'Peer', value: 'peer' }, + { name: 'Seeder', value: 'seeder' }, ]; protected multicastModeOptions = [ {"name": 'Half duplex', "value": "half"}, diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts index a1a3188..2b519c2 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts @@ -26,9 +26,9 @@ export class EditOrganizationalUnitComponent implements OnInit { ogLives: any[] = []; repositories: any[] = []; protected p2pModeOptions = [ - {"name": 'Leecher', "value": "p2p-mode-leecher"}, - {"name": 'Peer', "value": "p2p-mode-peer"}, - {"name": 'Seeder', "value": "p2p-mode-seeder"}, + {"name": 'Leecher', "value": "leecher"}, + {"name": 'Peer', "value": "peer"}, + {"name": 'Seeder', "value": "seeder"}, ]; protected multicastModeOptions = [ {"name": 'Half duplex', "value": "half"}, @@ -266,7 +266,7 @@ export class EditOrganizationalUnitComponent implements OnInit { }, error => { console.error('Error al realizar POST:', error); - this.toastService.error('Error al editar:', error); + this.toastService.error('Error al editar:', error.error['hydra:description']); } ); } -- 2.40.1 From e3e00f3765503f4d8fa7081ce6c7ebedeee33eae Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Thu, 30 Jan 2025 12:09:14 +0100 Subject: [PATCH 15/28] refs #1417 Change submit button from add to edit when editting OU. --- .../create-organizational-unit.component.html | 2 +- .../edit-organizational-unit.component.html | 2 +- ogWebconsole/src/locale/en.json | 3 ++- ogWebconsole/src/locale/es.json | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html index fda236e..8375fd9 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html @@ -178,5 +178,5 @@
- +
diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html index e1c72e1..4a0e2f1 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html @@ -166,5 +166,5 @@
- +
diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index 047eb32..72c162a 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -217,7 +217,8 @@ "hardwareProfileLabel": "Hardware Profile", "urlFormatError": "Invalid URL format.", "validationToggle": "Validation", - "submitButton": "Add", + "addOUSubmitButton": "Add", + "editOUSubmitButton": "Edit", "addOrgUnitTitle": "Add Organizational Unit", "createOrgUnitparentLabel": "Parent organizational unit", "noParentOption": "--", diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 66eb9b7..9d226a3 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -217,7 +217,8 @@ "hardwareProfileLabel": "Perfil de Hardware", "urlFormatError": "Formato de URL inválido.", "validationToggle": "Validación", - "submitButton": "Añadir", + "addOUSubmitButton": "Añadir", + "editOUSubmitButton": "Editar", "addOrgUnitTitle": "Añadir Unidad Organizativa", "createOrgUnitparentLabel": "Unidad organizativa padre", "noParentOption": "--", -- 2.40.1 From b2bf6b8c96820e18d7d9091693aafc1fe626ce36 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Thu, 30 Jan 2025 12:16:08 +0100 Subject: [PATCH 16/28] refs #1416 Add translations for organizational unit types in edit form --- .../edit-organizational-unit.component.html | 2 +- .../edit-organizational-unit.component.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html index 4a0e2f1..dd3ce20 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html @@ -8,7 +8,7 @@ {{ 'typeLabel' | translate }} - {{ type }} + {{ typeTranslations[type] }} diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts index 2b519c2..859edc6 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts @@ -19,6 +19,12 @@ export class EditOrganizationalUnitComponent implements OnInit { networkSettingsFormGroup: FormGroup; classroomInfoFormGroup: FormGroup; types: string[] = ['organizational-unit', 'classrooms-group', 'classroom', 'clients-group']; + typeTranslations: { [key: string]: string } = { + 'organizational-unit': 'Centro', + 'classrooms-group': 'Grupo de aulas', + 'classroom': 'Aula', + 'clients-group': 'Grupo de clientes' + }; parentUnits: any[] = []; hardwareProfiles: any[] = []; isEditMode: boolean; -- 2.40.1 From bdbb16d3fd0553cdd74008feb0687d6c9b7faeae Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Fri, 31 Jan 2025 09:10:02 +0100 Subject: [PATCH 17/28] ogRepo new endpoints. Export/Import image --- ogWebconsole/src/app/app.module.ts | 4 + .../export-image/export-image.component.css | 22 +++ .../export-image/export-image.component.html | 15 ++ .../export-image.component.spec.ts | 58 +++++++ .../export-image/export-image.component.ts | 59 +++++++ .../components/images/images.component.css | 6 + .../components/images/images.component.html | 8 +- .../app/components/images/images.component.ts | 9 ++ .../import-image/import-image.component.css | 22 +++ .../import-image/import-image.component.html | 15 ++ .../import-image.component.spec.ts | 59 +++++++ .../import-image/import-image.component.ts | 58 +++++++ .../main-repository-view.component.css | 92 ++++++++++- .../main-repository-view.component.html | 139 ++++++++--------- .../main-repository-view.component.ts | 146 +++++------------- .../repositories/repositories.component.css | 7 + .../repositories/repositories.component.html | 23 ++- .../repositories/repositories.component.ts | 11 ++ ogWebconsole/src/locale/en.json | 1 + ogWebconsole/src/locale/es.json | 1 + 20 files changed, 558 insertions(+), 197 deletions(-) create mode 100644 ogWebconsole/src/app/components/images/export-image/export-image.component.css create mode 100644 ogWebconsole/src/app/components/images/export-image/export-image.component.html create mode 100644 ogWebconsole/src/app/components/images/export-image/export-image.component.spec.ts create mode 100644 ogWebconsole/src/app/components/images/export-image/export-image.component.ts create mode 100644 ogWebconsole/src/app/components/repositories/import-image/import-image.component.css create mode 100644 ogWebconsole/src/app/components/repositories/import-image/import-image.component.html create mode 100644 ogWebconsole/src/app/components/repositories/import-image/import-image.component.spec.ts create mode 100644 ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index d620284..c460a6e 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -124,6 +124,8 @@ import { MatSortModule } from '@angular/material/sort'; import { MenusComponent } from './components/menus/menus.component'; import { CreateMenuComponent } from './components/menus/create-menu/create-menu.component'; import { CreateMultipleClientComponent } from './components/groups/shared/clients/create-multiple-client/create-multiple-client.component'; +import { ExportImageComponent } from './components/images/export-image/export-image.component'; +import {ImportImageComponent} from "./components/repositories/import-image/import-image.component"; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); } @@ -206,6 +208,8 @@ export function HttpLoaderFactory(http: HttpClient) { MenusComponent, CreateMenuComponent, CreateMultipleClientComponent, + ExportImageComponent, + ImportImageComponent, ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/images/export-image/export-image.component.css b/ogWebconsole/src/app/components/images/export-image/export-image.component.css new file mode 100644 index 0000000..2239479 --- /dev/null +++ b/ogWebconsole/src/app/components/images/export-image/export-image.component.css @@ -0,0 +1,22 @@ +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100px; +} + +mat-form-field { + width: 100%; +} + +mat-dialog-actions { + display: flex; + justify-content: flex-end; +} + +.checkbox-group { + display: flex; + flex-direction: column; + gap: 10px; +} diff --git a/ogWebconsole/src/app/components/images/export-image/export-image.component.html b/ogWebconsole/src/app/components/images/export-image/export-image.component.html new file mode 100644 index 0000000..40f46ec --- /dev/null +++ b/ogWebconsole/src/app/components/images/export-image/export-image.component.html @@ -0,0 +1,15 @@ +

Exportar imagen {{data.image?.name}}

+ + + + Seleccione repositorio destino + + {{ repository.name }} + + + + + + + + diff --git a/ogWebconsole/src/app/components/images/export-image/export-image.component.spec.ts b/ogWebconsole/src/app/components/images/export-image/export-image.component.spec.ts new file mode 100644 index 0000000..e45ee65 --- /dev/null +++ b/ogWebconsole/src/app/components/images/export-image/export-image.component.spec.ts @@ -0,0 +1,58 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExportImageComponent } from './export-image.component'; +import {FormBuilder, ReactiveFormsModule} from "@angular/forms"; +import {ToastrModule, ToastrService} from "ngx-toastr"; +import {provideHttpClient} from "@angular/common/http"; +import {provideHttpClientTesting} from "@angular/common/http/testing"; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatInputModule} from "@angular/material/input"; +import {MatButtonModule} from "@angular/material/button"; +import {MatSelectModule} from "@angular/material/select"; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {TranslateModule} from "@ngx-translate/core"; + +describe('ExportImageComponent', () => { + let component: ExportImageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ExportImageComponent], + imports: [ + ReactiveFormsModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatSelectModule, + BrowserAnimationsModule, + ToastrModule.forRoot(), + TranslateModule.forRoot() + ], + providers: [ + FormBuilder, + ToastrService, + provideHttpClient(), + provideHttpClientTesting(), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: {} + }] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ExportImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/images/export-image/export-image.component.ts b/ogWebconsole/src/app/components/images/export-image/export-image.component.ts new file mode 100644 index 0000000..ea43de0 --- /dev/null +++ b/ogWebconsole/src/app/components/images/export-image/export-image.component.ts @@ -0,0 +1,59 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {ToastrService} from "ngx-toastr"; + +@Component({ + selector: 'app-export-image', + templateUrl: './export-image.component.html', + styleUrl: './export-image.component.css' +}) +export class ExportImageComponent implements OnInit { + baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; + loading: boolean = true; + repositories: any[] = []; + selectedRepository: string = ''; + + constructor( + private http: HttpClient, + public dialogRef: MatDialogRef, + private toastService: ToastrService, + @Inject(MAT_DIALOG_DATA) public data: { image: any } + ) { + + } + + ngOnInit(): void { + this.loading = true; + this.loadRepositories(); + } + + loadRepositories() { + this.http.get(`${this.baseUrl}/image-repositories?page=1&itemsPerPage=50`).subscribe( + response => { + this.repositories = response['hydra:member']; + this.loading = false; + }, + error => console.error('Error fetching organizational units:', error) + ); + } + + save() { + this.http.post(`${this.baseUrl}${this.selectedRepository}/export-image`, { + images: [this.data.image['@id']] + }).subscribe({ + next: (response) => { + this.toastService.success('Imagen exportada correctamente'); + this.dialogRef.close(); + }, + error: error => { + console.error('Error al exportar imagen:', error); + this.toastService.error('Error al exportar imagen'); + } + }); + } + + close() { + this.dialogRef.close(); + } +} diff --git a/ogWebconsole/src/app/components/images/images.component.css b/ogWebconsole/src/app/components/images/images.component.css index 50f0f3a..f1a253c 100644 --- a/ogWebconsole/src/app/components/images/images.component.css +++ b/ogWebconsole/src/app/components/images/images.component.css @@ -91,3 +91,9 @@ table { color: white; } +.header-container-title { + flex-grow: 1; + text-align: left; + margin-left: 1em; +} + diff --git a/ogWebconsole/src/app/components/images/images.component.html b/ogWebconsole/src/app/components/images/images.component.html index 235fdb9..836f976 100644 --- a/ogWebconsole/src/app/components/images/images.component.html +++ b/ogWebconsole/src/app/components/images/images.component.html @@ -3,7 +3,11 @@ -

{{ 'imagesTitle' | translate }}

+
+

+ {{ 'imagesTitle' | translate }} +

+
- +
- - - - - - - - - - - -
{{ column.header }} - - - {{ image[column.columnDef] ? 'check_circle' : 'cancel' }} - - - - {{ column.cell(image) }} - - Acciones - - - - - - - -
-
- - -
-
+ diff --git a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts index f209816..3b77099 100644 --- a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts +++ b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts @@ -1,4 +1,4 @@ -import {Component, Inject} from '@angular/core'; +import {Component, Inject, OnInit} from '@angular/core'; import {FormBuilder, FormGroup, Validators} from "@angular/forms"; import {HttpClient} from "@angular/common/http"; import {ToastrService} from "ngx-toastr"; @@ -17,7 +17,7 @@ import {MatDialog} from "@angular/material/dialog"; templateUrl: './main-repository-view.component.html', styleUrl: './main-repository-view.component.css' }) -export class MainRepositoryViewComponent { +export class MainRepositoryViewComponent implements OnInit { baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; repositoryForm: FormGroup; repositoryId: string | null = null; @@ -25,16 +25,20 @@ export class MainRepositoryViewComponent { loading: boolean = true; diskUsage: any = {}; servicesStatus: any = {}; + processesStatus: any = {}; diskUsageChartData: any[] = []; + ramUsageChartData: any[] = []; + ramUsage: any = {}; + cpuUsage: any = {}; alertMessage: string | null = null; length: number = 0; itemsPerPage: number = 10; page: number = 0; view: [number, number] = [800, 500]; gradient: boolean = true; - showLegend: boolean = true; showLabels: boolean = true; isDoughnut: boolean = true; + status: boolean = false; repositoryData: any = {}; colorScheme: any = { domain: ['#FF6384', '#3f51b5'] @@ -115,7 +119,6 @@ export class MainRepositoryViewComponent { comments: [response.comments], }); this.loading = false; - // Llamar searchImages() solo cuando la data de repository esté cargada this.searchImages(); }, (error) => { @@ -157,28 +160,33 @@ export class MainRepositoryViewComponent { loadStatus(): void { this.http.get(`${this.baseUrl}/image-repositories/server/${this.repositoryId}/status`).subscribe(data => { - const diskData = data.output.disk; - const servicesData = data.output.services; + if (!data.success) { + console.error('Error: No se pudo obtener los datos del servidor'); + this.status = false; + return; + } - this.diskUsage = { - total: diskData.total, - used: diskData.used, - available: diskData.available, - percentage: diskData.used_percentage - }; + this.status = true; + const { disk, services, ram, cpu, processes } = data.output; + + this.diskUsage = { ...disk }; this.diskUsageChartData = [ - { - name: 'Usado', - value: parseFloat(diskData.used) - }, - { - name: 'Disponible', - value: parseFloat(diskData.available) - } + { name: 'Usado', value: parseFloat(disk.used.replace('GB', '')) }, + { name: 'Disponible', value: parseFloat(disk.available.replace('GB', '')) } ]; - this.servicesStatus = servicesData; + this.ramUsage = { ...ram }; + this.ramUsageChartData = [ + { name: 'Usado', value: parseFloat(ram.used.replace('GB', '')) }, + { name: 'Disponible', value: parseFloat(ram.available.replace('GB', '')) } + ]; + + this.cpuUsage = { percentage: cpu.used_percentage }; + + this.servicesStatus = Object.entries(services).map(([name, status]) => ({ name, status })); + + this.processesStatus = Object.entries(processes).map(([name, status]) => ({ name, status })); }, error => { console.error('Error fetching status', error); @@ -186,10 +194,17 @@ export class MainRepositoryViewComponent { } getServices(): { name: string, status: string }[] { - return Object.keys(this.servicesStatus).map(key => ({ - name: key, - status: this.servicesStatus[key] - })); + if (!this.status) { + return []; + } + return this.servicesStatus ? this.servicesStatus : []; + } + + getProcesses(): { name: string, status: string }[] { + if (!this.status) { + return []; + } + return this.processesStatus ? this.processesStatus : []; } searchImages(): void { @@ -207,64 +222,6 @@ export class MainRepositoryViewComponent { ); } - editImage(event: MouseEvent, image: any): void { - event.stopPropagation(); - this.dialog.open(CreateImageComponent, { - width: '800px', - data: image['@id'] - }).afterClosed().subscribe(() => this.searchImages()); - } - - deleteImage(image: any): void { - this.dialog.open(DeleteModalComponent, { - width: '300px', - data: { name: image.name }, - }).afterClosed().subscribe((result) => { - if (result) { - this.http.delete(`${this.apiUrl}/server/${image.uuid}/delete`).subscribe({ - next: () => { - this.toastService.success('Imagen eliminada con éxito'); - this.searchImages(); - }, - error: (error) => { - this.toastService.error(error.error['hydra:description']); - console.error('Error al eliminar la imagen:', error); - } - }); - } - }); - } - - loadImageAlert(image: any): Observable { - return this.http.get(`${this.apiUrl}/server/${image.uuid}/get`, {}); - } - - showImageInfo(event: MouseEvent, image:any) { - event.stopPropagation(); - this.loadImageAlert(image).subscribe( - response => { - this.alertMessage = response.output; - - this.dialog.open(ServerInfoDialogComponent, { - width: '600px', - data: { - message: this.alertMessage - } - }); - }, - error => { - this.toastService.error(error.error['hydra:description']); - } - ); - } - - onPageChange(event: any): void { - this.page = event.pageIndex; - this.itemsPerPage = event.pageSize; - this.length = event.length; - this.searchImages(); - } - loadAlert(): Observable { return this.http.get(`${this.baseUrl}/image-repositories/server/get-collection`); } @@ -280,29 +237,6 @@ export class MainRepositoryViewComponent { }); } - - toggleAction(image: any, action:string): void { - switch (action) { - case 'get-aux': - this.http.post(`${this.baseUrl}/images/server/${image.uuid}/create-aux-files`, {}).subscribe({ - next: () => { - this.toastService.success('Petición de creación de archivos auxiliares enviada'); - this.searchImages() - }, - error: (error) => { - this.toastService.error(error.error['hydra:description']); - } - }); - break; - case 'delete': - this.deleteImage(image); - break; - default: - console.error('Acción no soportada:', action); - break; - } - } - openImageInfoDialog() { this.loadAlert().subscribe( response => { diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.css b/ogWebconsole/src/app/components/repositories/repositories.component.css index 16117cb..8207143 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.css +++ b/ogWebconsole/src/app/components/repositories/repositories.component.css @@ -100,3 +100,10 @@ table { margin: 8px 8px 8px 0; } +.header-container-title { + flex-grow: 1; + text-align: left; + margin-left: 1em; +} + + diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.html b/ogWebconsole/src/app/components/repositories/repositories.component.html index debcfd6..a8dbde6 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.html +++ b/ogWebconsole/src/app/components/repositories/repositories.component.html @@ -2,7 +2,11 @@ -

Administrar repositorios

+
+

+ {{ 'repositoryTitle' | translate }} +

+
@@ -11,14 +15,20 @@
- Buscar nombre de imagen + Buscar nombre de repositorio search Pulsar 'enter' para buscar + + Buscar IP de repositorio + + search + Pulsar 'enter' para buscar +
- +
- diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.ts b/ogWebconsole/src/app/components/repositories/repositories.component.ts index 20229db..e497435 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.ts +++ b/ogWebconsole/src/app/components/repositories/repositories.component.ts @@ -8,6 +8,7 @@ import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delet import { JoyrideService } from 'ngx-joyride'; import {CreateRepositoryComponent} from "./create-repository/create-repository.component"; import { Router } from '@angular/router'; +import {ImportImageComponent} from "./import-image/import-image.component"; @Component({ selector: 'app-repositories', @@ -91,6 +92,16 @@ export class RepositoriesComponent { this.router.navigate(['repository', repository.uuid]); } + importImage(event: MouseEvent, repository: any): void { + event.stopPropagation(); + this.dialog.open(ImportImageComponent, { + width: '600px', + data: { repository } + }).afterClosed().subscribe(() => { + this.search(); + }); + } + deleteRepository(event: MouseEvent,command: any): void { event.stopPropagation(); this.dialog.open(DeleteModalComponent, { diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index 72c162a..250997a 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -275,6 +275,7 @@ "ogliveColumn": "OgLive", "selectImageOption": "Select image", "selectOgLiveOption": "Select OgLive", + "repositoryTitle": "Admin Repository", "saveAssociationsButton": "Save Associations", "partitionAssistantTitle": "Partition assistant", "diskSizeLabel": "Size", diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 9d226a3..aab2787 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -300,6 +300,7 @@ "internalUnits": "Unidades internas", "noResultsMessage": "No hay resultados para mostrar.", "imagesTitle": "Administrar imágenes", + "repositoryTitle": "Administrar repositorios", "addImageButton": "Añadir imagen", "searchNameDescription": "Busca imágenes por nombre para encontrar rápidamente una imagen específica.", "searchDefaultDescription": "Filtra las imágenes para mostrar solo las imágenes por defecto o no por defecto.", -- 2.40.1 From 1494ed50c83c14b15fa8ed8db6b45b97745ba9cb Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 31 Jan 2025 10:18:38 +0100 Subject: [PATCH 18/28] refs #1423 Show network settings step only for classroom type in edit organizational unit form --- .../edit-organizational-unit.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html index dd3ce20..7bfee63 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html @@ -78,7 +78,7 @@ - + {{ 'networkSettingsStepLabel' | translate }} -- 2.40.1 From f09275446408ee21154d42801fbd313384afc30f Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 31 Jan 2025 10:31:20 +0100 Subject: [PATCH 19/28] refs #1423 Allow network settings step for both classroom and clients-group types in edit organizational unit form --- .../edit-organizational-unit.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html index 7bfee63..1ffc68a 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html @@ -78,7 +78,7 @@ - + {{ 'networkSettingsStepLabel' | translate }} -- 2.40.1 From b81db7923729c5743772f5231e8d042683ccf542 Mon Sep 17 00:00:00 2001 From: Lucas Lara Date: Fri, 31 Jan 2025 14:09:14 +0100 Subject: [PATCH 20/28] refs #1419 and #1420. Fix OU's path in clients' and OUs' form. Remove stepper from forms. --- .../groups/services/data.service.ts | 10 + .../create-client.component.html | 7 +- .../create-client/create-client.component.ts | 11 + .../create-multiple-client.component.html | 6 +- .../create-multiple-client.component.ts | 16 +- .../edit-client/edit-client.component.html | 8 +- .../edit-client/edit-client.component.ts | 16 +- .../create-organizational-unit.component.html | 320 ++++++++---------- .../create-organizational-unit.component.ts | 16 +- .../edit-organizational-unit.component.html | 298 ++++++++-------- .../edit-organizational-unit.component.ts | 18 +- 11 files changed, 380 insertions(+), 346 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/services/data.service.ts b/ogWebconsole/src/app/components/groups/services/data.service.ts index 997c149..34b0bc3 100644 --- a/ogWebconsole/src/app/components/groups/services/data.service.ts +++ b/ogWebconsole/src/app/components/groups/services/data.service.ts @@ -231,5 +231,15 @@ export class DataService { ); } + getOrganizationalUnitPath(unit: UnidadOrganizativa, units: UnidadOrganizativa[]): string { + const path: string[] = []; + let currentUnit: UnidadOrganizativa | undefined = unit; + while (currentUnit) { + path.unshift(currentUnit.name); + currentUnit = units.find(u => u['@id'] === currentUnit?.parent?.['@id']); + } + + return path.join(' / '); + } } 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 84e406d..3ef6251 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 @@ -6,9 +6,12 @@ Padre - + + {{ getSelectedParentName() }} + +
{{ unit.name }}
-
{{ unit.path }}
+
{{ unit.path }}
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 eb6a5a4..514893b 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 @@ -15,6 +15,7 @@ export class CreateClientComponent implements OnInit { baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; clientForm!: FormGroup; parentUnits: any[] = []; + parentUnitsWithPaths: { id: string, name: string, path: string }[] = []; hardwareProfiles: any[] = []; ogLives: any[] = []; menus: any[] = []; @@ -80,6 +81,11 @@ export class CreateClientComponent implements OnInit { this.http.get(url).subscribe( response => { this.parentUnits = response['hydra:member']; + this.parentUnitsWithPaths = this.parentUnits.map(unit => ({ + id: unit['@id'], + name: unit.name, + path: this.dataService.getOrganizationalUnitPath(unit, this.parentUnits) + })); this.loading = false; }, error => { @@ -89,6 +95,11 @@ export class CreateClientComponent implements OnInit { ); } + getSelectedParentName(): string | undefined { + const parentId = this.clientForm.get('organizationalUnit')?.value; + return this.parentUnitsWithPaths.find(unit => unit.id === parentId)?.name; + } + loadHardwareProfiles(): void { this.dataService.getHardwareProfiles().subscribe( (data: any[]) => { diff --git a/ogWebconsole/src/app/components/groups/shared/clients/create-multiple-client/create-multiple-client.component.html b/ogWebconsole/src/app/components/groups/shared/clients/create-multiple-client/create-multiple-client.component.html index b0f956f..1b34d9c 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/create-multiple-client/create-multiple-client.component.html +++ b/ogWebconsole/src/app/components/groups/shared/clients/create-multiple-client/create-multiple-client.component.html @@ -7,8 +7,12 @@ {{ 'organizationalUnitLabel' | translate }} - + + {{ getSelectedParentName() }} + +
{{ unit.name }}
+
{{ unit.path }}
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 9720054..2e1617e 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 @@ -4,6 +4,7 @@ import {HttpClient} from "@angular/common/http"; import {MatSnackBar} from "@angular/material/snack-bar"; import {ToastrService} from "ngx-toastr"; import {MAT_DIALOG_DATA} from "@angular/material/dialog"; +import { DataService } from '../../../services/data.service'; @Component({ selector: 'app-create-multiple-client', @@ -13,6 +14,7 @@ import {MAT_DIALOG_DATA} from "@angular/material/dialog"; export class CreateMultipleClientComponent implements OnInit{ baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; parentUnits: any[] = []; + parentUnitsWithPaths: { id: string, name: string, path: string }[] = []; uploadedClients: any[] = []; loading: boolean = false; displayedColumns: string[] = ['name', 'ip', 'mac']; @@ -25,7 +27,8 @@ export class CreateMultipleClientComponent implements OnInit{ @Inject(MAT_DIALOG_DATA) private data: any, private http: HttpClient, private snackBar: MatSnackBar, - private toastService: ToastrService + private toastService: ToastrService, + private dataService: DataService ) {} ngOnInit(): void { @@ -43,6 +46,11 @@ export class CreateMultipleClientComponent implements OnInit{ this.http.get(url).subscribe( response => { this.parentUnits = response['hydra:member']; + this.parentUnitsWithPaths = this.parentUnits.map(unit => ({ + id: unit['@id'], + name: unit.name, + path: this.dataService.getOrganizationalUnitPath(unit, this.parentUnits) + })); this.loading = false; }, error => { @@ -52,8 +60,12 @@ export class CreateMultipleClientComponent implements OnInit{ ); } + getSelectedParentName(): string | undefined { + const parentId = this.organizationalUnit; + return this.parentUnitsWithPaths.find(unit => unit.id === parentId)?.name; + } + setOrganizationalUnit(organizationalUnit: any): void { - console.log('Organizational unit selected:', organizationalUnit.value); this.organizationalUnit = organizationalUnit.value; } diff --git a/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.html b/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.html index 663e8e0..7fb4fcc 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.html +++ b/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.html @@ -6,8 +6,12 @@ {{ 'organizationalUnitLabel' | translate }} - - {{ unit.name }} + + {{ getSelectedParentName() }} + + +
{{ unit.name }}
+
{{ unit.path }}
diff --git a/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.ts b/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.ts index 1b05c2c..e53dd47 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.ts @@ -15,6 +15,7 @@ export class EditClientComponent { baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; clientForm!: FormGroup; parentUnits: any[] = []; + parentUnitsWithPaths: { id: string, name: string, path: string }[] = []; hardwareProfiles: any[] = []; repositories: any[] = []; ogLives: any[] = []; @@ -68,19 +69,32 @@ export class EditClientComponent { }); } - loadParentUnits() { + loadParentUnits(): void { + this.loading = true; const url = `${this.baseUrl}/organizational-units?page=1&itemsPerPage=10000`; this.http.get(url).subscribe( response => { this.parentUnits = response['hydra:member']; + this.parentUnitsWithPaths = this.parentUnits.map(unit => ({ + id: unit['@id'], + name: unit.name, + path: this.dataService.getOrganizationalUnitPath(unit, this.parentUnits) + })); + this.loading = false; }, error => { console.error('Error fetching parent units:', error); + this.loading = false; } ); } + getSelectedParentName(): string | undefined { + const parentId = this.clientForm.get('organizationalUnit')?.value; + return this.parentUnitsWithPaths.find(unit => unit.id === parentId)?.name; + } + loadHardwareProfiles(): void { this.dataService.getHardwareProfiles().subscribe( (data: any[]) => { diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html index 8375fd9..675c34c 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.html @@ -1,180 +1,158 @@

{{ 'addOrgUnitTitle' | translate }}

-
- - - - - {{ 'generalStepLabel' | translate }} - - {{ 'typeLabel' | translate }} - - - {{ typeTranslations[type] }} - - - - - {{ 'nameLabel' | translate }} - - - - {{ 'createOrgUnitparentLabel' | translate }} - - {{ 'noParentOption' | translate }} - {{ unit.name }} - - - - {{ 'descriptionLabel' | translate }} - - -
- -
- -
+ +
+ + {{ 'typeLabel' | translate }} + + + {{ typeTranslations[type] }} + + + + + {{ 'nameLabel' | translate }} + + + + {{ 'createOrgUnitparentLabel' | translate }} + + + {{ getSelectedParentName() }} + + +
{{ unit.name }}
+
{{ unit.path }}
+
+
+
+ + {{ 'descriptionLabel' | translate }} + + + - - -
- {{ 'classroomInfoStepLabel' | translate }} - - {{ 'locationLabel' | translate }} - - - {{ 'projectorToggle' | translate }} - {{ 'boardToggle' | translate }} - - {{ 'capacityLabel' | translate }} - - - - {{ 'associatedCalendarLabel' | translate }} - - - {{ calendar.name }} - - - + + + + {{ 'locationLabel' | translate }} + + + {{ 'projectorToggle' | translate }} + {{ 'boardToggle' | translate }} + + {{ 'capacityLabel' | translate }} + + + + {{ 'associatedCalendarLabel' | translate }} + + + {{ calendar.name }} + + + + -
- - -
- -
+ +
+ + {{ 'commentsLabel' | translate }} + + + - - -
- {{ 'additionalInfoStepLabel' | translate }} - - {{ 'commentsLabel' | translate }} - - -
- - -
- -
+ +
+ + {{ 'ogLiveLabel' | translate }} + + + {{ oglive.filename }} + + + + + {{ 'repositoryLabel' | translate }} + + + {{ repository.name }} + + + + + {{ 'nextServerLabel' | translate }} + + + + {{ 'bootFileNameLabel' | translate }} + + - - - - {{ 'networkSettingsStepLabel' | translate }} - - {{ 'ogLiveLabel' | translate }} - - - {{ oglive.filename }} - - - - - {{ 'repositoryLabel' | translate }} - - - {{ repository.name }} - - - - - {{ 'nextServerLabel' | translate }} - - - - {{ 'bootFileNameLabel' | translate }} - - - - - {{ 'proxyUrlLabel' | translate }} - - - - {{ 'dnsIpLabel' | translate }} - - - - {{ 'netmaskLabel' | translate }} - - - - {{ 'routerLabel' | translate }} - - - - {{ 'ntpIpLabel' | translate }} - - - - {{ 'p2pModeLabel' | translate }} - - - {{ option.name }} - - - - - {{ 'p2pTimeLabel' | translate }} - - - - {{ 'mcastIpLabel' | translate }} - - - - {{ 'mcastSpeedLabel' | translate }} - - - - {{ 'mcastPortLabel' | translate }} - - - - {{ 'mcastModeLabel' | translate }} - - - {{ option.name }} - - - - - {{ 'menuUrlLabel' | translate }} - - - - {{ 'hardwareProfileLabel' | translate }} - - {{ unit.description }} - - {{ 'urlFormatError' | translate }} - - - -
+ + {{ 'proxyUrlLabel' | translate }} + + + + {{ 'dnsIpLabel' | translate }} + + + + {{ 'netmaskLabel' | translate }} + + + + {{ 'routerLabel' | translate }} + + + + {{ 'ntpIpLabel' | translate }} + + + + {{ 'p2pModeLabel' | translate }} + + + {{ option.name }} + + + + + {{ 'p2pTimeLabel' | translate }} + + + + {{ 'mcastIpLabel' | translate }} + + + + {{ 'mcastSpeedLabel' | translate }} + + + + {{ 'mcastPortLabel' | translate }} + + + + {{ 'mcastModeLabel' | translate }} + + + {{ option.name }} + + + + + {{ 'menuUrlLabel' | translate }} + + + + {{ 'hardwareProfileLabel' | translate }} + + {{ unit.description }} + + {{ 'urlFormatError' | translate }} + +
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 c9b45c1..ec2a96f 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 @@ -39,7 +39,7 @@ export class CreateOrganizationalUnitComponent implements OnInit { ogLives: any[] = []; repositories: any[] = []; selectedCalendarUuid: string | null = null; - + parentUnitsWithPaths: { id: string, name: string, path: string }[] = []; @Output() unitAdded = new EventEmitter<{ uuid: string; name: string }>(); @@ -104,11 +104,23 @@ export class CreateOrganizationalUnitComponent implements OnInit { loadParentUnits() { const url = `${this.baseUrl}/organizational-units?page=1&itemsPerPage=1000`; this.http.get(url).subscribe( - response => this.parentUnits = response['hydra:member'], + response => { + this.parentUnits = response['hydra:member']; + this.parentUnitsWithPaths = this.parentUnits.map(unit => ({ + id: unit['@id'], + name: unit.name, + path: this.dataService.getOrganizationalUnitPath(unit, this.parentUnits) + })); + }, error => console.error('Error fetching parent units:', error) ); } + getSelectedParentName(): string | undefined { + const parentId = this.generalFormGroup.get('parent')?.value; + return this.parentUnitsWithPaths.find(unit => unit.id === parentId)?.name; + } + loadHardwareProfiles(): void { this.dataService.getHardwareProfiles().subscribe( (data: any[]) => this.hardwareProfiles = data, diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html index 1ffc68a..4e7c86c 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html @@ -1,168 +1,146 @@

{{ 'editOrgUnitTitle' | translate }}

- - - -
- {{ 'generalStepLabel' | translate }} - - {{ 'typeLabel' | translate }} - - {{ typeTranslations[type] }} - - - - {{ 'nameLabel' | translate }} - - - - {{ 'editOrgUnitParentLabel' | translate }} - - {{ unit.name }} - - - - {{ 'descriptionLabel' | translate }} - - -
- -
- -
+ +
+ + {{ 'typeLabel' | translate }} + + + {{ typeTranslations[type] }} + + + + + {{ 'nameLabel' | translate }} + + + + {{ 'editOrgUnitParentLabel' | translate }} + + + {{ getSelectedParentName() }} + + +
{{ unit.name }}
+
{{ unit.path }}
+
+
+
+ + {{ 'descriptionLabel' | translate }} + + + - - -
- {{ 'classroomInfoStepLabel' | translate }} - - {{ 'locationLabel' | translate }} - - - {{ 'projectorToggle' | translate }} - {{ 'boardToggle' | translate }} - - {{ 'capacityLabel' | translate }} - - + + + + {{ 'locationLabel' | translate }} + + + {{ 'projectorToggle' | translate }} + {{ 'boardToggle' | translate }} + + {{ 'capacityLabel' | translate }} + + + + {{ 'associatedCalendarLabel' | translate }} + + + {{ calendar.name }} + + + + - - {{ 'associatedCalendarLabel' | translate }} - - - {{ calendar.name }} - - - + +
+ + {{ 'commentsLabel' | translate }} + + + -
- - -
- -
- - - -
- {{ 'additionalInfoStepLabel' | translate }} - - {{ 'commentsLabel' | translate }} - - -
- - -
- -
- - - -
- {{ 'networkSettingsStepLabel' | translate }} - - {{ 'ogLiveLabel' | translate }} - - - {{ oglive.filename }} - - - - - {{ 'repositoryLabel' | translate }} - - - {{ repository.name }} - - - - - {{ 'proxyUrlLabel' | translate }} - - - - {{ 'dnsIpLabel' | translate }} - - - - {{ 'netmaskLabel' | translate }} - - - - {{ 'routerLabel' | translate }} - - - - {{ 'ntpIpLabel' | translate }} - - - - {{ 'p2pModeLabel' | translate }} - - {{ option.name }} - - - - {{ 'p2pTimeLabel' | translate }} - - - - {{ 'mcastIpLabel' | translate }} - - - - {{ 'mcastSpeedLabel' | translate }} - - - - {{ 'mcastPortLabel' | translate }} - - - - {{ 'mcastModeLabel' | translate }} - - {{ option.name }} - - - - {{ 'menuUrlLabel' | translate }} - - - - {{ 'hardwareProfileLabel' | translate }} - - {{ unit.description }} - - {{ 'urlFormatError' | translate }} - - {{ 'validationToggle' | translate }} -
- -
- -
-
+ +
+ + {{ 'ogLiveLabel' | translate }} + + + {{ oglive.filename }} + + + + + {{ 'repositoryLabel' | translate }} + + + {{ repository.name }} + + + + + {{ 'proxyUrlLabel' | translate }} + + + + {{ 'dnsIpLabel' | translate }} + + + + {{ 'netmaskLabel' | translate }} + + + + {{ 'routerLabel' | translate }} + + + + {{ 'ntpIpLabel' | translate }} + + + + {{ 'p2pModeLabel' | translate }} + + {{ option.name }} + + + + {{ 'p2pTimeLabel' | translate }} + + + + {{ 'mcastIpLabel' | translate }} + + + + {{ 'mcastSpeedLabel' | translate }} + + + + {{ 'mcastPortLabel' | translate }} + + + + {{ 'mcastModeLabel' | translate }} + + {{ option.name }} + + + + {{ 'menuUrlLabel' | translate }} + + + + {{ 'hardwareProfileLabel' | translate }} + + {{ unit.description }} + + {{ 'urlFormatError' | translate }} + + {{ 'validationToggle' | translate }} +
diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts index 859edc6..251e2b1 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.ts @@ -31,6 +31,7 @@ export class EditOrganizationalUnitComponent implements OnInit { currentCalendar: any = []; ogLives: any[] = []; repositories: any[] = []; + parentUnitsWithPaths: { id: string, name: string, path: string }[] = []; protected p2pModeOptions = [ {"name": 'Leecher', "value": "leecher"}, {"name": 'Peer', "value": "peer"}, @@ -109,18 +110,25 @@ export class EditOrganizationalUnitComponent implements OnInit { } loadParentUnits() { - const url = `${this.baseUrl}/organizational-units?page=1&itemsPerPage=10000`; - + const url = `${this.baseUrl}/organizational-units?page=1&itemsPerPage=1000`; this.http.get(url).subscribe( response => { this.parentUnits = response['hydra:member']; + this.parentUnitsWithPaths = this.parentUnits.map(unit => ({ + id: unit['@id'], + name: unit.name, + path: this.dataService.getOrganizationalUnitPath(unit, this.parentUnits) + })); }, - error => { - console.error('Error fetching parent units:', error); - } + error => console.error('Error fetching parent units:', error) ); } + getSelectedParentName(): string | undefined { + const parentId = this.generalFormGroup.get('parent')?.value; + return this.parentUnitsWithPaths.find(unit => unit.id === parentId)?.name; + } + loadHardwareProfiles(): void { this.dataService.getHardwareProfiles().subscribe( (data: any[]) => { -- 2.40.1 From 45bf13e63a2fbfa4eedf77a24911c6ecb704766e Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 3 Feb 2025 11:14:19 +0100 Subject: [PATCH 21/28] Updated repo/images UX --- .../execute-command.component.ts | 13 +++---- .../create-organizational-unit.component.css | 2 +- .../create-organizational-unit.component.ts | 4 +- .../edit-organizational-unit.component.css | 13 +++---- .../edit-organizational-unit.component.html | 2 +- .../create-image/create-image.component.ts | 5 --- .../components/images/images.component.css | 5 +++ .../components/images/images.component.html | 6 ++- .../app/components/images/images.component.ts | 39 ++++++++++++++++--- .../import-image/import-image.component.css | 17 ++++++++ .../import-image/import-image.component.html | 13 +++++++ .../import-image/import-image.component.ts | 9 +++++ .../main-repository-view.component.html | 2 +- .../repositories/repositories.component.html | 2 +- ogWebconsole/src/locale/en.json | 1 + ogWebconsole/src/locale/es.json | 1 + 16 files changed, 101 insertions(+), 33 deletions(-) diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts index 4e685f7..9f6bdc9 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts @@ -50,7 +50,6 @@ export class ExecuteCommandComponent implements OnInit { ngOnChanges(changes: SimpleChanges): void { if (changes['clientData']) { console.log(this.clientData.length) - console.log('clientData ha cambiado:', changes['clientData'].currentValue); } } @@ -94,13 +93,11 @@ export class ExecuteCommandComponent implements OnInit { } powerOnClient(): void { - const payload = { - client: '' - } - - this.http.post('', payload).subscribe( + this.http.post(`${this.baseUrl}/image-repositories/wol`, { + clients: this.clientData.map((client: any) => client['@id']) + }).subscribe( response => { - this.toastService.success('Cliente actualizado correctamente'); + this.toastService.success('Petición de encendido enviada correctamente'); }, error => { this.toastService.error('Error de conexión con el cliente'); @@ -113,7 +110,7 @@ export class ExecuteCommandComponent implements OnInit { clients: this.clientData.map((client: any) => client['@id']) }).subscribe( response => { - this.toastService.success('Cliente actualizado correctamente'); + this.toastService.success('Petición de apagado enviada correctamente'); }, error => { this.toastService.error('Error de conexión con el cliente'); diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.css b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.css index f8e9c6c..ffd86db 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.css +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component.css @@ -39,4 +39,4 @@ button { mat-slide-toggle{ margin-left: 10px; -} \ No newline at end of file +} 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 ec2a96f..f3aae66 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 @@ -130,7 +130,9 @@ export class CreateOrganizationalUnitComponent implements OnInit { loadOgLives() { this.dataService.getOgLives().subscribe( - (data: any[]) => this.ogLives = data, + (data: any[]) => { + this.ogLives = data + }, error => console.error('Error fetching ogLives', error) ); } diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.css b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.css index 40124bd..76f9983 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.css +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.css @@ -5,35 +5,34 @@ h1 { color: #3f51b5; margin-bottom: 20px; } - + .network-form { display: flex; flex-direction: column; gap: 15px; } - + .form-field { width: 100%; margin-top: 10px; } - + .mat-dialog-content { padding: 20px; } - + .mat-dialog-actions { display: flex; justify-content: flex-end; padding: 10px 20px; } - + button { text-transform: none; font-size: 16px; font-weight: 500; } - + .mat-slide-toggle { margin-top: 20px; } - \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html index 4e7c86c..c4bdb8a 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component.html @@ -33,7 +33,7 @@ -
+ {{ 'locationLabel' | translate }} diff --git a/ogWebconsole/src/app/components/images/create-image/create-image.component.ts b/ogWebconsole/src/app/components/images/create-image/create-image.component.ts index ae6b57c..52fd24b 100644 --- a/ogWebconsole/src/app/components/images/create-image/create-image.component.ts +++ b/ogWebconsole/src/app/components/images/create-image/create-image.component.ts @@ -90,11 +90,6 @@ export class CreateImageComponent implements OnInit { } saveImage(): void { - if (this.imageForm.invalid) { - this.toastService.error('Por favor, rellena los campos obligatorios'); - return; - } - const payload: any = { name: this.imageForm.value.name, description: this.imageForm.value.description, diff --git a/ogWebconsole/src/app/components/images/images.component.css b/ogWebconsole/src/app/components/images/images.component.css index f1a253c..af5ea91 100644 --- a/ogWebconsole/src/app/components/images/images.component.css +++ b/ogWebconsole/src/app/components/images/images.component.css @@ -91,6 +91,11 @@ table { color: white; } +.chip-transferring { + background-color: #f5a623 !important; + color: white; +} + .header-container-title { flex-grow: 1; text-align: left; diff --git a/ogWebconsole/src/app/components/images/images.component.html b/ogWebconsole/src/app/components/images/images.component.html index 836f976..9a4aa01 100644 --- a/ogWebconsole/src/app/components/images/images.component.html +++ b/ogWebconsole/src/app/components/images/images.component.html @@ -40,7 +40,8 @@ 'chip-failed': image.status === 'failed', 'chip-success': image.status === 'success', 'chip-pending': image.status === 'pending', - 'chip-in-progress': image.status === 'in-progress' + 'chip-in-progress': image.status === 'in-progress', + 'chip-transferring': image.status === 'transferring', }"> {{ getStatusLabel(image[column.columnDef]) }} @@ -65,8 +66,9 @@ + - + diff --git a/ogWebconsole/src/app/components/images/images.component.ts b/ogWebconsole/src/app/components/images/images.component.ts index 4d9e6c7..f74d530 100644 --- a/ogWebconsole/src/app/components/images/images.component.ts +++ b/ogWebconsole/src/app/components/images/images.component.ts @@ -1,15 +1,13 @@ -import { Component, OnInit } from '@angular/core'; +import {Component, Input, OnInit} from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { HttpClient } from '@angular/common/http'; import { MatTableDataSource } from '@angular/material/table'; import { ToastrService } from 'ngx-toastr'; import { DatePipe } from '@angular/common'; import { CreateImageComponent } from './create-image/create-image.component'; -import {CreateCommandComponent} from "../commands/main-commands/create-command/create-command.component"; import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component"; import {ServerInfoDialogComponent} from "../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component"; import {Observable} from "rxjs"; -import {InfoImageComponent} from "../ogboot/pxe-images/info-image/info-image/info-image.component"; import { JoyrideService } from 'ngx-joyride'; import {ExportImageComponent} from "./export-image/export-image.component"; @@ -68,16 +66,34 @@ export class ImagesComponent implements OnInit { displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; private apiUrl = `${this.baseUrl}/images`; + @Input() repositoryUuid: any + private repositoryId: any; constructor( public dialog: MatDialog, private http: HttpClient, private toastService: ToastrService, - private joyrideService: JoyrideService + private joyrideService: JoyrideService, ) {} ngOnInit(): void { - this.search(); + if (this.repositoryUuid) { + this.loadRepository() + } else { + this.search(); + } + } + + loadRepository(): void { + this.http.get(`${this.baseUrl}/image-repositories/${this.repositoryUuid}`, {}).subscribe( + data => { + this.repositoryId = data.id; + this.search(); + }, + error => { + console.error('Error fetching image repositories', error); + } + ) } getStatusLabel(status: string): string { @@ -111,7 +127,7 @@ export class ImagesComponent implements OnInit { search(): void { this.loading = true; - this.http.get(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe( + this.http.get(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}&repository.id=${this.repositoryId}`, { params: this.filters }).subscribe( data => { this.dataSource.data = data['hydra:member']; this.length = data['hydra:totalItems']; @@ -206,6 +222,17 @@ export class ImagesComponent implements OnInit { } }); break; + case 'delete-permanent': + this.http.post(`${this.baseUrl}/images/server/${image.uuid}/delete-permanent`, {}).subscribe({ + next: () => { + this.toastService.success('Petición de eliminación de la papelera temporal enviada'); + this.search() + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }); + break; case 'recover': this.http.post(`${this.baseUrl}/images/server/${image.uuid}/recover`, {}).subscribe({ next: () => { diff --git a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.css b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.css index 2239479..6e0686f 100644 --- a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.css +++ b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.css @@ -20,3 +20,20 @@ mat-dialog-actions { flex-direction: column; gap: 10px; } + +.selected-list ul { + list-style: none; + padding: 0; +} + +.selected-item { + display: flex; + justify-content: space-between; /* Alinea texto a la izquierda y botón a la derecha */ + align-items: center; /* Centra verticalmente */ + padding: 8px; + border-bottom: 1px solid #ccc; +} + +.selected-item button { + margin-left: 10px; +} diff --git a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.html b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.html index 4d834d0..943cc38 100644 --- a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.html +++ b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.html @@ -7,6 +7,19 @@ {{ image.name }} + +
+

Imágenes seleccionadas:

+
    +
  • + {{ getImageName(imageId) }} + +
  • +
+
+ diff --git a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts index 3ffd0df..1faa242 100644 --- a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts +++ b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts @@ -38,6 +38,15 @@ export class ImportImageComponent implements OnInit{ ); } + getImageName(imageId: string): string { + const image = this.images.find(img => img['@id'] === imageId); + return image ? image.name : 'Desconocido'; + } + + removeImage(imageId: string) { + this.selectedClients = this.selectedClients.filter(id => id !== imageId); + } + save() { this.http.post(`${this.baseUrl}${this.data.repository['@id']}/import-image`, { images: this.selectedClients diff --git a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html index d6baad7..ae23d9d 100644 --- a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html +++ b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html @@ -123,6 +123,6 @@ - + diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.html b/ogWebconsole/src/app/components/repositories/repositories.component.html index a8dbde6..204a119 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.html +++ b/ogWebconsole/src/app/components/repositories/repositories.component.html @@ -41,7 +41,7 @@
{{ column.header }} @@ -30,9 +40,10 @@ Acciones - - + + + Acciones - +
- + -- 2.40.1 From fe72fcb5fb802b3f688b303c1a6f8df4d0ce6fc5 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 3 Feb 2025 13:10:39 +0100 Subject: [PATCH 24/28] Added isGlobal property in Image --- .../images/create-image/create-image.component.html | 7 +++++++ .../images/create-image/create-image.component.ts | 3 +++ .../images/export-image/export-image.component.ts | 5 ++++- .../src/app/components/images/images.component.html | 7 ++++++- ogWebconsole/src/app/components/images/images.component.ts | 5 +++++ .../repositories/import-image/import-image.component.ts | 3 +++ ogWebconsole/src/locale/en.json | 1 + ogWebconsole/src/locale/es.json | 1 + 8 files changed, 30 insertions(+), 2 deletions(-) diff --git a/ogWebconsole/src/app/components/images/create-image/create-image.component.html b/ogWebconsole/src/app/components/images/create-image/create-image.component.html index 6f48c33..409bd7f 100644 --- a/ogWebconsole/src/app/components/images/create-image/create-image.component.html +++ b/ogWebconsole/src/app/components/images/create-image/create-image.component.html @@ -42,6 +42,13 @@ {{ 'remotePcLabel' | translate }} + + {{ 'globalImageLabel' | translate }} + +
diff --git a/ogWebconsole/src/app/components/images/create-image/create-image.component.ts b/ogWebconsole/src/app/components/images/create-image/create-image.component.ts index 52fd24b..5043740 100644 --- a/ogWebconsole/src/app/components/images/create-image/create-image.component.ts +++ b/ogWebconsole/src/app/components/images/create-image/create-image.component.ts @@ -30,6 +30,7 @@ export class CreateImageComponent implements OnInit { description: [''], comments: [''], remotePc: [false], + isGlobal: [false], softwareProfile: [''], imageRepository: ['', Validators.required], }); @@ -51,6 +52,7 @@ export class CreateImageComponent implements OnInit { description: [response.description], comments: [response.comments], remotePc: [response.remotePc], + isGlobal: [response.isGlobal], softwareProfile: [response.softwareProfile ? response.softwareProfile['@id'] : null, Validators.required], imageRepository: [response.imageRepository ? response.imageRepository['@id'] : null, Validators.required], }); @@ -95,6 +97,7 @@ export class CreateImageComponent implements OnInit { description: this.imageForm.value.description, comments: this.imageForm.value.comments, remotePc: this.imageForm.value.remotePc, + isGlobal: this.imageForm.value.isGlobal, imageRepository: this.imageForm.value.imageRepository, ...(this.imageForm.value.softwareProfile ? { softwareProfile: this.imageForm.value.softwareProfile } : {}), }; diff --git a/ogWebconsole/src/app/components/images/export-image/export-image.component.ts b/ogWebconsole/src/app/components/images/export-image/export-image.component.ts index ea43de0..9e81e34 100644 --- a/ogWebconsole/src/app/components/images/export-image/export-image.component.ts +++ b/ogWebconsole/src/app/components/images/export-image/export-image.component.ts @@ -2,6 +2,7 @@ import {Component, Inject, OnInit} from '@angular/core'; import {HttpClient} from "@angular/common/http"; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {ToastrService} from "ngx-toastr"; +import {Router} from "@angular/router"; @Component({ selector: 'app-export-image', @@ -18,6 +19,7 @@ export class ExportImageComponent implements OnInit { private http: HttpClient, public dialogRef: MatDialogRef, private toastService: ToastrService, + private router: Router, @Inject(MAT_DIALOG_DATA) public data: { image: any } ) { @@ -29,7 +31,7 @@ export class ExportImageComponent implements OnInit { } loadRepositories() { - this.http.get(`${this.baseUrl}/image-repositories?page=1&itemsPerPage=50`).subscribe( + this.http.get(`${this.baseUrl}/image-repositories?id[ne]=1&page=1&itemsPerPage=50`).subscribe( response => { this.repositories = response['hydra:member']; this.loading = false; @@ -45,6 +47,7 @@ export class ExportImageComponent implements OnInit { next: (response) => { this.toastService.success('Imagen exportada correctamente'); this.dialogRef.close(); + this.router.navigate(['/commands-logs']); }, error: error => { console.error('Error al exportar imagen:', error); diff --git a/ogWebconsole/src/app/components/images/images.component.html b/ogWebconsole/src/app/components/images/images.component.html index 9a4aa01..7162256 100644 --- a/ogWebconsole/src/app/components/images/images.component.html +++ b/ogWebconsole/src/app/components/images/images.component.html @@ -35,6 +35,11 @@ {{ image[column.columnDef] ? 'check_circle' : 'cancel' }} + + + {{ image[column.columnDef] ? 'check_circle' : 'cancel' }} + + + {{ column.cell(image) }} diff --git a/ogWebconsole/src/app/components/images/images.component.ts b/ogWebconsole/src/app/components/images/images.component.ts index f74d530..fbe94b9 100644 --- a/ogWebconsole/src/app/components/images/images.component.ts +++ b/ogWebconsole/src/app/components/images/images.component.ts @@ -47,6 +47,11 @@ export class ImagesComponent implements OnInit { header: 'Remote Pc', cell: (image: any) => `${image.remotePc}` }, + { + columnDef: 'isGlobal', + header: 'Imagen Global', + cell: (image: any) => `${image.isGlobal}` + }, { columnDef: 'status', header: 'Estado', diff --git a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts index 1faa242..8db695a 100644 --- a/ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts +++ b/ogWebconsole/src/app/components/repositories/import-image/import-image.component.ts @@ -2,6 +2,7 @@ import {Component, Inject, OnInit} from '@angular/core'; import {HttpClient} from "@angular/common/http"; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {ToastrService} from "ngx-toastr"; +import {Router} from "@angular/router"; @Component({ selector: 'app-import-image', @@ -18,6 +19,7 @@ export class ImportImageComponent implements OnInit{ private http: HttpClient, public dialogRef: MatDialogRef, private toastService: ToastrService, + private router: Router, @Inject(MAT_DIALOG_DATA) public data: { repository: any } ) { @@ -54,6 +56,7 @@ export class ImportImageComponent implements OnInit{ next: (response) => { this.toastService.success('Peticion de importacion de imagen enviada correctamente'); this.dialogRef.close(); + this.router.navigate(['/commands-logs']); }, error: error => { this.toastService.error('Error al importar imagenes'); diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index ca93953..2ee9b67 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -284,6 +284,7 @@ "usageColumn": "Usage (%)", "formatColumn": "Format", "remotePcLabel": "Remote PC", + "globalImageLabel": "Global image", "ntfsOption": "NTFS", "linuxOption": "LINUX", "cacheOption": "CACHE", diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index cc565bc..16355f8 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -302,6 +302,7 @@ "imagesTitle": "Administrar imágenes", "repositoryTitle": "Administrar repositorios", "remotePcLabel": "Remote PC", + "globalImageLabel": "Imagen Global", "addImageButton": "Añadir imagen", "searchNameDescription": "Busca imágenes por nombre para encontrar rápidamente una imagen específica.", "searchDefaultDescription": "Filtra las imágenes para mostrar solo las imágenes por defecto o no por defecto.", -- 2.40.1 From ab5a4448dce09cbef8ea27d539f4490d49a70392 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 4 Feb 2025 07:57:45 +0100 Subject: [PATCH 25/28] Added global loading --- ogWebconsole/src/app/app.module.ts | 2 ++ .../create-image/create-image.component.html | 2 ++ .../create-image/create-image.component.ts | 5 ++++ .../deploy-image/deploy-image.component.html | 2 ++ .../deploy-image/deploy-image.component.ts | 5 ++++ .../partition-assistant.component.html | 2 ++ .../partition-assistant.component.ts | 5 ++++ .../main-layout/main-layout.component.css | 9 ++++---- .../app/shared/loading/loading.component.css | 12 ++++++++++ .../app/shared/loading/loading.component.html | 3 +++ .../shared/loading/loading.component.spec.ts | 23 +++++++++++++++++++ .../app/shared/loading/loading.component.ts | 10 ++++++++ 12 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 ogWebconsole/src/app/shared/loading/loading.component.css create mode 100644 ogWebconsole/src/app/shared/loading/loading.component.html create mode 100644 ogWebconsole/src/app/shared/loading/loading.component.spec.ts create mode 100644 ogWebconsole/src/app/shared/loading/loading.component.ts diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index c460a6e..a079e76 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -126,6 +126,7 @@ import { CreateMenuComponent } from './components/menus/create-menu/create-menu. import { CreateMultipleClientComponent } from './components/groups/shared/clients/create-multiple-client/create-multiple-client.component'; import { ExportImageComponent } from './components/images/export-image/export-image.component'; import {ImportImageComponent} from "./components/repositories/import-image/import-image.component"; +import { LoadingComponent } from './shared/loading/loading.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); } @@ -210,6 +211,7 @@ export function HttpLoaderFactory(http: HttpClient) { CreateMultipleClientComponent, ExportImageComponent, ImportImageComponent, + LoadingComponent, ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html index 8f02096..634a3bd 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html @@ -1,3 +1,5 @@ + +

diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts index 08d95b1..a5d1706 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts @@ -21,6 +21,7 @@ export class CreateClientImageComponent { selectedPartition: any = null; name: string = ''; client: any = null; + loading: boolean = false; dataSource = new MatTableDataSource(); columns = [ { @@ -99,6 +100,8 @@ export class CreateClientImageComponent { } save(): void { + this.loading = true; + const payload = { client: `/clients/${this.clientId}`, name: this.name, @@ -111,10 +114,12 @@ export class CreateClientImageComponent { .subscribe({ next: (response) => { this.toastService.success('Petición de creación de imagen enviada'); + this.loading = false; this.router.navigate(['/images']); }, error: (error) => { this.toastService.error(error.error['hydra:description']); + this.loading = false; } } ); diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html index d7582a9..9abcb98 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html @@ -1,3 +1,5 @@ + +

diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts index aec624a..30be7b3 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts @@ -34,6 +34,7 @@ export class DeployImageComponent { name: string = ''; client: any = null; clientData: any = []; + loading: boolean = false; protected p2pModeOptions = [ { name: 'Leecher', value: 'leecher' }, @@ -150,6 +151,8 @@ export class DeployImageComponent { } save(): void { + this.loading = true; + if (!this.selectedImage) { this.toastService.error('Debe seleccionar una imagen'); return; @@ -188,6 +191,7 @@ export class DeployImageComponent { .subscribe({ next: (response) => { this.toastService.success('Petición de despliegue enviada correctamente'); + this.loading = false; this.router.navigate(['/commands-logs']); }, error: (error) => { @@ -201,6 +205,7 @@ export class DeployImageComponent { "extendedTimeOut": 0, "tapToDismiss": false }); + this.loading = false; } } ); diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html index c254e55..7c09394 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html @@ -1,3 +1,5 @@ + +

diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts index 308658d..94c85da 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts @@ -40,6 +40,7 @@ export class PartitionAssistantComponent { data: any = {}; disks: { diskNumber: number; totalDiskSize: number; partitions: Partition[]; chartData: any[]; used: number; percentage: number }[] = []; clientData: any = []; + loading: boolean = false; private apiUrl: string = this.baseUrl + '/partitions'; @@ -258,6 +259,8 @@ export class PartitionAssistantComponent { return; } + this.loading = true; + const totalPartitionSize = this.selectedDisk.partitions.reduce((sum: any, partition: { size: any; }) => sum + partition.size, 0); if (totalPartitionSize > this.selectedDisk.totalDiskSize) { @@ -295,10 +298,12 @@ export class PartitionAssistantComponent { this.http.post(this.apiUrl, bulkPayload).subscribe( (response) => { this.toastService.success('Particiones creadas exitosamente para el disco seleccionado.'); + this.loading = false; this.router.navigate(['/commands-logs']); }, (error) => { console.error('Error al crear las particiones:', error); + this.loading = false; this.toastService.error('Error al crear las particiones.'); } ); diff --git a/ogWebconsole/src/app/layout/main-layout/main-layout.component.css b/ogWebconsole/src/app/layout/main-layout/main-layout.component.css index 31b6b4c..c60e4f2 100644 --- a/ogWebconsole/src/app/layout/main-layout/main-layout.component.css +++ b/ogWebconsole/src/app/layout/main-layout/main-layout.component.css @@ -4,15 +4,16 @@ .container { width: 100vw; - height: calc(100vh - 10vh); + height: calc(100vh - 10vh); } .sidebar { - width: 250px; + width: 250px; + z-index: auto !important; } .content { margin: 0px 10px 10px 10px; padding: 0px 10px 10px 10px; - box-sizing: border-box; -} \ No newline at end of file + box-sizing: border-box; +} diff --git a/ogWebconsole/src/app/shared/loading/loading.component.css b/ogWebconsole/src/app/shared/loading/loading.component.css new file mode 100644 index 0000000..04b222d --- /dev/null +++ b/ogWebconsole/src/app/shared/loading/loading.component.css @@ -0,0 +1,12 @@ +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 99999; +} diff --git a/ogWebconsole/src/app/shared/loading/loading.component.html b/ogWebconsole/src/app/shared/loading/loading.component.html new file mode 100644 index 0000000..2ecad58 --- /dev/null +++ b/ogWebconsole/src/app/shared/loading/loading.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/ogWebconsole/src/app/shared/loading/loading.component.spec.ts b/ogWebconsole/src/app/shared/loading/loading.component.spec.ts new file mode 100644 index 0000000..0356eca --- /dev/null +++ b/ogWebconsole/src/app/shared/loading/loading.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoadingComponent } from './loading.component'; + +describe('LoadingComponent', () => { + let component: LoadingComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [LoadingComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LoadingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/shared/loading/loading.component.ts b/ogWebconsole/src/app/shared/loading/loading.component.ts new file mode 100644 index 0000000..49bd8da --- /dev/null +++ b/ogWebconsole/src/app/shared/loading/loading.component.ts @@ -0,0 +1,10 @@ +import {Component, Input} from '@angular/core'; + +@Component({ + selector: 'app-loading', + templateUrl: './loading.component.html', + styleUrl: './loading.component.css' +}) +export class LoadingComponent { + @Input() isLoading: boolean = false; +} -- 2.40.1 From 22ed91551e4269f27a87f4cf42523b87600d19c4 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 4 Feb 2025 08:03:05 +0100 Subject: [PATCH 26/28] Fixed test --- .../deploy-image/deploy-image.component.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts index 94ead75..f38ab00 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts @@ -18,6 +18,7 @@ import { ToastrModule, ToastrService } from 'ngx-toastr'; import { provideRouter } from '@angular/router'; import { MatSelectModule } from '@angular/material/select'; import {MatExpansionModule} from "@angular/material/expansion"; +import {LoadingComponent} from "../../../../../shared/loading/loading.component"; describe('DeployImageComponent', () => { let component: DeployImageComponent; @@ -25,7 +26,7 @@ describe('DeployImageComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [DeployImageComponent], + declarations: [DeployImageComponent, LoadingComponent], imports: [ ReactiveFormsModule, FormsModule, -- 2.40.1 From 24d81ac5332bc3819b0ba573d602fd8afd53a4b9 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 4 Feb 2025 08:12:32 +0100 Subject: [PATCH 27/28] Fixed test --- .../deploy-image/deploy-image.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts index f38ab00..2548243 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts @@ -26,7 +26,7 @@ describe('DeployImageComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [DeployImageComponent, LoadingComponent], + declarations: [DeployImageComponent, LoadingComponent ], imports: [ ReactiveFormsModule, FormsModule, -- 2.40.1 From 0b790543b90e703075b301dcccfbc06621ad492f Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 4 Feb 2025 09:18:00 +0100 Subject: [PATCH 28/28] Fixed test --- .../deploy-image/deploy-image.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts index 2548243..f38ab00 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts @@ -26,7 +26,7 @@ describe('DeployImageComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [DeployImageComponent, LoadingComponent ], + declarations: [DeployImageComponent, LoadingComponent], imports: [ ReactiveFormsModule, FormsModule, -- 2.40.1