Merge pull request 'Añadido campo resolución en los ajustes de red de aulas y grupos para propagarlo a los equipos. Rediseño de la vista de propiedades de una OU' (#73) from ou-menu-resolution into main
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
oggui-debian-package/pipeline/head This commit looks good Details

Reviewed-on: #73
pull/74/head
Juan Manuel Bardallo 2026-04-24 09:02:03 +02:00
commit a0dd9a1803
10 changed files with 3365 additions and 3069 deletions

View File

@ -1,5 +1,10 @@
# Changelog
## [1.5.2] - 2026-04-24
### Added
- Añadido campo resolución en los ajustes de red de aulas y grupos para propagarlo a los equipos.
- Rediseño del menú de configuracion de una Unidad Organizativa
## [1.5.1] - 2026-04-23
### Improved
- Creadas claves de traducción para idiomas, añadido menu para cambiar de idioma en la barra superior

View File

@ -108,23 +108,23 @@ export class ClientDetailsComponent {
updateGeneralData() {
this.generalData = [
{ property: 'Nombre', value: this.clientData?.name || '' },
{ property: 'IP', value: this.clientData?.ip || '' },
{ property: 'MAC', value: this.clientData?.mac || '' },
{ property: 'Nº de serie', value: this.clientData?.serialNumber || '' },
{ property: 'Netiface', value: this.clientData?.netiface || this.clientData?.organizationalUnit?.networkSettings?.netiface || '' },
{ property: 'Perfil hardware', value: this.clientData?.hardwareProfile?.description || '' },
{ property: this.translate.instant('labelName'), value: this.clientData?.name || '' },
{ property: this.translate.instant('labelIp'), value: this.clientData?.ip || '' },
{ property: this.translate.instant('labelMac'), value: this.clientData?.mac || '' },
{ property: this.translate.instant('labelSerialNumber'), value: this.clientData?.serialNumber || '' },
{ property: this.translate.instant('labelNetiface'), value: this.clientData?.netiface || this.clientData?.organizationalUnit?.networkSettings?.netiface || '' },
{ property: this.translate.instant('labelHardwareProfile'), value: this.clientData?.hardwareProfile?.description || '' },
];
}
updateNetworkData() {
this.networkData = [
{ property: 'Padre', value: this.clientData?.organizationalUnit?.name || '' },
{ property: 'Pxe', value: this.clientData?.pxeTemplate?.name || '' },
{ property: 'Remote Pc', value: this.clientData.remotePc || '' },
{ property: 'Subred', value: this.clientData?.subnet || '' },
{ property: 'OGlive', value: this.clientData?.ogLive?.name || '' },
{ property: 'Repositorio', value: this.clientData?.repository?.name || '' },
{ property: this.translate.instant('labelParent'), value: this.clientData?.organizationalUnit?.name || '' },
{ property: this.translate.instant('labelTemplate'), value: this.clientData?.pxeTemplate?.name || '' },
{ property: this.translate.instant('remotePc'), value: this.clientData.remotePc || '' },
{ property: this.translate.instant('labelSubnet'), value: this.clientData?.subnet || '' },
{ property: this.translate.instant('labelOgLive'), value: this.clientData?.ogLive?.name || '' },
{ property: this.translate.instant('labelRepository'), value: this.clientData?.repository?.name || '' },
];
}
@ -154,14 +154,14 @@ export class ClientDetailsComponent {
this.chartDisk = Array.from(diskUsageMap.entries()).map(([diskNumber, { total, used, partitions }]) => {
const partitionData = partitions.map(partition => ({
name: `Partición ${partition.partitionNumber}`,
name: `${this.translate.instant('labelPartition')} ${partition.partitionNumber}`,
value: Number((partition.size / 1024).toFixed(2))
}));
const freeSpace = total - used;
if (freeSpace > 0) {
partitionData.push({
name: 'Espacio libre',
name: this.translate.instant('labelFreeSpace'),
value: Number(freeSpace.toFixed(2))
});
}

View File

@ -55,8 +55,8 @@ mat-option .unit-path {
padding: 1.5em;
}
/* Boot grouping */
.boot-group {
/* Generic fieldset grouping */
.fieldset-group {
grid-column: 1 / -1;
border: 1px solid #c5c5c5;
border-radius: 6px;
@ -64,7 +64,7 @@ mat-option .unit-path {
margin: 0;
}
.boot-group-legend {
.fieldset-legend {
font-size: 0.78rem;
color: #3f51b5;
font-weight: 600;
@ -73,7 +73,13 @@ mat-option .unit-path {
text-transform: uppercase;
}
.boot-group-grid {
.fieldset-grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
column-gap: 20px;
}
.fieldset-grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 20px;

View File

@ -20,7 +20,7 @@
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labelRoleName' | translate }}</mat-label>
<mat-label>{{ 'labelName' | translate }}</mat-label>
<input matInput formControlName="name" required>
</mat-form-field>
@ -59,43 +59,49 @@
</mat-select>
</mat-form-field>
<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>
<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="boot-group">
<legend class="boot-group-legend">{{ 'bootConfiguration' | translate }}</legend>
<div class="boot-group-grid">
<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>

View File

@ -66,16 +66,16 @@ h1 {
margin-bottom: 10px;
}
/* Boot grouping */
.boot-group {
/* Generic fieldset grouping */
.fieldset-group {
grid-column: 1 / -1;
border: 1px solid #c5c5c5;
border-radius: 6px;
padding: 12px 16px 4px 16px;
margin: 0;
margin: 10px 0;
}
.boot-group-legend {
.fieldset-legend {
font-size: 0.78rem;
color: #3f51b5;
font-weight: 600;
@ -84,7 +84,13 @@ h1 {
text-transform: uppercase;
}
.boot-group-grid {
.fieldset-grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
column-gap: 20px;
}
.fieldset-grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 20px;

View File

@ -42,10 +42,9 @@
</form>
<!-- Paso 2: Información del Aula -->
<span *ngIf="generalFormGroup.value.type === 'classroom' && !loading"
class="step-title">{{ 'labelClassroomInfoStep' | translate }}</span>
<form *ngIf="generalFormGroup.value.type === 'classroom' && !loading" class="grid-form"
[formGroup]="classroomInfoFormGroup">
@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">
@ -70,20 +69,22 @@
<mat-slide-toggle formControlName="board">{{ 'boardToggle' | translate }}</mat-slide-toggle>
</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="boot-group">
<legend class="boot-group-legend">{{ 'bootConfiguration' | translate }}</legend>
<div class="boot-group-grid">
<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-option [value]="server['@id']">{{ server.name }}</mat-option>
}
</mat-select>
</mat-form-field>
@ -92,10 +93,10 @@
<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>
<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-option disabled>{{ 'noOgLivesInServer' | translate }}</mat-option>
}
</mat-select>
</mat-form-field>
@ -104,16 +105,17 @@
<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>
<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-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)">
@ -150,60 +152,97 @@
<mat-label>{{ 'labelNtp' | translate }}</mat-label>
<input matInput formControlName="ntp">
</mat-form-field>
<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>
<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>
<mat-form-field class="form-field">
<mat-label>{{ 'labelMenu' | translate }}</mat-label>
<mat-select formControlName="menu">
<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>{{ '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-error>{{ 'urlFormatError' | translate }}</mat-error>
</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>
<!-- ===== 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">
<mat-form-field class="form-field description-form-field">
<mat-label>{{ 'labelComments' | translate }}</mat-label>
<textarea matInput formControlName="comments"></textarea>
</mat-form-field>
@ -214,5 +253,5 @@
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="onSubmit()"
[disabled]="!generalFormGroup.valid || !additionalInfoFormGroup.valid || !networkSettingsFormGroup.valid">{{
isEditMode ? ('edit' | translate) : ('createButton' | translate) }}</button>
isEditMode ? ('buttonEdit' | translate) : ('createButton' | translate) }}</button>
</mat-dialog-actions>

View File

@ -78,7 +78,7 @@ export class ManageOrganizationalUnitComponent implements OnInit {
});
this.additionalInfoFormGroup = this._formBuilder.group({
comments: [null],
comments: [null]
});
this.networkSettingsFormGroup = this._formBuilder.group({
@ -99,6 +99,7 @@ export class ManageOrganizationalUnitComponent implements OnInit {
mcastPort: [null],
mcastMode: [null],
menu: [null],
resolution: [null],
hardwareProfile: [null]
});
@ -125,9 +126,11 @@ export class ManageOrganizationalUnitComponent implements OnInit {
Promise.all(observables).then(() => {
if (this.isEditMode) {
this.loadData(this.data.uuid).then(() => {
this.setMenuResolutionIfEmpty();
this.loading = false;
});
} else {
this.setMenuResolutionIfEmpty();
this.loading = false;
}
}).catch(error => {
@ -156,6 +159,7 @@ export class ManageOrganizationalUnitComponent implements OnInit {
ogLive: unit.networkSettings?.ogLive?.['@id'],
pxeTemplate: unit.networkSettings?.pxeTemplate?.['@id'],
menu: unit.networkSettings?.menu?.['@id'],
resolution: unit.networkSettings?.resolution || null,
mcastIp: unit.networkSettings?.mcastIp,
mcastSpeed: unit.networkSettings?.mcastSpeed,
mcastPort: unit.networkSettings?.mcastPort,
@ -192,14 +196,12 @@ export class ManageOrganizationalUnitComponent implements OnInit {
setOrganizationalUnitDefaults(unitId: string): void {
const selectedUnit: any = this.parentUnitsWithPaths.find(unit => unit.id === unitId);
const exclude = this.generalFormGroup.get('excludeParentChanges')?.value;
if (selectedUnit && !exclude) {
if (selectedUnit && !exclude) {
this.networkSettingsFormGroup.patchValue({
repository: selectedUnit.repository || null,
bootServer: selectedUnit.bootServer || null,
hardwareProfile: selectedUnit.hardwareProfile || null,
ogLive: selectedUnit.ogLive || null,
pxeTemplate: selectedUnit.pxeTemplate || null,
menu: selectedUnit.menu || null,
mcastIp: selectedUnit.mcastIp || null,
mcastSpeed: selectedUnit.mcastSpeed || null,
mcastPort: selectedUnit.mcastPort || null,
@ -210,7 +212,10 @@ export class ManageOrganizationalUnitComponent implements OnInit {
dns: selectedUnit.dns || null,
netmask: selectedUnit.netmask || null,
router: selectedUnit.router || null,
ntp: selectedUnit.ntp || null
ntp: selectedUnit.ntp || null,
menu: selectedUnit.menu || null,
resolution: selectedUnit.resolution || null,
hardwareProfile: selectedUnit.hardwareProfile || null
});
}
}
@ -239,11 +244,7 @@ export class ManageOrganizationalUnitComponent implements OnInit {
excludeParentChanges: data.excludeParentChanges
});
this.additionalInfoFormGroup.patchValue({
comments: data.comments
});
// Patch all network fields except ogLive and pxeTemplate
// Patch all network fields except ogLive and pxeTemplate
this.networkSettingsFormGroup.patchValue({
proxy: data.networkSettings?.proxy,
dns: data.networkSettings?.dns,
@ -257,10 +258,15 @@ export class ManageOrganizationalUnitComponent implements OnInit {
mcastSpeed: data.networkSettings?.mcastSpeed,
mcastPort: data.networkSettings?.mcastPort,
mcastMode: data.networkSettings?.mcastMode,
menu: data.networkSettings?.menu ? data.networkSettings.menu['@id'] : null,
bootServer: bootServerIri,
repository: data.networkSettings?.repository ? data.networkSettings.repository['@id'] : null,
menu: data.networkSettings?.menu ? data.networkSettings.menu['@id'] : null,
resolution: data.networkSettings?.resolution ? String(data.networkSettings.resolution) : null,
hardwareProfile: data.networkSettings?.hardwareProfile ? data.networkSettings.hardwareProfile['@id'] : null,
repository: data.networkSettings?.repository ? data.networkSettings.repository['@id'] : null
});
this.additionalInfoFormGroup.patchValue({
comments: data.comments
});
this.classroomInfoFormGroup.patchValue({
@ -304,8 +310,8 @@ export class ManageOrganizationalUnitComponent implements OnInit {
const confirmRef = this.dialog.open(DeleteModalComponent, {
width: '400px',
data: {
title : this.translate.instant('propagateParameters'),
message : this.translate.instant('youWantToPropagateTheParametersOf'),
title: this.translate.instant('propagateParameters'),
message: this.translate.instant('youWantToPropagateTheParametersOf'),
confirmText: 'Sí, propagar',
cancelText: 'No, solo guardar',
}
@ -323,7 +329,8 @@ export class ManageOrganizationalUnitComponent implements OnInit {
private submitForm(propagate: boolean) {
const parentValue = this.generalFormGroup.value.parent;
const networkSettings = { ...this.networkSettingsFormGroup.value };
const networkSettings = { ...this.networkSettingsFormGroup.value };
if (networkSettings.p2pMode !== 'seeder') {
networkSettings.p2pTime = null;
}
@ -334,7 +341,7 @@ export class ManageOrganizationalUnitComponent implements OnInit {
parent: parentValue || null,
description: this.generalFormGroup.value.description,
comments: this.additionalInfoFormGroup.value.comments,
remoteCalendar: this.generalFormGroup.value.remoteCalendar,
remoteCalendar: this.classroomInfoFormGroup.value.remoteCalendar,
type: this.generalFormGroup.value.type,
networkSettings: networkSettings,
location: this.classroomInfoFormGroup.value.location,
@ -491,8 +498,6 @@ export class ManageOrganizationalUnitComponent implements OnInit {
});
}
// loadPxeTemplates and loadOgLives removed: resources are now loaded on-demand per BootServer
loadMenus(): Promise<void> {
return new Promise((resolve, reject) => {
const url = `${this.baseUrl}/menus?page=1&itemsPerPage=10000`;
@ -544,20 +549,47 @@ export class ManageOrganizationalUnitComponent implements OnInit {
});
}
setMenuResolutionIfEmpty(): void {
const selectedMenuId = this.networkSettingsFormGroup.get('menu')?.value;
const currentResolution = this.networkSettingsFormGroup.get('resolution')?.value;
if (selectedMenuId && !currentResolution) {
const selectedMenu = this.menus.find(menu => menu['@id'] === selectedMenuId);
if (selectedMenu && selectedMenu.resolution) {
this.networkSettingsFormGroup.patchValue({
resolution: String(selectedMenu.resolution)
});
}
}
}
onMenuChange(event: any): void {
// Only pre-fill if resolution is empty
const currentResolution = this.networkSettingsFormGroup.get('resolution')?.value;
if (!currentResolution) {
const selectedMenu = this.menus.find(menu => menu['@id'] === event.value);
if (selectedMenu && selectedMenu.resolution) {
this.networkSettingsFormGroup.patchValue({
resolution: String(selectedMenu.resolution)
});
}
}
}
onCalendarChange(event: any) {
this.generalFormGroup.value.remoteCalendar = event.value;
this.classroomInfoFormGroup.patchValue({ remoteCalendar: event.value });
}
onOgLiveChange(event: any) {
this.networkSettingsFormGroup.value.ogLive = event.value;
this.networkSettingsFormGroup.patchValue({ ogLive: event.value });
}
onRepositoryChange(event: any) {
this.networkSettingsFormGroup.value.repository = event.value;
this.networkSettingsFormGroup.patchValue({ repository: event.value });
}
onPxeTemplateChange(event: any) {
this.networkSettingsFormGroup.value.pxeTemplate = event.value;
this.networkSettingsFormGroup.patchValue({ pxeTemplate: event.value });
}
onNoClick(): void {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,118 @@
const fs = require('fs');
const path = require('path');
// Directorios base: buscará en todo ./src y modificará los JSON de ./src/locale
const srcDir = __dirname+"/src";
const localeDir = path.join(srcDir, 'locale');
// Expresiones regulares para capturar las claves de traducción
const regexes = [
// Captura: 'mi.clave' | translate O "mi.clave" | translate
/['"]([^'"]+)['"]\s*\|\s*translate/g,
// Captura: translate="mi.clave" O translate='mi.clave'
/translate\s*=\s*['"]([^'"]+)['"]/g,
// Captura: translate.instant('mi.clave') O translate.instant("mi.clave")
/translate\.instant\(\s*['"]([^'"]+)['"]/g,
// Captura: [translate]="'mi.clave'"
/\[translate\]\s*=\s*['"]'([^']+)'['"]/g,
// Captura: [translate]='"mi.clave"'
/\[translate\]\s*=\s*['"]"([^"]+)"['"]/g
];
/**
* Función recursiva para obtener todos los archivos con las extensiones deseadas
*/
function getFiles(dir, exts, fileList = []) {
const files = fs.readdirSync(dir);
for (const file of files) {
const filePath = path.join(dir, file);
if (fs.statSync(filePath).isDirectory()) {
getFiles(filePath, exts, fileList);
} else {
if (exts.includes(path.extname(filePath))) {
fileList.push(filePath);
}
}
}
return fileList;
}
/**
* Extrae todas las claves únicas usando las expresiones regulares
*/
function extractKeys() {
const files = getFiles(srcDir, ['.ts', '.html']);
const keys = new Set();
files.forEach(file => {
const content = fs.readFileSync(file, 'utf8');
regexes.forEach(regex => {
let match;
while ((match = regex.exec(content)) !== null) {
const key = match[1];
if (key && key.trim() !== '') {
keys.add(key.trim());
}
}
});
});
return Array.from(keys);
}
/**
* Actualiza y ordena los archivos de idiomas
*/
function updateLocales(extractedKeys) {
if (!fs.existsSync(localeDir)) {
console.error('No se pudo encontrar el directorio de traducciones:', localeDir);
return;
}
const localeFiles = fs.readdirSync(localeDir).filter(f => f.endsWith('.json'));
localeFiles.forEach(file => {
const filePath = path.join(localeDir, file);
let localeData = {};
try {
localeData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (e) {
console.error(`Error al procesar el JSON en ${file}:`, e.message);
return;
}
let addedCount = 0;
// Añadir claves faltantes
extractedKeys.forEach(key => {
if (!localeData.hasOwnProperty(key)) {
localeData[key] = key; // Usa la propia clave como valor
addedCount++;
}
});
// Ordenar alfabéticamente todas las claves
const sortedData = {};
Object.keys(localeData)
.sort((a, b) => a.localeCompare(b))
.forEach(k => {
sortedData[k] = localeData[k];
});
// Guardar el fichero de nuevo
fs.writeFileSync(filePath, JSON.stringify(sortedData, null, 4) + '\n', 'utf8');
console.log(`[${file}] Actualizado. Agregadas ${addedCount} claves nuevas. Fichero ordenado alfabéticamente.`);
});
}
function run() {
console.log('Buscando claves de traducción en archivos .ts y .html...');
const keys = extractKeys();
console.log(`Se encontraron ${keys.length} claves únicas en el código.`);
console.log('Actualizando diccionarios...');
updateLocales(keys);
console.log('¡Proceso completado exitosamente!');
}
run();