From 609304acc02fb7f9993d1fabbdb7637c1455675d Mon Sep 17 00:00:00 2001 From: jmbardallo Date: Mon, 5 Oct 2026 09:26:55 +0200 Subject: [PATCH] feat: standardise and refactor OU selection and migrate control flow --- CHANGELOG.md | 10 + ogWebconsole/package-lock.json | 4 +- ogWebconsole/package.json | 3 +- ogWebconsole/src/app/app.module.ts | 4 +- .../add-user-modal.component.html | 17 +- .../add-user-modal.component.ts | 10 +- .../create-task/create-task.component.html | 229 ++++---- .../create-task/create-task.component.ts | 9 +- .../change-parent.component.html | 12 +- .../change-parent/change-parent.component.ts | 10 +- .../create-multiple-client.component.html | 86 +-- .../create-multiple-client.component.ts | 5 +- .../manage-client.component.html | 318 ++++++----- .../manage-client/manage-client.component.ts | 4 - .../manage-organizational-unit.component.html | 496 +++++++++--------- .../manage-organizational-unit.component.ts | 5 +- .../pxe-boot-files.component.html | 21 +- .../pxe-boot-files.component.ts | 32 +- .../add-clients-to-subnet.component.html | 19 +- .../add-clients-to-subnet.component.ts | 30 +- .../shared/ou-select/ou-select.component.css | 11 + .../shared/ou-select/ou-select.component.html | 21 + .../ou-select/ou-select.component.spec.ts | 23 + .../shared/ou-select/ou-select.component.ts | 77 +++ 24 files changed, 791 insertions(+), 665 deletions(-) create mode 100644 ogWebconsole/src/app/components/shared/ou-select/ou-select.component.css create mode 100644 ogWebconsole/src/app/components/shared/ou-select/ou-select.component.html create mode 100644 ogWebconsole/src/app/components/shared/ou-select/ou-select.component.spec.ts create mode 100644 ogWebconsole/src/app/components/shared/ou-select/ou-select.component.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2050604..b42aea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [1.14.0] - 2026-10-05 + +### Added +- **UI/UX**: Añadido buscador en todos los desplegables de selección de Unidad Organizativa gracias al nuevo componente reutilizable `OuSelectComponent`. + +### Changed +- **UI/UX**: Estandarizada la selección de Unidades Organizativas en toda la consola para mostrar de forma consistente la ruta jerárquica (breadcrumb) del elemento. +- **Refactor**: Extraída la lógica de selección de Unidades Organizativas a un componente genérico (`OuSelectComponent`) para cumplir con el principio DRY. +- **Refactor**: Migrada la sintaxis de plantillas (directivas estructurales `*ngIf`, `*ngFor`, `*ngSwitch`) a la nueva sintaxis de control de flujo de Angular 18 (`@if`, `@for`, `@switch`) en los componentes afectados por la refactorización. + ## [1.13.0] - 2026-10-02 ### Added diff --git a/ogWebconsole/package-lock.json b/ogWebconsole/package-lock.json index c7ff158..c117201 100644 --- a/ogWebconsole/package-lock.json +++ b/ogWebconsole/package-lock.json @@ -1,12 +1,12 @@ { "name": "og-webconsole", - "version": "0.5.0", + "version": "1.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "og-webconsole", - "version": "0.5.0", + "version": "1.14.0", "dependencies": { "@angular/animations": "^18.0.0", "@angular/cdk": "~18.0.0", diff --git a/ogWebconsole/package.json b/ogWebconsole/package.json index 670cf55..d3c85de 100644 --- a/ogWebconsole/package.json +++ b/ogWebconsole/package.json @@ -1,6 +1,6 @@ { "name": "og-webconsole", - "version": "0.5.0", + "version": "1.14.0", "scripts": { "ng": "ng", "start": "ng serve", @@ -54,4 +54,3 @@ "typescript": "~5.4.5" } } - diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index d6b298c..bb3b54a 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -210,6 +210,7 @@ export function HttpLoaderFactory(http: HttpClient) { import { ImportLegacyComponent } from './components/admin/import-legacy/import-legacy.component'; import { PageNotFoundComponent } from './shared/page-not-found/page-not-found.component'; +import { OuSelectComponent } from './components/shared/ou-select/ou-select.component'; export function initializeApp(configService: ConfigService) { return () => configService.loadConfig(); @@ -351,7 +352,8 @@ registerLocaleData(localeEs, 'es-ES'); OgdhcpStatusComponent, StatusLogsComponent, ImportLegacyComponent, - PageNotFoundComponent + PageNotFoundComponent, + OuSelectComponent ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.html b/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.html index 9acb0a0..d9c6ad6 100644 --- a/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.html +++ b/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.html @@ -23,16 +23,13 @@ - - {{ 'labelOrganizationalUnit' | translate }} - - @for (unit of organizationalUnits; track unit['@id']) { - - {{ unit.name }} - - } - - + + {{ 'cardView' | translate }} diff --git a/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.ts b/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.ts index bceaa5b..9312390 100644 --- a/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.ts +++ b/ogWebconsole/src/app/components/admin/users/users/add-user-modal/add-user-modal.component.ts @@ -7,6 +7,7 @@ import { ToastrService } from "ngx-toastr"; import { HttpClient } from "@angular/common/http"; import { DataService } from "../data.service"; import { ConfigService } from '@services/config.service'; +import { DataService as GroupsDataService } from '../../../../groups/services/data.service'; interface UserGroup { '@id': string; @@ -44,6 +45,7 @@ export class AddUserModalComponent implements OnInit { private http: HttpClient, private fb: FormBuilder, private dataService: DataService, + private groupsDataService: GroupsDataService, private toastService: ToastrService, private configService: ConfigService ) { @@ -71,7 +73,13 @@ export class AddUserModalComponent implements OnInit { }); this.dataService.getOrganizationalUnits().subscribe((data) => { - this.organizationalUnits = data['hydra:member'].filter((item: any) => item.type === 'organizational-unit'); + const allUnits = data['hydra:member']; + this.organizationalUnits = allUnits + .filter((item: any) => item.type === 'organizational-unit') + .map((unit: any) => ({ + ...unit, + path: this.groupsDataService.getOrganizationalUnitPath(unit, allUnits) + })); }); if (this.data) { diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html index 58720dd..d3cc657 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html @@ -3,116 +3,127 @@ - + @if (loading) { + + } -
- - - add_task - {{ 'labelCreateTask' | translate }} - - - playlist_add - {{ 'addToExistingTask' | translate }} - - -
- -
- - {{ 'selectTask' | translate }} - - {{ task.name }} - - - - - {{ 'executionOrder' | translate }} - - -
- -
- - {{ 'labelRoleName' | translate }} - - {{ 'requiredFieldError' | translate }} - - - - {{ 'labelNotes' | translate }} - - - - - {{ 'scheduleAfterCreate' | translate }} - - - - - - {{ 'labelScope' | translate }} - - {{ 'organizationalUnit' | translate }} - {{ 'scopeClassroomsGroup' | translate }} - {{ 'classrooms' | translate }} - {{ 'clientGroupsTitle' | translate }} - {{ 'labelClients' | translate }} - - - - - {{ 'labelOrganizationalUnit' | translate }} - - -
{{ unit.name }}
-
{{ unit.path }}
-
-
-
- -
-

{{ 'selectedClients' | translate }}

- -
- info - {{ 'preSelectedInfo' | translate }} -
- -
-
- - -
-
{{ client.name || client.hostname }}
-
- {{ client.ip }} - - {{ client.status }} - -
-
- - check_circle - -
-
-
-
+ @if (!loading) { +
+ @if (data?.source === 'assistant') { + + + add_task + {{ 'labelCreateTask' | translate }} + + + playlist_add + {{ 'addToExistingTask' | translate }} + + + }
- + } + + @if (taskMode === 'add') { +
+ + {{ 'selectTask' | translate }} + + @for (task of existingTasks; track task) { + {{ task.name }} + } + + + + {{ 'executionOrder' | translate }} + + +
+ } + + @if (taskMode === 'create' && taskForm && !loading) { +
+ + {{ 'labelRoleName' | translate }} + + @if (taskForm.get('name')?.invalid) { + {{ 'requiredFieldError' | translate }} + } + + + {{ 'labelNotes' | translate }} + + + @if (!editing) { + + {{ 'scheduleAfterCreate' | translate }} + + } + + + {{ 'labelScope' | translate }} + + {{ 'organizationalUnit' | translate }} + {{ 'scopeClassroomsGroup' | translate }} + {{ 'classrooms' | translate }} + {{ 'clientGroupsTitle' | translate }} + {{ 'labelClients' | translate }} + + + @if (taskForm.get('scope')?.value !== 'clients') { + + + } + @if (taskForm.get('scope')?.value === 'clients') { +
+

{{ 'selectedClients' | translate }}

+ @if (data?.selectedClients && data.selectedClients.length > 0) { +
+ info + {{ 'preSelectedInfo' | translate }} +
+ } +
+
+ @for (client of clients; track client) { + + +
+
{{ client.name || client.hostname }}
+
+ {{ client.ip }} + + {{ client.status }} + +
+
+ + check_circle + +
+
+ } +
+
+
+ } +
+ } @@ -120,7 +131,7 @@ diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts index 991c7c2..5602349 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts @@ -10,6 +10,7 @@ import {of} from "rxjs"; import {startWith, switchMap} from "rxjs/operators"; import {CreateTaskScheduleComponent} from "../create-task-schedule/create-task-schedule.component"; import {ShowTaskScriptComponent} from "../show-task-script/show-task-script.component"; +import { DataService as GroupsDataService } from "../../../groups/services/data.service"; @Component({ selector: 'app-create-task', @@ -42,7 +43,8 @@ export class CreateTaskComponent implements OnInit { private toastr: ToastrService, private dialog: MatDialog, public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: any + @Inject(MAT_DIALOG_DATA) public data: any, + private dataService: GroupsDataService ) { this.baseUrl = this.configService.apiUrl; this.apiUrl = `${this.baseUrl}/command-tasks`; @@ -214,7 +216,10 @@ export class CreateTaskComponent implements OnInit { return new Promise((resolve, reject) => { this.http.get(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=100`).subscribe( (data) => { - this.allOrganizationalUnits = data['hydra:member']; + this.allOrganizationalUnits = data['hydra:member'].map((unit: any) => ({ + ...unit, + path: this.dataService.getOrganizationalUnitPath(unit, data['hydra:member']) + })); this.availableOrganizationalUnits = [...this.allOrganizationalUnits]; resolve(); }, diff --git a/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.html b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.html index e0763a6..284e256 100644 --- a/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.html +++ b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.html @@ -1,12 +1,12 @@

{{ 'moveClientsTo' | translate }}

- - {{ 'selectClassroomPrompt' | translate }} - - {{ unit.name }} - - + +
diff --git a/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.ts b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.ts index 16c231f..1de639e 100644 --- a/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.ts @@ -6,6 +6,7 @@ import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {ToastrService} from "ngx-toastr"; import {Router} from "@angular/router"; import {ConfigService} from "@services/config.service"; +import {DataService} from "../../services/data.service"; @Component({ selector: 'app-change-parent', @@ -25,6 +26,7 @@ export class ChangeParentComponent implements OnInit { private toastService: ToastrService, private router: Router, private configService: ConfigService, + private dataService: DataService, @Inject(MAT_DIALOG_DATA) public data: { clients: any} ) { this.baseUrl = this.configService.apiUrl; @@ -38,7 +40,11 @@ export class ChangeParentComponent implements OnInit { loadUnits() { this.http.get(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=500`).subscribe( response => { - this.units = response['hydra:member']; + const allUnits = response['hydra:member']; + this.units = allUnits.map((unit: any) => ({ + ...unit, + path: this.dataService.getOrganizationalUnitPath(unit, allUnits) + })); this.loading = false; }, error => console.error(this.translate.instant('errorFetchingOrganizationalUnits1'), error) @@ -48,7 +54,7 @@ export class ChangeParentComponent implements OnInit { save() { this.http.post(`${this.baseUrl}/clients/change-organizational-unit`, { clients: this.data.clients.map((client: any) => client['@id']), - organizationalUnit: this.newOU['@id'] + organizationalUnit: this.newOU }).subscribe({ next: (response) => { this.toastService.success(this.translate.instant('parentChangedSuccessfully')); 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 c810f83..1e365a0 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 @@ -3,57 +3,59 @@
-
- - {{ 'labelOrganizationalUnit' | translate }} - - - {{ getSelectedParentName() }} - - -
{{ unit.name }}
-
{{ unit.path }}
-
-
-
-
+ @if (!loading) { +
+ + +
+ }

{{ 'orAddThemManually' | translate }}

-
- - +
+ } +
+ + @if (uploadedClients.length > 0) { +

{{ 'importedClientsZero' | translate }}

+ } + @if (uploadedClients.length > 0) { +
+ @if (uploadedClients.length > 0) { + + + + + + + + + + + + + + + +
{{ 'labelRoleName' | translate }} {{ client.name }} {{ 'labelIpCaps' | translate }} {{ client.ip }} {{ 'labelMac' | translate }} {{ client.mac }}
+ }
-
- -

{{ 'importedClientsZero' | translate }}

-
- - - - - - - - - - - - - - - - - - -
{{ 'labelRoleName' | translate }} {{ client.name }} {{ 'labelIpCaps' | translate }} {{ client.ip }} {{ 'labelMac' | translate }} {{ client.mac }}
-
+ }
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 9c8000c..af0a3da 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 @@ -65,10 +65,7 @@ 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 { this.organizationalUnit = organizationalUnit.value; diff --git a/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.html b/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.html index 40559ff..7eacd05 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.html +++ b/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.html @@ -1,175 +1,155 @@ -

{{ dialogTitle | translate }}

-
- -
- - - {{ 'labelOrganizationalUnit' | translate }} - - - {{ getSelectedParentName() }} - - @for (unit of parentUnitsWithPaths; track unit.id) { - -
{{ unit.name }}
-
{{ unit.path }}
-
- } -
-
- - - {{ 'labelName' | translate }} - - - - - {{ 'labelSerialNumber' | translate }} - - - - - {{ 'labelNetiface' | translate }} - - @for (type of netifaceTypes; track type.value) { - {{ type.name }} - } - - - - - {{ 'labelMac' | translate }} - - {{ 'macError' | translate }} - - - - {{ 'labelIp' | translate }} - - {{ 'ipError' | translate }} - - - - {{ 'labelRepository' | translate }} - - @for (repository of repositories; track repository['@id']) { - {{ repository.name }} - } - - - - - {{ 'maintenance' | translate }} - - - -
- {{ 'menuConfiguration' | translate }} -
- - {{ 'labelMenu' | translate }} - - @for (menu of menus; track menu['@id']) { - {{ menu.name }} - } - - {{ 'menuError' | translate }} - - - - {{ 'labelResolution' | translate }} - - {{ 'resolution640x480x8' | translate }} - {{ 'resolution800x600x8' | translate }} - {{ 'resolution1024x768x8' | translate }} - {{ 'resolution1280x1024x8' | translate }} - {{ 'resolution640x480x16' | translate }} - {{ 'resolution800x600x16' | translate }} - {{ 'resolution1024x768x16' | translate }} - {{ 'resolution1280x1024x16' | translate }} - {{ 'resolution640x480x24' | translate }} - {{ 'resolution800x600x24' | translate }} - {{ 'resolution1024x768x24' | translate }} - {{ 'resolution1280x1024x24' | translate }} - - {{ 'resolutionError' | translate }} - -
-
- - -
- {{ 'bootConfiguration' | translate }} -
- - - {{ 'bootServer' | translate }} - - @for (server of bootServers; track server['@id']) { - {{ server.name }} - } - - - - - {{ 'labelOgLive' | translate }} - - @for (ogLive of ogLives; track ogLive['@id']) { - {{ ogLive.name }} - } - @if (ogLives.length === 0 && clientForm.get('bootServer')?.value) { - {{ 'noOgLivesInServer' | translate }} - } - - - - - {{ 'labelTemplate' | translate }} - - @for (template of templates; track template['@id']) { - {{ template.name }} - } - @if (templates.length === 0 && clientForm.get('bootServer')?.value) { - {{ 'noTemplatesInServer' | translate }} - } - - - -
-
- - @if (isEditMode) { -
- {{ 'labelHardwareProfile' | translate }} -
- - - {{ 'noHardwareProfileAssigned' | translate }} - @for (profile of hardwareProfiles; track profile['@id']) { - {{ profile.description }} - } - - - -
-
+

{{ dialogTitle | translate }}

+
+ @if (loading) { + + } + @if (clientForm && !loading) { + + + + + {{ 'labelName' | translate }} + + + + {{ 'labelSerialNumber' | translate }} + + + + {{ 'labelNetiface' | translate }} + + @for (type of netifaceTypes; track type.value) { + {{ type.name }} } - - - {{ 'labelNotes' | translate }} - + + + + {{ 'labelMac' | translate }} + + {{ 'macError' | translate }} + + + {{ 'labelIp' | translate }} + + {{ 'ipError' | translate }} + + + {{ 'labelRepository' | translate }} + + @for (repository of repositories; track repository['@id']) { + {{ repository.name }} + } + + + + {{ 'maintenance' | translate }} + + +
+ {{ 'menuConfiguration' | translate }} +
+ + {{ 'labelMenu' | translate }} + + @for (menu of menus; track menu['@id']) { + {{ menu.name }} + } + + {{ 'menuError' | translate }} - -
+ + {{ 'labelResolution' | translate }} + + {{ 'resolution640x480x8' | translate }} + {{ 'resolution800x600x8' | translate }} + {{ 'resolution1024x768x8' | translate }} + {{ 'resolution1280x1024x8' | translate }} + {{ 'resolution640x480x16' | translate }} + {{ 'resolution800x600x16' | translate }} + {{ 'resolution1024x768x16' | translate }} + {{ 'resolution1280x1024x16' | translate }} + {{ 'resolution640x480x24' | translate }} + {{ 'resolution800x600x24' | translate }} + {{ 'resolution1024x768x24' | translate }} + {{ 'resolution1280x1024x24' | translate }} + + {{ 'resolutionError' | translate }} + +
+ + +
+ {{ 'bootConfiguration' | translate }} +
+ + {{ 'bootServer' | translate }} + + @for (server of bootServers; track server['@id']) { + {{ server.name }} + } + + + + {{ 'labelOgLive' | translate }} + + @for (ogLive of ogLives; track ogLive['@id']) { + {{ ogLive.name }} + } + @if (ogLives.length === 0 && clientForm.get('bootServer')?.value) { + {{ 'noOgLivesInServer' | translate }} + } + + + + {{ 'labelTemplate' | translate }} + + @for (template of templates; track template['@id']) { + {{ template.name }} + } + @if (templates.length === 0 && clientForm.get('bootServer')?.value) { + {{ 'noTemplatesInServer' | translate }} + } + + +
+
+ @if (isEditMode) { +
+ {{ 'labelHardwareProfile' | translate }} +
+ + + {{ 'noHardwareProfileAssigned' | translate }} + @for (profile of hardwareProfiles; track profile['@id']) { + {{ profile.description }} + } + + + +
+
+ } + + {{ 'labelNotes' | translate }} + + + + } +
- - + + \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.ts b/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.ts index 6b7ee1f..5398c0b 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/clients/manage-client/manage-client.component.ts @@ -137,10 +137,6 @@ export class ManageClientComponent implements OnInit { }); } - getSelectedParentName(): string | undefined { - const parentId = this.clientForm.get('organizationalUnit')?.value; - return this.parentUnitsWithPaths.find(unit => unit.id === parentId)?.name; - } loadOgLivesByServer(bootServerIri: string): Promise { // Extract UUID from IRI (e.g. "/boot-servers/xxxx-xxx" -> "xxxx-xxx") diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html b/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html index b4f2b82..48c06ad 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.html @@ -1,261 +1,279 @@ -

{{ isEditMode ? ('edit' | translate) : ('createButton' | translate) }} {{ - 'labelOrganizationalUnit' | translate }}

-
- - - {{ 'labelGeneralTab' | translate }} -
- - {{ 'labelType' | translate }} - - - {{ typeTranslations[type] }} - - - - - {{ 'labelRoleName' | translate }} - - - - {{ 'labelCreateOrgUnitparent' | translate }} - - - {{ getSelectedParentName() }} - - -
{{ unit.name }}
-
{{ unit.path }}
-
-
-
+

{{ isEditMode ? ('edit' | translate) : ('createButton' | translate) }} {{ + 'labelOrganizationalUnit' | translate }}

+
+ + @if (loading) { + + } + @if (!loading) { + {{ 'labelGeneralTab' | translate }} + } + @if (generalFormGroup && !loading) { + + + {{ 'labelType' | translate }} + + @for (type of filteredTypes; track type) { + + {{ typeTranslations[type] }} + + } + + + + {{ 'labelRoleName' | translate }} + + + + + + {{ 'labelDescription' | translate }} + + + + {{ 'excludeParentChanges' | translate }} + + + } - - {{ 'labelDescription' | translate }} - - + + @if (generalFormGroup.value.type === 'classroom' && !loading) { + {{ 'labelClassroomInfoStep' | translate }} +
+ + {{ 'labelLocation' | translate }} + + + + {{ 'labelCapacity' | translate }} + + @if (classroomInfoFormGroup.get('capacity')?.hasError('min')) { + + {{ 'capacityWarning' | translate }} + + } + + + {{ 'labelAssociatedCalendar' | translate }} + + @for (calendar of calendars; track calendar) { + + {{ calendar.name }} + + } + + +
+ {{ 'projectorAlt' | translate }} + {{ 'boardToggle' | translate }} + {{ 'remotePcLabel' | translate }} + @if (classroomInfoFormGroup.get('remotePc')?.value) { + {{ 'remotePcRebootOnUnreserveLabel' | translate }} + } +
+
+ } - - {{ 'excludeParentChanges' | translate }} - - - - - @if (generalFormGroup.value.type === 'classroom' && !loading) { - {{ 'labelClassroomInfoStep' | translate }} -
+ + @if (!loading) { + {{ 'labelNetworkSettingsStep' | translate }} + } + @if (networkSettingsFormGroup && !loading) { + + +
+ {{ 'bootConfiguration' | translate }} +
- {{ 'labelLocation' | translate }} - - - - {{ 'labelCapacity' | translate }} - - - {{ 'capacityWarning' | translate }} - - - - {{ 'labelAssociatedCalendar' | translate }} - - - {{ calendar.name }} - - - -
- {{ 'projectorAlt' | translate }} - {{ 'boardToggle' | translate }} - {{ 'remotePcLabel' | translate }} - @if (classroomInfoFormGroup.get('remotePc')?.value) { - {{ 'remotePcRebootOnUnreserveLabel' | translate }} + {{ 'bootServer' | translate }} + + @for (server of bootServers; track server['@id']) { + {{ server.name }} } -
- - } - - - {{ 'labelNetworkSettingsStep' | translate }} -
- - -
- {{ 'bootConfiguration' | translate }} -
- - - {{ 'bootServer' | translate }} - - @for (server of bootServers; track server['@id']) { - {{ server.name }} - } - - - - - {{ 'ogLive' | translate }} - - @for (oglive of ogLives; track oglive['@id']) { - {{ oglive.name }} - } - @if (ogLives.length === 0 && networkSettingsFormGroup.get('bootServer')?.value) { - {{ 'noOgLivesInServer' | translate }} - } - - - - - {{ 'labelTemplate' | translate }} - - @for (template of pxeTemplates; track template['@id']) { - {{ template.name }} - } - @if (pxeTemplates.length === 0 && networkSettingsFormGroup.get('bootServer')?.value) { - {{ 'noTemplatesInServer' | translate }} - } - - - -
-
- - - {{ 'labelRepository' | translate }} - - - {{ repository.name }} - - + - {{ 'labelProxy' | translate }} - + {{ 'ogLive' | translate }} + + @for (oglive of ogLives; track oglive['@id']) { + {{ oglive.name }} + } + @if (ogLives.length === 0 && networkSettingsFormGroup.get('bootServer')?.value) { + {{ 'noOgLivesInServer' | translate }} + } + - {{ 'labelDns' | translate }} - + {{ 'labelTemplate' | translate }} + + @for (template of pxeTemplates; track template['@id']) { + {{ template.name }} + } + @if (pxeTemplates.length === 0 && networkSettingsFormGroup.get('bootServer')?.value) { + {{ 'noTemplatesInServer' | translate }} + } + + +
+
+ + {{ 'labelRepository' | translate }} + + @for (repository of repositories; track repository) { + + {{ repository.name }} + + } + + + + {{ 'labelProxy' | translate }} + + + + {{ 'labelDns' | translate }} + + + + {{ 'labelNetmask' | translate }} + + + + {{ 'labelNetiface' | translate }} + + @for (type of netifaceTypes; track type) { + + {{ type.name }} + + } + + + + {{ 'labelRouter' | translate }} + + + + {{ 'labelNtp' | translate }} + + + + {{ 'labelHardwareProfile' | translate }} + + @for (unit of hardwareProfiles; track unit) { + {{ unit.description + }} + } + + + +
+ {{ 'multicastConfiguration' | translate }} +
+ + {{ 'labelMcastIp' | translate }} + - {{ 'labelNetmask' | translate }} - + {{ 'labelMcastSpeed' | translate }} + - {{ 'labelNetiface' | translate }} - - - {{ type.name }} - - + {{ 'labelMcastPort' | translate }} + - {{ 'labelRouter' | translate }} - + {{ 'labelMcastMode' | translate }} + + @for (option of multicastModeOptions; track option) { + + {{ option.name }} + + } + + +
+
+ +
+ {{ 'p2pConfiguration' | translate }} +
+ + {{ 'labelP2pMode' | translate }} + + @for (option of p2pModeOptions; track option) { + + {{ option.name }} + + } + + + @if (networkSettingsFormGroup.get('p2pMode')?.value === 'seeder') { + + {{ 'labelP2pTime' | translate }} (minutos) + + + } +
+
+ +
+ {{ 'menuConfiguration' | translate }} +
+ + {{ 'labelMenu' | translate }} + + @for (menu of menus; track menu) { + + {{ menu.name }} + + } + - {{ 'labelNtp' | translate }} - + {{ 'labelResolution' | translate }} + + {{ 'resolution640x480x8' | translate }} + {{ 'resolution800x600x8' | translate }} + {{ 'resolution1024x768x8' | translate }} + {{ 'resolution1280x1024x8' | translate }} + {{ 'resolution640x480x16' | translate }} + {{ 'resolution800x600x16' | translate }} + {{ 'resolution1024x768x16' | translate }} + {{ 'resolution1280x1024x16' | translate }} + {{ 'resolution640x480x24' | translate }} + {{ 'resolution800x600x24' | translate }} + {{ 'resolution1024x768x24' | translate }} + {{ 'resolution1280x1024x24' | translate }} + - - {{ 'labelHardwareProfile' | translate }} - - {{ unit.description - }} - - - -
- {{ 'multicastConfiguration' | translate }} -
- - {{ 'labelMcastIp' | translate }} - - - - {{ 'labelMcastSpeed' | translate }} - - - - {{ 'labelMcastPort' | translate }} - - - - {{ 'labelMcastMode' | translate }} - - - {{ option.name }} - - - -
-
+
+
+ + } - -
- {{ 'p2pConfiguration' | translate }} -
- - {{ 'labelP2pMode' | translate }} - - - {{ option.name }} - - - - - {{ 'labelP2pTime' | translate }} (minutos) - - -
-
- - -
- {{ 'menuConfiguration' | translate }} -
- - {{ 'labelMenu' | translate }} - - - {{ menu.name }} - - - - - - {{ 'labelResolution' | translate }} - - {{ 'resolution640x480x8' | translate }} - {{ 'resolution800x600x8' | translate }} - {{ 'resolution1024x768x8' | translate }} - {{ 'resolution1280x1024x8' | translate }} - {{ 'resolution640x480x16' | translate }} - {{ 'resolution800x600x16' | translate }} - {{ 'resolution1024x768x16' | translate }} - {{ 'resolution1280x1024x16' | translate }} - {{ 'resolution640x480x24' | translate }} - {{ 'resolution800x600x24' | translate }} - {{ 'resolution1024x768x24' | translate }} - {{ 'resolution1280x1024x24' | translate }} - - -
-
- - - - {{ 'labelAdditionalInfoStep' | translate }} -
- - {{ 'labelComments' | translate }} - - -
-
+ + @if (!loading) { + {{ 'labelAdditionalInfoStep' | translate }} + } + @if (additionalInfoFormGroup && !loading) { +
+ + {{ 'labelComments' | translate }} + + +
+ } +
- - + + \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.ts b/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.ts index c13d359..c3ae2d6 100644 --- a/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component.ts @@ -430,10 +430,7 @@ export class ManageOrganizationalUnitComponent implements OnInit { ); } - getSelectedParentName(): string | undefined { - const parentId = this.generalFormGroup.get('parent')?.value; - return this.parentUnitsWithPaths.find(unit => unit.id === parentId)?.name; - } + loadBootServers(): Promise { return new Promise((resolve, reject) => { diff --git a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html index 3c8447c..99c8520 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html +++ b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html @@ -25,19 +25,14 @@ } - - {{ 'labelSelectClass' | translate }} - - - @for (unit of getFilteredUnits(); track unit.id) { - {{ unit.displayName }} - } - - + +
diff --git a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts index d8de551..1a635a3 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts +++ b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts @@ -11,6 +11,7 @@ import { ActivatedRoute } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { DeleteModalComponent } from 'src/app/shared/delete_modal/delete-modal/delete-modal.component'; import { forkJoin } from 'rxjs'; +import { DataService as GroupsDataService } from '../../groups/services/data.service'; @Component({ selector: 'app-pxe-boot-files', @@ -33,14 +34,6 @@ export class PxeBootFilesComponent implements OnInit { ogLiveOptions: any[] = []; globalOgLive: string | null = null; unitSearchText: string = ''; - - getFilteredUnits(): any[] { - if (!this.unitSearchText) { - return this.units; - } - const searchText = this.unitSearchText.toLowerCase(); - return this.units.filter(unit => unit.displayName.toLowerCase().includes(searchText)); - } length: number = 0; itemsPerPage: number = 10; page: number = 0; @@ -56,7 +49,8 @@ export class PxeBootFilesComponent implements OnInit { private configService: ConfigService, private joyrideService: JoyrideService, private route: ActivatedRoute, - private dialog: MatDialog + private dialog: MatDialog, + private groupsDataService: GroupsDataService ) { this.baseUrl = this.configService.apiUrl; this.taskForm = this.fb.group({ @@ -111,25 +105,19 @@ export class PxeBootFilesComponent implements OnInit { .filter((unit: any) => unit.type === 'classroom') .map((unit: any) => ({ ...unit, - displayName: this.buildFullDisplayName(unit, rawUnits) + path: this.groupsDataService.getOrganizationalUnitPath(unit, rawUnits) })); - this.units.sort((a, b) => a.displayName.localeCompare(b.displayName)); + this.units.sort((a, b) => { + const pathA = a.path || a.name; + const pathB = b.path || b.name; + return pathA.localeCompare(pathB); + }); }, error => console.error(this.translate.instant('errorFetchingOrganizationalUnits1'), error) ); } - private buildFullDisplayName(unit: any, allUnits: any[]): string { - let hierarchy = unit.name; - let currentParent = unit.parent; - - while (currentParent) { - hierarchy = `${currentParent.name} / ${hierarchy}`; - const fullParent = allUnits.find((u: any) => u['@id'] === currentParent['@id']); - currentParent = fullParent ? fullParent.parent : null; - } - return hierarchy; - } + fetchPxeTemplates(): void { this.http.get(`${this.baseUrl}/pxe-templates?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }) diff --git a/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.html b/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.html index fc92271..957ca53 100644 --- a/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.html +++ b/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.html @@ -3,18 +3,13 @@

Añade clientes a {{data.subnetName}}

- - {{ 'selectClassroomPrompt' | translate }} - - - @for (unit of getFilteredUnits(); track unit.id) { - {{ unit.displayName }} - } - - + +
diff --git a/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.ts b/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.ts index 19d83fb..9d9423d 100644 --- a/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.ts +++ b/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.ts @@ -7,6 +7,7 @@ import { FormControl } from '@angular/forms'; import { ToastrService } from 'ngx-toastr'; import { OperationResultDialogComponent } from "../operation-result-dialog/operation-result-dialog.component"; import { ConfigService } from '@services/config.service'; +import { DataService as GroupsDataService } from "../../groups/services/data.service"; @Component({ selector: 'app-add-clients-to-subnet', @@ -23,15 +24,7 @@ export class AddClientsToSubnetComponent implements OnInit { unitControl = new FormControl(); childUnitControl = new FormControl(); allSelected: boolean = false; - unitSearchText: string = ''; - getFilteredUnits(): any[] { - if (!this.unitSearchText) { - return this.units; - } - const searchText = this.unitSearchText.toLowerCase(); - return this.units.filter(unit => unit.displayName.toLowerCase().includes(searchText)); - } constructor( private http: HttpClient, @@ -39,6 +32,7 @@ export class AddClientsToSubnetComponent implements OnInit { private toastService: ToastrService, public dialog: MatDialog, private configService: ConfigService, + private groupsDataService: GroupsDataService, @Inject(MAT_DIALOG_DATA) public data: { subnetUuid: string, subnetName: string } ) { this.baseUrl = this.configService.apiUrl; @@ -55,26 +49,20 @@ export class AddClientsToSubnetComponent implements OnInit { const rawUnits = response['hydra:member']; this.units = rawUnits.map((unit: any) => ({ ...unit, - displayName: this.buildFullDisplayName(unit, rawUnits) + name: unit.name, + path: this.groupsDataService.getOrganizationalUnitPath(unit, rawUnits) })); - this.units.sort((a, b) => a.displayName.localeCompare(b.displayName)); + this.units.sort((a, b) => { + const pathA = a.path || a.name; + const pathB = b.path || b.name; + return pathA.localeCompare(pathB); + }); this.loading = false; }, error => console.error(this.translate.instant('errorFetchingOrganizationalUnits1'), error) ); } - private buildFullDisplayName(unit: any, allUnits: any[]): string { - let hierarchy = unit.name; - let currentParent = unit.parent; - - while (currentParent) { - hierarchy = `${currentParent.name} / ${hierarchy}`; - const fullParent = allUnits.find((u: any) => u['@id'] === currentParent['@id']); - currentParent = fullParent ? fullParent.parent : null; - } - return hierarchy; - } loadChildUnits(event: any) { this.http.get(`${this.baseUrl}/clients?organizationalUnit.id=${event.value.id}&exists[subnet]=false`).subscribe( diff --git a/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.css b/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.css new file mode 100644 index 0000000..f8a4299 --- /dev/null +++ b/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.css @@ -0,0 +1,11 @@ +:host { + display: block; + width: 100%; +} +.full-width { + width: 100%; +} +.unit-name { + line-height: normal; + padding-top: 4px; +} diff --git a/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.html b/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.html new file mode 100644 index 0000000..12791ac --- /dev/null +++ b/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.html @@ -0,0 +1,21 @@ + + {{ label | translate }} + + + {{ getSelectedName() }} + + + @for (unit of filteredUnits; track (unit.id || unit['@id'])) { + +
{{ unit.name }}
+ @if (unit.path) { +
{{ unit.path }}
+ } +
+ } +
+
diff --git a/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.spec.ts b/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.spec.ts new file mode 100644 index 0000000..f3493e6 --- /dev/null +++ b/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OuSelectComponent } from './ou-select.component'; + +describe('OuSelectComponent', () => { + let component: OuSelectComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [OuSelectComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(OuSelectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.ts b/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.ts new file mode 100644 index 0000000..11b4e90 --- /dev/null +++ b/ogWebconsole/src/app/components/shared/ou-select/ou-select.component.ts @@ -0,0 +1,77 @@ +import { Component, Input, Output, EventEmitter, forwardRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; + +@Component({ + selector: 'app-ou-select', + templateUrl: './ou-select.component.html', + styleUrls: ['./ou-select.component.css'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => OuSelectComponent), + multi: true + } + ] +}) +export class OuSelectComponent implements ControlValueAccessor { + @Input() units: any[] = []; + @Input() multiple = false; + @Input() required = false; + @Input() disabled = false; + @Input() label: string = 'labelOrganizationalUnit'; + + @Output() selectionChange = new EventEmitter(); + + value: any = null; + searchText: string = ''; + + get filteredUnits() { + if (!this.searchText) { + return this.units; + } + const lower = this.searchText.toLowerCase(); + return this.units.filter(unit => + unit.name.toLowerCase().includes(lower) || + (unit.path && unit.path.toLowerCase().includes(lower)) + ); + } + + onOpened() { + this.searchText = ''; + } + + onChange = (val: any) => {}; + onTouched = () => {}; + + writeValue(val: any): void { + this.value = val; + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + setDisabledState?(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + onSelectionChange(event: any) { + this.value = event.value; + this.onChange(this.value); + this.selectionChange.emit(event); + this.onTouched(); + } + + getSelectedName(): string { + if (this.multiple) { + if (!this.value || !Array.isArray(this.value)) return ''; + return this.units.filter(u => this.value.includes(u.id || u['@id'])).map(u => u.name).join(', '); + } else { + return this.units.find(u => (u.id || u['@id']) === this.value)?.name || ''; + } + } +}