feat: standardise and refactor OU selection and migrate control flow
parent
bdfe4ac0df
commit
609304acc0
10
CHANGELOG.md
10
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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -23,16 +23,13 @@
|
|||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'labelOrganizationalUnit' | translate }}</mat-label>
|
||||
<mat-select multiple formControlName="organizationalUnits">
|
||||
@for (unit of organizationalUnits; track unit['@id']) {
|
||||
<mat-option [value]="unit['@id']">
|
||||
{{ unit.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<app-ou-select
|
||||
class="full-width"
|
||||
formControlName="organizationalUnits"
|
||||
[multiple]="true"
|
||||
[units]="organizationalUnits"
|
||||
[label]="'labelOrganizationalUnit'">
|
||||
</app-ou-select>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'cardView' | translate }}</mat-label>
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -3,116 +3,127 @@
|
|||
</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
@if (loading) {
|
||||
<mat-spinner class="loading-spinner"></mat-spinner>
|
||||
}
|
||||
|
||||
<div class="task-form" *ngIf="!loading">
|
||||
<mat-button-toggle-group *ngIf="data?.source === 'assistant'" [(ngModel)]="taskMode" class="task-mode-selection" name="taskMode" appearance="standard">
|
||||
<mat-button-toggle value="create">
|
||||
<mat-icon>add_task</mat-icon>
|
||||
{{ 'labelCreateTask' | translate }}
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle value="add">
|
||||
<mat-icon>playlist_add</mat-icon>
|
||||
{{ 'addToExistingTask' | translate }}
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
|
||||
<div *ngIf="taskMode === 'add'" class="select-task">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'selectTask' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedExistingTask" name="existingTask">
|
||||
<mat-option *ngFor="let task of existingTasks" [value]="task">{{ task.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'executionOrder' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="number"
|
||||
[(ngModel)]="executionOrder"
|
||||
name="executionOrder"
|
||||
min="1"
|
||||
[placeholder]="'enterOrder' | translate"
|
||||
>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<form *ngIf="taskMode === 'create' && taskForm && !loading" [formGroup]="taskForm" class="task-form">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'labelRoleName' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" [placeholder]="'nameLabel' | translate">
|
||||
<mat-error *ngIf="taskForm.get('name')?.invalid">{{ 'requiredFieldError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'labelNotes' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="notes" [placeholder]="'notesPlaceholder' | translate"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-checkbox *ngIf="!editing" formControlName="scheduleAfterCreate" class="schedule-checkbox">
|
||||
{{ 'scheduleAfterCreate' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
<mat-divider style="margin: 15px 0;"></mat-divider>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'labelScope' | translate }}</mat-label>
|
||||
<mat-select formControlName="scope" (selectionChange)="onScopeChange($event.value)"
|
||||
[disabled]="data?.clients && data.clients.length >= 1">
|
||||
<mat-option value="organizational-unit">{{ 'organizationalUnit' | translate }}</mat-option>
|
||||
<mat-option value="classrooms-group">{{ 'scopeClassroomsGroup' | translate }}</mat-option>
|
||||
<mat-option value="classroom">{{ 'classrooms' | translate }}</mat-option>
|
||||
<mat-option value="clients-group">{{ 'clientGroupsTitle' | translate }}</mat-option>
|
||||
<mat-option value="clients">{{ 'labelClients' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field *ngIf="taskForm.get('scope')?.value !== 'clients'" appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'labelOrganizationalUnit' | translate }}</mat-label>
|
||||
<mat-select formControlName="organizationalUnit">
|
||||
<mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
|
||||
<div class="unit-name">{{ unit.name }}</div>
|
||||
<div style="font-size: smaller; color: gray;">{{ unit.path }}</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div *ngIf="taskForm.get('scope')?.value === 'clients'" class="clients-selection">
|
||||
<h4>{{ 'selectedClients' | translate }}</h4>
|
||||
|
||||
<div *ngIf="data?.selectedClients && data.selectedClients.length > 0" class="pre-selected-info">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>{{ 'preSelectedInfo' | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div class="clients-list">
|
||||
<div class="clients-grid">
|
||||
<mat-card
|
||||
*ngFor="let client of clients"
|
||||
class="client-card"
|
||||
[class.pre-selected]="isClientPreSelected(client)"
|
||||
>
|
||||
<mat-card-content>
|
||||
<div class="client-info">
|
||||
<div class="client-name">{{ client.name || client.hostname }}</div>
|
||||
<div class="client-details">
|
||||
<span class="client-ip">{{ client.ip }}</span>
|
||||
<span class="client-status" [class]="'status-' + client.status">
|
||||
{{ client.status }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-icon class="selected-icon">
|
||||
check_circle
|
||||
</mat-icon>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
@if (!loading) {
|
||||
<div class="task-form">
|
||||
@if (data?.source === 'assistant') {
|
||||
<mat-button-toggle-group [(ngModel)]="taskMode" class="task-mode-selection" name="taskMode" appearance="standard">
|
||||
<mat-button-toggle value="create">
|
||||
<mat-icon>add_task</mat-icon>
|
||||
{{ 'labelCreateTask' | translate }}
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle value="add">
|
||||
<mat-icon>playlist_add</mat-icon>
|
||||
{{ 'addToExistingTask' | translate }}
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
@if (taskMode === 'add') {
|
||||
<div class="select-task">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'selectTask' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedExistingTask" name="existingTask">
|
||||
@for (task of existingTasks; track task) {
|
||||
<mat-option [value]="task">{{ task.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'executionOrder' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="number"
|
||||
[(ngModel)]="executionOrder"
|
||||
name="executionOrder"
|
||||
min="1"
|
||||
[placeholder]="'enterOrder' | translate"
|
||||
>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (taskMode === 'create' && taskForm && !loading) {
|
||||
<form [formGroup]="taskForm" class="task-form">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'labelRoleName' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" [placeholder]="'nameLabel' | translate">
|
||||
@if (taskForm.get('name')?.invalid) {
|
||||
<mat-error>{{ 'requiredFieldError' | translate }}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'labelNotes' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="notes" [placeholder]="'notesPlaceholder' | translate"></textarea>
|
||||
</mat-form-field>
|
||||
@if (!editing) {
|
||||
<mat-checkbox formControlName="scheduleAfterCreate" class="schedule-checkbox">
|
||||
{{ 'scheduleAfterCreate' | translate }}
|
||||
</mat-checkbox>
|
||||
}
|
||||
<mat-divider style="margin: 15px 0;"></mat-divider>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'labelScope' | translate }}</mat-label>
|
||||
<mat-select formControlName="scope" (selectionChange)="onScopeChange($event.value)"
|
||||
[disabled]="data?.clients && data.clients.length >= 1">
|
||||
<mat-option value="organizational-unit">{{ 'organizationalUnit' | translate }}</mat-option>
|
||||
<mat-option value="classrooms-group">{{ 'scopeClassroomsGroup' | translate }}</mat-option>
|
||||
<mat-option value="classroom">{{ 'classrooms' | translate }}</mat-option>
|
||||
<mat-option value="clients-group">{{ 'clientGroupsTitle' | translate }}</mat-option>
|
||||
<mat-option value="clients">{{ 'labelClients' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@if (taskForm.get('scope')?.value !== 'clients') {
|
||||
<app-ou-select
|
||||
class="full-width"
|
||||
formControlName="organizationalUnit"
|
||||
[units]="availableOrganizationalUnits"
|
||||
[label]="'labelOrganizationalUnit'">
|
||||
</app-ou-select>
|
||||
}
|
||||
@if (taskForm.get('scope')?.value === 'clients') {
|
||||
<div class="clients-selection">
|
||||
<h4>{{ 'selectedClients' | translate }}</h4>
|
||||
@if (data?.selectedClients && data.selectedClients.length > 0) {
|
||||
<div class="pre-selected-info">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>{{ 'preSelectedInfo' | translate }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="clients-list">
|
||||
<div class="clients-grid">
|
||||
@for (client of clients; track client) {
|
||||
<mat-card
|
||||
class="client-card"
|
||||
[class.pre-selected]="isClientPreSelected(client)"
|
||||
>
|
||||
<mat-card-content>
|
||||
<div class="client-info">
|
||||
<div class="client-name">{{ client.name || client.hostname }}</div>
|
||||
<div class="client-details">
|
||||
<span class="client-ip">{{ client.ip }}</span>
|
||||
<span class="client-status" [class]="'status-' + client.status">
|
||||
{{ client.status }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-icon class="selected-icon">
|
||||
check_circle
|
||||
</mat-icon>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
}
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions class="action-container">
|
||||
|
|
@ -120,7 +131,7 @@
|
|||
<button
|
||||
class="submit-button"
|
||||
(click)="taskMode === 'create' ? saveTask() : addToExistingTask()"
|
||||
>
|
||||
>
|
||||
{{ 'labelSaveButton' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
|||
|
|
@ -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<CreateTaskComponent>,
|
||||
@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<any>(`${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();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<h2 mat-dialog-title>{{ 'moveClientsTo' | translate }}</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'selectClassroomPrompt' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="newOU">
|
||||
<mat-option *ngFor="let unit of units" [value]="unit">{{ unit.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<app-ou-select
|
||||
class="full-width"
|
||||
[(ngModel)]="newOU"
|
||||
[units]="units"
|
||||
[label]="'selectClassroomPrompt'">
|
||||
</app-ou-select>
|
||||
</mat-dialog-content>
|
||||
|
||||
<div class="action-container">
|
||||
|
|
|
|||
|
|
@ -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<any>(`${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<any>(`${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'));
|
||||
|
|
|
|||
|
|
@ -3,57 +3,59 @@
|
|||
<div class="inputs-container">
|
||||
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
||||
|
||||
<form class="client-form grid-form" *ngIf="!loading">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelOrganizationalUnit' | translate }}</mat-label>
|
||||
<mat-select (selectionChange)="setOrganizationalUnit($event)" [value]="organizationalUnit">
|
||||
<mat-select-trigger>
|
||||
{{ getSelectedParentName() }}
|
||||
</mat-select-trigger>
|
||||
<mat-option *ngFor="let unit of parentUnitsWithPaths" [value]="unit.id">
|
||||
<div class="unit-name">{{ unit.name }}</div>
|
||||
<div style="font-size: smaller; color: gray;">{{ unit.path }}</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
@if (!loading) {
|
||||
<form class="client-form grid-form">
|
||||
<app-ou-select
|
||||
class="form-field"
|
||||
[ngModel]="organizationalUnit"
|
||||
[units]="parentUnitsWithPaths"
|
||||
[label]="'labelOrganizationalUnit'"
|
||||
(selectionChange)="setOrganizationalUnit($event)">
|
||||
</app-ou-select>
|
||||
</form>
|
||||
}
|
||||
|
||||
<div>
|
||||
<div class="upload-container">
|
||||
<button class="action-button" (click)="fileInput.click()">{{ 'buttonUploadFile' | translate }}</button>
|
||||
<input #fileInput type="file" (change)="onFileUpload($event)" accept="*" hidden>
|
||||
<p>{{ 'orAddThemManually' | translate }}</p>
|
||||
<div *ngIf="showTextarea">
|
||||
<textarea #textarea matInput
|
||||
[placeholder]="'dhcpHostExample' | translate"
|
||||
@if (showTextarea) {
|
||||
<div>
|
||||
<textarea #textarea matInput
|
||||
[placeholder]="'dhcpHostExample' | translate"
|
||||
rows="20" cols="100"></textarea>
|
||||
<button class="action-button" style="margin-top: 0.5em;"
|
||||
<button class="action-button" style="margin-top: 0.5em;"
|
||||
(click)="onTextarea(textarea.value)">{{ 'buttonPreview' | translate }}</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (uploadedClients.length > 0) {
|
||||
<h4>{{ 'importedClientsZero' | translate }}</h4>
|
||||
}
|
||||
@if (uploadedClients.length > 0) {
|
||||
<div class="scrollable-table">
|
||||
@if (uploadedClients.length > 0) {
|
||||
<table mat-table [dataSource]="uploadedClients" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'labelRoleName' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.name }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="ip">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'labelIpCaps' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.ip }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="mac">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'labelMac' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.mac }} </td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 *ngIf="uploadedClients.length > 0">{{ 'importedClientsZero' | translate }}</h4>
|
||||
<div *ngIf="uploadedClients.length > 0" class="scrollable-table">
|
||||
<table mat-table [dataSource]="uploadedClients" class="mat-elevation-z8" *ngIf="uploadedClients.length > 0">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'labelRoleName' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.name }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="ip">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'labelIpCaps' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.ip }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="mac">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'labelMac' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.mac }} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,175 +1,155 @@
|
|||
<mat-dialog-content class="create-client-container">
|
||||
<h1 mat-dialog-title>{{ dialogTitle | translate }}</h1>
|
||||
<div [ngClass]="{'loading': loading}">
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
<form *ngIf="clientForm && !loading" [formGroup]="clientForm" class="client-form grid-form">
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelOrganizationalUnit' | translate }}</mat-label>
|
||||
<mat-select formControlName="organizationalUnit" (selectionChange)="onParentChange($event)">
|
||||
<mat-select-trigger>
|
||||
{{ getSelectedParentName() }}
|
||||
</mat-select-trigger>
|
||||
@for (unit of parentUnitsWithPaths; track unit.id) {
|
||||
<mat-option [value]="unit.id">
|
||||
<div class="unit-name">{{ unit.name }}</div>
|
||||
<div style="font-size: smaller; color: gray;">{{ unit.path }}</div>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelName' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelSerialNumber' | translate }}</mat-label>
|
||||
<input matInput formControlName="serialNumber">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelNetiface' | translate }}</mat-label>
|
||||
<mat-select formControlName="netiface">
|
||||
@for (type of netifaceTypes; track type.value) {
|
||||
<mat-option [value]="type.value">{{ type.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelMac' | translate }}</mat-label>
|
||||
<input matInput formControlName="mac" required>
|
||||
<mat-error>{{ 'macError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelIp' | translate }}</mat-label>
|
||||
<input matInput formControlName="ip" required>
|
||||
<mat-error>{{ 'ipError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelRepository' | translate }}</mat-label>
|
||||
<mat-select formControlName="repository">
|
||||
@for (repository of repositories; track repository['@id']) {
|
||||
<mat-option [value]="repository['@id']">{{ repository.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-checkbox formControlName="maintenance">
|
||||
{{ 'maintenance' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
<!-- ===== Agrupación: Configuración del menú ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'menuConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-2">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelMenu' | translate }}</mat-label>
|
||||
<mat-select formControlName="menu">
|
||||
@for (menu of menus; track menu['@id']) {
|
||||
<mat-option [value]="menu['@id']">{{ menu.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>{{ 'menuError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelResolution' | translate }}</mat-label>
|
||||
<mat-select formControlName="resolution" required>
|
||||
<mat-option value="769">{{ 'resolution640x480x8' | translate }}</mat-option>
|
||||
<mat-option value="771">{{ 'resolution800x600x8' | translate }}</mat-option>
|
||||
<mat-option value="773">{{ 'resolution1024x768x8' | translate }}</mat-option>
|
||||
<mat-option value="775">{{ 'resolution1280x1024x8' | translate }}</mat-option>
|
||||
<mat-option value="784">{{ 'resolution640x480x16' | translate }}</mat-option>
|
||||
<mat-option value="787">{{ 'resolution800x600x16' | translate }}</mat-option>
|
||||
<mat-option value="791">{{ 'resolution1024x768x16' | translate }}</mat-option>
|
||||
<mat-option value="794">{{ 'resolution1280x1024x16' | translate }}</mat-option>
|
||||
<mat-option value="785">{{ 'resolution640x480x24' | translate }}</mat-option>
|
||||
<mat-option value="788">{{ 'resolution800x600x24' | translate }}</mat-option>
|
||||
<mat-option value="792">{{ 'resolution1024x768x24' | translate }}</mat-option>
|
||||
<mat-option value="795">{{ 'resolution1280x1024x24' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>{{ 'resolutionError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- ===== Agrupación: Configuración de arranque ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'bootConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-3">
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'bootServer' | translate }}</mat-label>
|
||||
<mat-select formControlName="bootServer" (selectionChange)="onBootServerChange($event)">
|
||||
@for (server of bootServers; track server['@id']) {
|
||||
<mat-option [value]="server['@id']">{{ server.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelOgLive' | translate }}</mat-label>
|
||||
<mat-select formControlName="ogLive">
|
||||
@for (ogLive of ogLives; track ogLive['@id']) {
|
||||
<mat-option [value]="ogLive['@id']">{{ ogLive.name }}</mat-option>
|
||||
}
|
||||
@if (ogLives.length === 0 && clientForm.get('bootServer')?.value) {
|
||||
<mat-option disabled>{{ 'noOgLivesInServer' | translate }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelTemplate' | translate }}</mat-label>
|
||||
<mat-select formControlName="pxeTemplate">
|
||||
@for (template of templates; track template['@id']) {
|
||||
<mat-option [value]="template['@id']">{{ template.name }}</mat-option>
|
||||
}
|
||||
@if (templates.length === 0 && clientForm.get('bootServer')?.value) {
|
||||
<mat-option disabled>{{ 'noTemplatesInServer' | translate }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@if (isEditMode) {
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'labelHardwareProfile' | translate }}</legend>
|
||||
<div class="hardware-profile-box">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-select formControlName="hardwareProfile">
|
||||
<mat-option [value]="null">{{ 'noHardwareProfileAssigned' | translate }}</mat-option>
|
||||
@for (profile of hardwareProfiles; track profile['@id']) {
|
||||
<mat-option [value]="profile['@id']">{{ profile.description }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button type="button" mat-icon-button class="hardware-profile-link" (click)="goToHardwareProfiles()"
|
||||
[matTooltip]="'manageHardwareProfiles' | translate">
|
||||
<mat-icon>settings</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
<h1 mat-dialog-title>{{ dialogTitle | translate }}</h1>
|
||||
<div [ngClass]="{'loading': loading}">
|
||||
@if (loading) {
|
||||
<mat-spinner class="loading-spinner"></mat-spinner>
|
||||
}
|
||||
@if (clientForm && !loading) {
|
||||
<form [formGroup]="clientForm" class="client-form grid-form">
|
||||
<app-ou-select
|
||||
class="form-field"
|
||||
formControlName="organizationalUnit"
|
||||
[units]="parentUnitsWithPaths"
|
||||
[label]="'labelOrganizationalUnit'"
|
||||
(selectionChange)="onParentChange($event)">
|
||||
</app-ou-select>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelName' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelSerialNumber' | translate }}</mat-label>
|
||||
<input matInput formControlName="serialNumber">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelNetiface' | translate }}</mat-label>
|
||||
<mat-select formControlName="netiface">
|
||||
@for (type of netifaceTypes; track type.value) {
|
||||
<mat-option [value]="type.value">{{ type.name }}</mat-option>
|
||||
}
|
||||
|
||||
<mat-form-field class="form-field full-width">
|
||||
<mat-label>{{ 'labelNotes' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="notes" rows="4"
|
||||
[placeholder]="'notesPlaceholder' | translate"></textarea>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelMac' | translate }}</mat-label>
|
||||
<input matInput formControlName="mac" required>
|
||||
<mat-error>{{ 'macError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelIp' | translate }}</mat-label>
|
||||
<input matInput formControlName="ip" required>
|
||||
<mat-error>{{ 'ipError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelRepository' | translate }}</mat-label>
|
||||
<mat-select formControlName="repository">
|
||||
@for (repository of repositories; track repository['@id']) {
|
||||
<mat-option [value]="repository['@id']">{{ repository.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-checkbox formControlName="maintenance">
|
||||
{{ 'maintenance' | translate }}
|
||||
</mat-checkbox>
|
||||
<!-- ===== Agrupación: Configuración del menú ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'menuConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-2">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelMenu' | translate }}</mat-label>
|
||||
<mat-select formControlName="menu">
|
||||
@for (menu of menus; track menu['@id']) {
|
||||
<mat-option [value]="menu['@id']">{{ menu.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>{{ 'menuError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelResolution' | translate }}</mat-label>
|
||||
<mat-select formControlName="resolution" required>
|
||||
<mat-option value="769">{{ 'resolution640x480x8' | translate }}</mat-option>
|
||||
<mat-option value="771">{{ 'resolution800x600x8' | translate }}</mat-option>
|
||||
<mat-option value="773">{{ 'resolution1024x768x8' | translate }}</mat-option>
|
||||
<mat-option value="775">{{ 'resolution1280x1024x8' | translate }}</mat-option>
|
||||
<mat-option value="784">{{ 'resolution640x480x16' | translate }}</mat-option>
|
||||
<mat-option value="787">{{ 'resolution800x600x16' | translate }}</mat-option>
|
||||
<mat-option value="791">{{ 'resolution1024x768x16' | translate }}</mat-option>
|
||||
<mat-option value="794">{{ 'resolution1280x1024x16' | translate }}</mat-option>
|
||||
<mat-option value="785">{{ 'resolution640x480x24' | translate }}</mat-option>
|
||||
<mat-option value="788">{{ 'resolution800x600x24' | translate }}</mat-option>
|
||||
<mat-option value="792">{{ 'resolution1024x768x24' | translate }}</mat-option>
|
||||
<mat-option value="795">{{ 'resolution1280x1024x24' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>{{ 'resolutionError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</fieldset>
|
||||
<!-- ===== Agrupación: Configuración de arranque ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'bootConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-3">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'bootServer' | translate }}</mat-label>
|
||||
<mat-select formControlName="bootServer" (selectionChange)="onBootServerChange($event)">
|
||||
@for (server of bootServers; track server['@id']) {
|
||||
<mat-option [value]="server['@id']">{{ server.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelOgLive' | translate }}</mat-label>
|
||||
<mat-select formControlName="ogLive">
|
||||
@for (ogLive of ogLives; track ogLive['@id']) {
|
||||
<mat-option [value]="ogLive['@id']">{{ ogLive.name }}</mat-option>
|
||||
}
|
||||
@if (ogLives.length === 0 && clientForm.get('bootServer')?.value) {
|
||||
<mat-option disabled>{{ 'noOgLivesInServer' | translate }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelTemplate' | translate }}</mat-label>
|
||||
<mat-select formControlName="pxeTemplate">
|
||||
@for (template of templates; track template['@id']) {
|
||||
<mat-option [value]="template['@id']">{{ template.name }}</mat-option>
|
||||
}
|
||||
@if (templates.length === 0 && clientForm.get('bootServer')?.value) {
|
||||
<mat-option disabled>{{ 'noTemplatesInServer' | translate }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</fieldset>
|
||||
@if (isEditMode) {
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'labelHardwareProfile' | translate }}</legend>
|
||||
<div class="hardware-profile-box">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-select formControlName="hardwareProfile">
|
||||
<mat-option [value]="null">{{ 'noHardwareProfileAssigned' | translate }}</mat-option>
|
||||
@for (profile of hardwareProfiles; track profile['@id']) {
|
||||
<mat-option [value]="profile['@id']">{{ profile.description }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button type="button" mat-icon-button class="hardware-profile-link" (click)="goToHardwareProfiles()"
|
||||
[matTooltip]="'manageHardwareProfiles' | translate">
|
||||
<mat-icon>settings</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
}
|
||||
<mat-form-field class="form-field full-width">
|
||||
<mat-label>{{ 'labelNotes' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="notes" rows="4"
|
||||
[placeholder]="'notesPlaceholder' | translate"></textarea>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" [disabled]="!clientForm.valid" (click)="onSubmit()">
|
||||
{{ isEditMode ? 'Guardar' : 'Crear' }}
|
||||
</button>
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" [disabled]="!clientForm.valid" (click)="onSubmit()">
|
||||
{{ isEditMode ? 'Guardar' : 'Crear' }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
@ -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<void> {
|
||||
// Extract UUID from IRI (e.g. "/boot-servers/xxxx-xxx" -> "xxxx-xxx")
|
||||
|
|
|
|||
|
|
@ -1,261 +1,279 @@
|
|||
<mat-dialog-content class="create-ou-container">
|
||||
<h1 mat-dialog-title>{{ isEditMode ? ('edit' | translate) : ('createButton' | translate) }} {{
|
||||
'labelOrganizationalUnit' | translate }}</h1>
|
||||
<div [ngClass]="{'loading': loading}">
|
||||
<!-- Paso 1: General -->
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
<span *ngIf="!loading" class="step-title">{{ 'labelGeneralTab' | translate }}</span>
|
||||
<form *ngIf="generalFormGroup && !loading" [formGroup]="generalFormGroup" class="grid-form">
|
||||
<mat-form-field class="form-field" appearance="fill">
|
||||
<mat-label>{{ 'labelType' | translate }}</mat-label>
|
||||
<mat-select formControlName="type" required>
|
||||
<mat-option *ngFor="let type of filteredTypes" [value]="type">
|
||||
{{ typeTranslations[type] }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" appearance="fill">
|
||||
<mat-label>{{ 'labelRoleName' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" appearance="fill">
|
||||
<mat-label>{{ 'labelCreateOrgUnitparent' | translate }}</mat-label>
|
||||
<mat-select formControlName="parent" (selectionChange)="onParentChange($event)">
|
||||
<mat-select-trigger>
|
||||
{{ getSelectedParentName() }}
|
||||
</mat-select-trigger>
|
||||
<mat-option *ngFor="let unit of parentUnitsWithPaths" [value]="unit.id">
|
||||
<div>{{ unit.name }}</div>
|
||||
<div style="font-size: smaller; color: gray;">{{ unit.path }}</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<h1 mat-dialog-title>{{ isEditMode ? ('edit' | translate) : ('createButton' | translate) }} {{
|
||||
'labelOrganizationalUnit' | translate }}</h1>
|
||||
<div [ngClass]="{'loading': loading}">
|
||||
<!-- Paso 1: General -->
|
||||
@if (loading) {
|
||||
<mat-spinner class="loading-spinner"></mat-spinner>
|
||||
}
|
||||
@if (!loading) {
|
||||
<span class="step-title">{{ 'labelGeneralTab' | translate }}</span>
|
||||
}
|
||||
@if (generalFormGroup && !loading) {
|
||||
<form [formGroup]="generalFormGroup" class="grid-form">
|
||||
<mat-form-field class="form-field" appearance="fill">
|
||||
<mat-label>{{ 'labelType' | translate }}</mat-label>
|
||||
<mat-select formControlName="type" required>
|
||||
@for (type of filteredTypes; track type) {
|
||||
<mat-option [value]="type">
|
||||
{{ typeTranslations[type] }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" appearance="fill">
|
||||
<mat-label>{{ 'labelRoleName' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
<app-ou-select
|
||||
class="form-field"
|
||||
formControlName="parent"
|
||||
[units]="parentUnitsWithPaths"
|
||||
[label]="'labelCreateOrgUnitparent'"
|
||||
(selectionChange)="onParentChange($event)">
|
||||
</app-ou-select>
|
||||
<mat-form-field class="form-field description-form-field" appearance="fill">
|
||||
<mat-label>{{ 'labelDescription' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="description"></textarea>
|
||||
</mat-form-field>
|
||||
<mat-checkbox formControlName="excludeParentChanges">
|
||||
{{ 'excludeParentChanges' | translate }}
|
||||
</mat-checkbox>
|
||||
</form>
|
||||
}
|
||||
|
||||
<mat-form-field class="form-field description-form-field" appearance="fill">
|
||||
<mat-label>{{ 'labelDescription' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="description"></textarea>
|
||||
</mat-form-field>
|
||||
<!-- Paso 2: Información del Aula -->
|
||||
@if (generalFormGroup.value.type === 'classroom' && !loading) {
|
||||
<span class="step-title">{{ 'labelClassroomInfoStep' | translate }}</span>
|
||||
<form class="grid-form" [formGroup]="classroomInfoFormGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelLocation' | translate }}</mat-label>
|
||||
<input matInput formControlName="location">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelCapacity' | translate }}</mat-label>
|
||||
<input matInput formControlName="capacity" type="number" min="0">
|
||||
@if (classroomInfoFormGroup.get('capacity')?.hasError('min')) {
|
||||
<mat-error>
|
||||
{{ 'capacityWarning' | translate }}
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" appearance="fill" style="grid-column: span 1;">
|
||||
<mat-label>{{ 'labelAssociatedCalendar' | translate }}</mat-label>
|
||||
<mat-select formControlName="remoteCalendar" (selectionChange)="onCalendarChange($event)">
|
||||
@for (calendar of calendars; track calendar) {
|
||||
<mat-option [value]="calendar['@id']">
|
||||
{{ calendar.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div class="projector-board-field">
|
||||
<mat-slide-toggle formControlName="projector">{{ 'projectorAlt' | translate }}</mat-slide-toggle>
|
||||
<mat-slide-toggle formControlName="board">{{ 'boardToggle' | translate }}</mat-slide-toggle>
|
||||
<mat-slide-toggle formControlName="remotePc">{{ 'remotePcLabel' | translate }}</mat-slide-toggle>
|
||||
@if (classroomInfoFormGroup.get('remotePc')?.value) {
|
||||
<mat-slide-toggle formControlName="remotePcRebootOnUnreserve">{{ 'remotePcRebootOnUnreserveLabel' | translate }}</mat-slide-toggle>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
<mat-checkbox formControlName="excludeParentChanges">
|
||||
{{ 'excludeParentChanges' | translate }}
|
||||
</mat-checkbox>
|
||||
</form>
|
||||
|
||||
<!-- Paso 2: Información del Aula -->
|
||||
@if (generalFormGroup.value.type === 'classroom' && !loading) {
|
||||
<span class="step-title">{{ 'labelClassroomInfoStep' | translate }}</span>
|
||||
<form class="grid-form" [formGroup]="classroomInfoFormGroup">
|
||||
<!-- Paso 3: Configuración de Red -->
|
||||
@if (!loading) {
|
||||
<span class="step-title">{{ 'labelNetworkSettingsStep' | translate }}</span>
|
||||
}
|
||||
@if (networkSettingsFormGroup && !loading) {
|
||||
<form [formGroup]="networkSettingsFormGroup" class="grid-form">
|
||||
<!-- ===== Agrupación: Configuración de arranque ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'bootConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-3">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelLocation' | translate }}</mat-label>
|
||||
<input matInput formControlName="location">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelCapacity' | translate }}</mat-label>
|
||||
<input matInput formControlName="capacity" type="number" min="0">
|
||||
<mat-error *ngIf="classroomInfoFormGroup.get('capacity')?.hasError('min')">
|
||||
{{ 'capacityWarning' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" appearance="fill" style="grid-column: span 1;">
|
||||
<mat-label>{{ 'labelAssociatedCalendar' | translate }}</mat-label>
|
||||
<mat-select formControlName="remoteCalendar" (selectionChange)="onCalendarChange($event)">
|
||||
<mat-option *ngFor="let calendar of calendars" [value]="calendar['@id']">
|
||||
{{ calendar.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div class="projector-board-field">
|
||||
<mat-slide-toggle formControlName="projector">{{ 'projectorAlt' | translate }}</mat-slide-toggle>
|
||||
<mat-slide-toggle formControlName="board">{{ 'boardToggle' | translate }}</mat-slide-toggle>
|
||||
<mat-slide-toggle formControlName="remotePc">{{ 'remotePcLabel' | translate }}</mat-slide-toggle>
|
||||
@if (classroomInfoFormGroup.get('remotePc')?.value) {
|
||||
<mat-slide-toggle formControlName="remotePcRebootOnUnreserve">{{ 'remotePcRebootOnUnreserveLabel' | translate }}</mat-slide-toggle>
|
||||
<mat-label>{{ 'bootServer' | translate }}</mat-label>
|
||||
<mat-select formControlName="bootServer" (selectionChange)="onBootServerChange($event)">
|
||||
@for (server of bootServers; track server['@id']) {
|
||||
<mat-option [value]="server['@id']">{{ server.name }}</mat-option>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
<!-- Paso 3: Configuración de Red -->
|
||||
<span *ngIf="!loading" class="step-title">{{ 'labelNetworkSettingsStep' | translate }}</span>
|
||||
<form *ngIf="networkSettingsFormGroup && !loading" [formGroup]="networkSettingsFormGroup" class="grid-form">
|
||||
|
||||
<!-- ===== Agrupación: Configuración de arranque ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'bootConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-3">
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'bootServer' | translate }}</mat-label>
|
||||
<mat-select formControlName="bootServer" (selectionChange)="onBootServerChange($event)">
|
||||
@for (server of bootServers; track server['@id']) {
|
||||
<mat-option [value]="server['@id']">{{ server.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'ogLive' | translate }}</mat-label>
|
||||
<mat-select formControlName="ogLive" (selectionChange)="onOgLiveChange($event)">
|
||||
@for (oglive of ogLives; track oglive['@id']) {
|
||||
<mat-option [value]="oglive['@id']">{{ oglive.name }}</mat-option>
|
||||
}
|
||||
@if (ogLives.length === 0 && networkSettingsFormGroup.get('bootServer')?.value) {
|
||||
<mat-option disabled>{{ 'noOgLivesInServer' | translate }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelTemplate' | translate }}</mat-label>
|
||||
<mat-select formControlName="pxeTemplate" (selectionChange)="onPxeTemplateChange($event)">
|
||||
@for (template of pxeTemplates; track template['@id']) {
|
||||
<mat-option [value]="template['@id']">{{ template.name }}</mat-option>
|
||||
}
|
||||
@if (pxeTemplates.length === 0 && networkSettingsFormGroup.get('bootServer')?.value) {
|
||||
<mat-option disabled>{{ 'noTemplatesInServer' | translate }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelRepository' | translate }}</mat-label>
|
||||
<mat-select formControlName="repository" (selectionChange)="onRepositoryChange($event)">
|
||||
<mat-option *ngFor="let repository of repositories" [value]="repository['@id']">
|
||||
{{ repository.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelProxy' | translate }}</mat-label>
|
||||
<input matInput formControlName="proxy">
|
||||
<mat-label>{{ 'ogLive' | translate }}</mat-label>
|
||||
<mat-select formControlName="ogLive" (selectionChange)="onOgLiveChange($event)">
|
||||
@for (oglive of ogLives; track oglive['@id']) {
|
||||
<mat-option [value]="oglive['@id']">{{ oglive.name }}</mat-option>
|
||||
}
|
||||
@if (ogLives.length === 0 && networkSettingsFormGroup.get('bootServer')?.value) {
|
||||
<mat-option disabled>{{ 'noOgLivesInServer' | translate }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelDns' | translate }}</mat-label>
|
||||
<input matInput formControlName="dns">
|
||||
<mat-label>{{ 'labelTemplate' | translate }}</mat-label>
|
||||
<mat-select formControlName="pxeTemplate" (selectionChange)="onPxeTemplateChange($event)">
|
||||
@for (template of pxeTemplates; track template['@id']) {
|
||||
<mat-option [value]="template['@id']">{{ template.name }}</mat-option>
|
||||
}
|
||||
@if (pxeTemplates.length === 0 && networkSettingsFormGroup.get('bootServer')?.value) {
|
||||
<mat-option disabled>{{ 'noTemplatesInServer' | translate }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</fieldset>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelRepository' | translate }}</mat-label>
|
||||
<mat-select formControlName="repository" (selectionChange)="onRepositoryChange($event)">
|
||||
@for (repository of repositories; track repository) {
|
||||
<mat-option [value]="repository['@id']">
|
||||
{{ repository.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelProxy' | translate }}</mat-label>
|
||||
<input matInput formControlName="proxy">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelDns' | translate }}</mat-label>
|
||||
<input matInput formControlName="dns">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelNetmask' | translate }}</mat-label>
|
||||
<input matInput formControlName="netmask">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label i18n="@@netiface-label">{{ 'labelNetiface' | translate }}</mat-label>
|
||||
<mat-select formControlName="netiface">
|
||||
@for (type of netifaceTypes; track type) {
|
||||
<mat-option [value]="type.value">
|
||||
{{ type.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelRouter' | translate }}</mat-label>
|
||||
<input matInput formControlName="router">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelNtp' | translate }}</mat-label>
|
||||
<input matInput formControlName="ntp">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelHardwareProfile' | translate }}</mat-label>
|
||||
<mat-select formControlName="hardwareProfile">
|
||||
@for (unit of hardwareProfiles; track unit) {
|
||||
<mat-option [value]="unit['@id']">{{ unit.description
|
||||
}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<!-- ===== Agrupación: Configuración de multicast ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'multicastConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-2">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelMcastIp' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastIp">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelNetmask' | translate }}</mat-label>
|
||||
<input matInput formControlName="netmask">
|
||||
<mat-label>{{ 'labelMcastSpeed' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastSpeed" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label i18n="@@netiface-label">{{ 'labelNetiface' | translate }}</mat-label>
|
||||
<mat-select formControlName="netiface">
|
||||
<mat-option *ngFor="let type of netifaceTypes" [value]="type.value">
|
||||
{{ type.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-label>{{ 'labelMcastPort' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastPort" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelRouter' | translate }}</mat-label>
|
||||
<input matInput formControlName="router">
|
||||
<mat-label>{{ 'labelMcastMode' | translate }}</mat-label>
|
||||
<mat-select formControlName="mcastMode">
|
||||
@for (option of multicastModeOptions; track option) {
|
||||
<mat-option [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</fieldset>
|
||||
<!-- ===== Agrupación: Configuración de p2p ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'p2pConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-2">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelP2pMode' | translate }}</mat-label>
|
||||
<mat-select formControlName="p2pMode">
|
||||
@for (option of p2pModeOptions; track option) {
|
||||
<mat-option [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@if (networkSettingsFormGroup.get('p2pMode')?.value === 'seeder') {
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelP2pTime' | translate }} (minutos)</mat-label>
|
||||
<input matInput formControlName="p2pTime" type="number">
|
||||
</mat-form-field>
|
||||
}
|
||||
</div>
|
||||
</fieldset>
|
||||
<!-- ===== Agrupación: Configuración del menú ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'menuConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-2">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelMenu' | translate }}</mat-label>
|
||||
<mat-select formControlName="menu" (selectionChange)="onMenuChange($event)">
|
||||
@for (menu of menus; track menu) {
|
||||
<mat-option [value]="menu['@id']">
|
||||
{{ menu.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelNtp' | translate }}</mat-label>
|
||||
<input matInput formControlName="ntp">
|
||||
<mat-label>{{ 'labelResolution' | translate }}</mat-label>
|
||||
<mat-select formControlName="resolution">
|
||||
<mat-option value="769">{{ 'resolution640x480x8' | translate }}</mat-option>
|
||||
<mat-option value="771">{{ 'resolution800x600x8' | translate }}</mat-option>
|
||||
<mat-option value="773">{{ 'resolution1024x768x8' | translate }}</mat-option>
|
||||
<mat-option value="775">{{ 'resolution1280x1024x8' | translate }}</mat-option>
|
||||
<mat-option value="784">{{ 'resolution640x480x16' | translate }}</mat-option>
|
||||
<mat-option value="787">{{ 'resolution800x600x16' | translate }}</mat-option>
|
||||
<mat-option value="791">{{ 'resolution1024x768x16' | translate }}</mat-option>
|
||||
<mat-option value="794">{{ 'resolution1280x1024x16' | translate }}</mat-option>
|
||||
<mat-option value="785">{{ 'resolution640x480x24' | translate }}</mat-option>
|
||||
<mat-option value="788">{{ 'resolution800x600x24' | translate }}</mat-option>
|
||||
<mat-option value="792">{{ 'resolution1024x768x24' | translate }}</mat-option>
|
||||
<mat-option value="795">{{ 'resolution1280x1024x24' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelHardwareProfile' | translate }}</mat-label>
|
||||
<mat-select formControlName="hardwareProfile">
|
||||
<mat-option *ngFor="let unit of hardwareProfiles" [value]="unit['@id']">{{ unit.description
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<!-- ===== Agrupación: Configuración de multicast ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'multicastConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-2">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelMcastIp' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastIp">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelMcastSpeed' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastSpeed" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelMcastPort' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastPort" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelMcastMode' | translate }}</mat-label>
|
||||
<mat-select formControlName="mcastMode">
|
||||
<mat-option *ngFor="let option of multicastModeOptions" [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
}
|
||||
|
||||
<!-- ===== Agrupación: Configuración de p2p ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'p2pConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-2">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelP2pMode' | translate }}</mat-label>
|
||||
<mat-select formControlName="p2pMode">
|
||||
<mat-option *ngFor="let option of p2pModeOptions" [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" *ngIf="networkSettingsFormGroup.get('p2pMode')?.value === 'seeder'">
|
||||
<mat-label>{{ 'labelP2pTime' | translate }} (minutos)</mat-label>
|
||||
<input matInput formControlName="p2pTime" type="number">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- ===== Agrupación: Configuración del menú ===== -->
|
||||
<fieldset class="fieldset-group">
|
||||
<legend class="fieldset-legend">{{ 'menuConfiguration' | translate }}</legend>
|
||||
<div class="fieldset-grid-2">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelMenu' | translate }}</mat-label>
|
||||
<mat-select formControlName="menu" (selectionChange)="onMenuChange($event)">
|
||||
<mat-option *ngFor="let menu of menus" [value]="menu['@id']">
|
||||
{{ menu.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'labelResolution' | translate }}</mat-label>
|
||||
<mat-select formControlName="resolution">
|
||||
<mat-option value="769">{{ 'resolution640x480x8' | translate }}</mat-option>
|
||||
<mat-option value="771">{{ 'resolution800x600x8' | translate }}</mat-option>
|
||||
<mat-option value="773">{{ 'resolution1024x768x8' | translate }}</mat-option>
|
||||
<mat-option value="775">{{ 'resolution1280x1024x8' | translate }}</mat-option>
|
||||
<mat-option value="784">{{ 'resolution640x480x16' | translate }}</mat-option>
|
||||
<mat-option value="787">{{ 'resolution800x600x16' | translate }}</mat-option>
|
||||
<mat-option value="791">{{ 'resolution1024x768x16' | translate }}</mat-option>
|
||||
<mat-option value="794">{{ 'resolution1280x1024x16' | translate }}</mat-option>
|
||||
<mat-option value="785">{{ 'resolution640x480x24' | translate }}</mat-option>
|
||||
<mat-option value="788">{{ 'resolution800x600x24' | translate }}</mat-option>
|
||||
<mat-option value="792">{{ 'resolution1024x768x24' | translate }}</mat-option>
|
||||
<mat-option value="795">{{ 'resolution1280x1024x24' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<!-- Paso 4: Información Adicional -->
|
||||
<span *ngIf="!loading" class="step-title">{{ 'labelAdditionalInfoStep' | translate }}</span>
|
||||
<form *ngIf="additionalInfoFormGroup && !loading" [formGroup]="additionalInfoFormGroup">
|
||||
<mat-form-field class="form-field description-form-field">
|
||||
<mat-label>{{ 'labelComments' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="comments"></textarea>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Paso 4: Información Adicional -->
|
||||
@if (!loading) {
|
||||
<span class="step-title">{{ 'labelAdditionalInfoStep' | translate }}</span>
|
||||
}
|
||||
@if (additionalInfoFormGroup && !loading) {
|
||||
<form [formGroup]="additionalInfoFormGroup">
|
||||
<mat-form-field class="form-field description-form-field">
|
||||
<mat-label>{{ 'labelComments' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="comments"></textarea>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSubmit()"
|
||||
[disabled]="!generalFormGroup.valid || !additionalInfoFormGroup.valid || !networkSettingsFormGroup.valid">{{
|
||||
isEditMode ? ('buttonEdit' | translate) : ('createButton' | translate) }}</button>
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSubmit()"
|
||||
[disabled]="!generalFormGroup.valid || !additionalInfoFormGroup.valid || !networkSettingsFormGroup.valid">{{
|
||||
isEditMode ? ('buttonEdit' | translate) : ('createButton' | translate) }}</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
@ -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<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
|
|||
|
|
@ -25,19 +25,14 @@
|
|||
</mat-form-field>
|
||||
}
|
||||
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="selectClassStep"
|
||||
text="{{ 'selectClassDescription' | translate }}">
|
||||
<mat-label>{{ 'labelSelectClass' | translate }}</mat-label>
|
||||
<mat-select (selectionChange)="loadChildUnits($event)" (opened)="unitSearchText = ''">
|
||||
<div class="dropdown-search-wrapper">
|
||||
<input type="text" class="dropdown-search-input" [(ngModel)]="unitSearchText" [ngModelOptions]="{standalone: true}" (keydown)="$event.stopPropagation()"
|
||||
placeholder="{{ 'labelSearch' | translate }}">
|
||||
</div>
|
||||
@for (unit of getFilteredUnits(); track unit.id) {
|
||||
<mat-option [value]="unit">{{ unit.displayName }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<app-ou-select
|
||||
class="search-boolean"
|
||||
joyrideStep="selectClassStep"
|
||||
text="{{ 'selectClassDescription' | translate }}"
|
||||
[units]="units"
|
||||
[label]="'labelSelectClass'"
|
||||
(selectionChange)="loadChildUnits($event)">
|
||||
</app-ou-select>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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<any>(`${this.baseUrl}/pxe-templates?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters })
|
||||
|
|
|
|||
|
|
@ -3,18 +3,13 @@
|
|||
<h2 mat-dialog-title>Añade clientes a {{data.subnetName}}</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'selectClassroomPrompt' | translate }}</mat-label>
|
||||
<mat-select [formControl]="unitControl" (selectionChange)="loadChildUnits($event)" (opened)="unitSearchText = ''">
|
||||
<div class="dropdown-search-wrapper">
|
||||
<input type="text" class="dropdown-search-input" [(ngModel)]="unitSearchText" [ngModelOptions]="{standalone: true}" (keydown)="$event.stopPropagation()"
|
||||
placeholder="{{ 'labelSearch' | translate }}">
|
||||
</div>
|
||||
@for (unit of getFilteredUnits(); track unit.id) {
|
||||
<mat-option [value]="unit">{{ unit.displayName }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<app-ou-select
|
||||
class="full-width"
|
||||
[formControl]="unitControl"
|
||||
[units]="units"
|
||||
[label]="'selectClassroomPrompt'"
|
||||
(selectionChange)="loadChildUnits($event)">
|
||||
</app-ou-select>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<label>{{ 'labelClients' | translate }}</label>
|
||||
|
|
|
|||
|
|
@ -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<any>(`${this.baseUrl}/clients?organizationalUnit.id=${event.value.id}&exists[subnet]=false`).subscribe(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
.unit-name {
|
||||
line-height: normal;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<mat-form-field class="full-width">
|
||||
<mat-label>{{ label | translate }}</mat-label>
|
||||
<mat-select [multiple]="multiple" [required]="required" [disabled]="disabled"
|
||||
[(ngModel)]="value" (selectionChange)="onSelectionChange($event)" (opened)="onOpened()">
|
||||
<mat-select-trigger>
|
||||
{{ getSelectedName() }}
|
||||
</mat-select-trigger>
|
||||
<div class="dropdown-search-wrapper">
|
||||
<input type="text" class="dropdown-search-input" [(ngModel)]="searchText" (keydown)="$event.stopPropagation()"
|
||||
placeholder="{{ 'labelSearch' | translate }}">
|
||||
</div>
|
||||
@for (unit of filteredUnits; track (unit.id || unit['@id'])) {
|
||||
<mat-option [value]="unit.id || unit['@id']">
|
||||
<div class="unit-name">{{ unit.name }}</div>
|
||||
@if (unit.path) {
|
||||
<div style="font-size: smaller; color: gray;">{{ unit.path }}</div>
|
||||
}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
|
@ -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<OuSelectComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [OuSelectComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(OuSelectComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -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<any>();
|
||||
|
||||
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 || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue