diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts
index fffa10a..a752bae 100644
--- a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts
+++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts
@@ -66,7 +66,8 @@ export class CreateClientImageComponent implements OnInit{
}
isPartitionDisabled = (row: any): boolean => {
- return !row.operativeSystem;
+ // Se debe permitir crear imagen de particiones de sólo datos, tan sólo evitamos partición CACHE
+ return row.filesystem === 'CACHE';
};
columns = [
diff --git a/ogWebconsole/src/app/components/groups/groups.component.css b/ogWebconsole/src/app/components/groups/groups.component.css
index 3730d39..cb4b5a4 100644
--- a/ogWebconsole/src/app/components/groups/groups.component.css
+++ b/ogWebconsole/src/app/components/groups/groups.component.css
@@ -1617,6 +1617,44 @@ mat-button-toggle-group {
height: 14px;
}
+/* 2. MODIFICADORES DE COLOR (Fondo claro + Texto oscuro de la misma gama) */
+
+/* 10 Gbps - Morado (Destaca como velocidad ultra-rápida/premium) */
+.badge-net-speed.speed-10000 {
+ background-color: #f3e5f5; /* Purple 50 */
+ color: #7b1fa2; /* Purple 700 */
+}
+
+/* 2.5 Gbps / 5 Gbps - Azul (El que tú ya tenías) */
+.badge-net-speed.speed-5000, .badge-net-speed.speed-2500 {
+ background-color: #e3f2fd; /* Blue 50 */
+ color: #1976d2; /* Blue 700 */
+}
+
+/* 1 Gbps - Verde (El estándar de red saludable) */
+.badge-net-speed.speed-1000 {
+ background-color: #e8f5e9; /* Green 50 */
+ color: #388e3c; /* Green 700 */
+}
+
+/* 100 Mbps - Naranja (Aviso de enlace inferior/cuello de botella) */
+.badge-net-speed.speed-100 {
+ background-color: #fff3e0; /* Orange 50 */
+ color: #f57c00; /* Orange 700 */
+}
+
+/* 10 Mbps - Rojo (Aviso crítico / Posible cable roto) */
+.badge-net-speed.speed-10 {
+ background-color: #ffebee; /* Red 50 */
+ color: #d32f2f; /* Red 700 */
+}
+
+/* Desconectado / Down - Gris (Neutro) */
+.badge-net-speed.speed-down {
+ background-color: #f5f5f5; /* Grey 100 */
+ color: #616161; /* Grey 700 */
+}
+
.list-view-badge {
display: inline-flex;
margin-top: 0;
diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html
index 568509c..aab9067 100644
--- a/ogWebconsole/src/app/components/groups/groups.component.html
+++ b/ogWebconsole/src/app/components/groups/groups.component.html
@@ -73,76 +73,7 @@
-
-
-
-
- {{ 'searchTree' | translate }}
-
- @if (treeSearchInput.value) {
-
- }
-
-
- {{ 'searchClient' | translate }}
-
- @if (clientSearchInput.value) {
-
- }
-
-
-
- @for (option of status; track option) {
-
- {{ option.name }}
-
- }
-
- @if (clientSearchStatusInput.value) {
-
- }
-
-
-
- {{ 'directClientsToggle' | translate }}
-
-
-
-
-
-
-
+
+
+
+ @if (showSearchFilters) {
+
+
+
+ {{ 'searchTree' | translate }}
+
+ @if (treeSearchInput.value) {
+
+ }
+
+
+ {{ 'searchClient' | translate }}
+
+ @if (clientSearchInput.value) {
+
+ }
+
+
+
+ @for (option of status; track option) {
+
+ {{ option.name }}
+
+ }
+
+ @if (clientSearchStatusInput.value) {
+
+ }
+
+
+
+ {{ 'directClientsToggle' | translate }}
+
+
+
+
+ }
@@ -420,7 +406,7 @@
{{ client.ip }}
{{ client.mac }}
@if (client.netSpeed) {
-
+
speed {{ client.netSpeed }} Mbps
}
@@ -546,7 +532,7 @@
{{ client.ip }}
{{ client.mac }}
@if (client.netSpeed) {
-
+
speed {{ client.netSpeed }} Mbps
}
diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts
index 984718d..7b0eb55 100644
--- a/ogWebconsole/src/app/components/groups/groups.component.ts
+++ b/ogWebconsole/src/app/components/groups/groups.component.ts
@@ -30,7 +30,7 @@ import { MatMenuTrigger } from '@angular/material/menu';
import { ClientDetailsComponent } from './shared/client-details/client-details.component';
import { PartitionTypeOrganizatorComponent } from './shared/partition-type-organizator/partition-type-organizator.component';
import { ClientTaskLogsComponent } from '../task-logs/client-task-logs/client-task-logs.component';
-import {ChangeParentComponent} from "./shared/change-parent/change-parent.component";
+import { ChangeParentComponent } from "./shared/change-parent/change-parent.component";
import { AuthService } from '@services/auth.service';
import { ClientPendingTasksComponent } from '../task-logs/client-pending-tasks/client-pending-tasks.component';
import { ClientLogsModalComponent } from './shared/client-logs-modal/client-logs-modal.component';
@@ -88,6 +88,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
private clientFilterSubject = new Subject();
loading = false;
showOnlyDirectClients: boolean = false;
+ showSearchFilters: boolean = false;
// Nuevas propiedades para funcionalidades mejoradas
selectedClient: any = null;
@@ -102,11 +103,11 @@ export class GroupsComponent implements OnInit, OnDestroy {
online: number;
busy: number;
} = {
- total: 0,
- off: 0,
- online: 0,
- busy: 0
- };
+ total: 0,
+ off: 0,
+ online: 0,
+ busy: 0
+ };
// Tipos de firmware disponibles
firmwareTypes: string[] = [];
@@ -163,6 +164,15 @@ export class GroupsComponent implements OnInit, OnDestroy {
if (!this.pageSizeOptions.includes(this.itemsPerPage)) {
this.pageSizeOptions = [...this.pageSizeOptions, this.itemsPerPage].sort((a, b) => a - b);
}
+
+ // Load search filters visibility state from localStorage, default false
+ const savedShowSearch = localStorage.getItem('showSearchFilters');
+ this.showSearchFilters = savedShowSearch ? JSON.parse(savedShowSearch) : false;
+ }
+
+ toggleSearchFilters(): void {
+ this.showSearchFilters = !this.showSearchFilters;
+ localStorage.setItem('showSearchFilters', JSON.stringify(this.showSearchFilters));
}
@@ -485,7 +495,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
this.selection.select(client);
}
});
-
+
if (this.isCheckingNodeStatus) {
members.forEach((c: Client) => c.uuid && this.syncingClientIds.add(c.uuid));
}
@@ -533,7 +543,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
}
this.loading = false;
});
-
+
}
@@ -886,27 +896,27 @@ export class GroupsComponent implements OnInit, OnDestroy {
this.selectedClients.data.forEach(c => c.uuid && this.syncingClientIds.add(c.uuid));
this.checkStatusSubscription = this.http.post<{ results: Array<{ uuid: string; ip: string; status: string; success: boolean }> }>(
- `${this.baseUrl}/organizational-units/${node.uuid}/agent/status`,
- {}
- ).subscribe({
- next: (response) => {
- this.isCheckingNodeStatus = false;
- const statusMap = new Map(response.results.map(r => [r.uuid, r.status]));
- response.results.forEach(r => this.syncingClientIds.delete(r.uuid));
- this.selectedClients.data = this.selectedClients.data.map(client => {
- const newStatus = statusMap.get(client.uuid);
- return newStatus ? { ...client, status: newStatus } : client;
- });
- this.arrayClients = this.selectedClients.data;
- this.calculateLocalStats();
- this.toastr.success(this.translate.instant('classroomStatusUpdated'));
- },
- error: () => {
- this.isCheckingNodeStatus = false;
- this.selectedClients.data.forEach(c => c.uuid && this.syncingClientIds.delete(c.uuid));
- this.toastr.error(this.translate.instant('errorCheckingStatus'));
- },
- });
+ `${this.baseUrl}/organizational-units/${node.uuid}/agent/status`,
+ {}
+ ).subscribe({
+ next: (response) => {
+ this.isCheckingNodeStatus = false;
+ const statusMap = new Map(response.results.map(r => [r.uuid, r.status]));
+ response.results.forEach(r => this.syncingClientIds.delete(r.uuid));
+ this.selectedClients.data = this.selectedClients.data.map(client => {
+ const newStatus = statusMap.get(client.uuid);
+ return newStatus ? { ...client, status: newStatus } : client;
+ });
+ this.arrayClients = this.selectedClients.data;
+ this.calculateLocalStats();
+ this.toastr.success(this.translate.instant('classroomStatusUpdated'));
+ },
+ error: () => {
+ this.isCheckingNodeStatus = false;
+ this.selectedClients.data.forEach(c => c.uuid && this.syncingClientIds.delete(c.uuid));
+ this.toastr.error(this.translate.instant('errorCheckingStatus'));
+ },
+ });
}
@@ -988,11 +998,11 @@ export class GroupsComponent implements OnInit, OnDestroy {
const selectedClientNames = clientData.map(client => client.name);
if (
- this.selectedNode &&
+ this.selectedNode &&
clientData.length === this.selectedClients.data.length &&
selectedClientNames.every(name => this.selectedClients.data.some(c => c.name === name))
) {
- return this.selectedNode;
+ return this.selectedNode;
} else if (clientData.length === 1) {
return clientData[0];
} else if (clientData.length > 1) {
@@ -1017,7 +1027,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
});
}
- changeParent(event: MouseEvent, ): void {
+ changeParent(event: MouseEvent,): void {
event.stopPropagation();
const dialogRef = this.dialog.open(ChangeParentComponent, {
@@ -1128,7 +1138,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
this.http.get(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}&page=1&itemsPerPage=10000`).subscribe({
next: (response) => {
const allClients = response['hydra:member'] || [];
-
+
if (allClients.length === 0) {
this.toastr.warning(this.translate.instant('thisOrganizationalUnitDoesNotHave'));
return;
@@ -1252,7 +1262,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
}
getStatusCount(status: string): number {
- switch(status) {
+ switch (status) {
case 'off':
return this.totalStats.off;
case 'online':
@@ -1266,12 +1276,15 @@ export class GroupsComponent implements OnInit, OnDestroy {
// Métodos para el árbol mejorado
- expandAll(): void {
- this.treeControl.expandAll();
- }
+ isTreeExpanded: boolean = false;
- collapseAll(): void {
- this.treeControl.collapseAll();
+ toggleExpandAll(): void {
+ if (this.isTreeExpanded) {
+ this.treeControl.collapseAll();
+ } else {
+ this.treeControl.expandAll();
+ }
+ this.isTreeExpanded = !this.isTreeExpanded;
}
getNodeTypeTooltip(nodeType: string): string {
@@ -1358,7 +1371,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
deleteMultipleClients(event: MouseEvent): void {
event.stopPropagation();
-
+
if (this.selection.selected.length === 0) {
this.toastr.warning(this.translate.instant('thereAreNoClientsSelectedFor'));
return;
@@ -1366,10 +1379,10 @@ export class GroupsComponent implements OnInit, OnDestroy {
const selectedClients = this.selection.selected;
const clientNames = selectedClients.map(client => client.name).join(', ');
-
+
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '500px',
- data: {
+ data: {
name: `${selectedClients.length} cliente${selectedClients.length > 1 ? 's' : ''}`,
message: `¿Está seguro de que desea eliminar los siguientes clientes?\n\n${clientNames}`,
isMultiple: true
@@ -1391,7 +1404,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
next: (response) => {
this.loading = false;
this.showDeletionResults(response, selectedClients);
-
+
this.refreshData(this.selectedNode?.id);
this.selection.clear();
},
diff --git a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html
index 1affb96..3c8447c 100644
--- a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html
+++ b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.html
@@ -28,9 +28,13 @@
{{ 'labelSelectClass' | translate }}
-
- @for (unit of units; track unit.id) {
- {{ unit.name }}
+
+
+
+
+ @for (unit of getFilteredUnits(); track unit.id) {
+ {{ unit.displayName }}
}
diff --git a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts
index f9e0b6a..3e06504 100644
--- a/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts
+++ b/ogWebconsole/src/app/components/ogboot/pxe-boot-files/pxe-boot-files.component.ts
@@ -32,6 +32,15 @@ export class PxeBootFilesComponent implements OnInit {
isFromDashboard: boolean = false;
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;
@@ -97,7 +106,11 @@ export class PxeBootFilesComponent implements OnInit {
loadUnits() {
this.http.get(`${this.baseUrl}/organizational-units?type=classroom&page=1&itemsPerPage=50`).subscribe(
response => {
- this.units = response['hydra:member'];
+ this.units = response['hydra:member'].map((unit: any) => ({
+ ...unit,
+ displayName: unit.parent && unit.parent.name ? `${unit.parent.name} / ${unit.name}` : unit.name
+ }));
+ this.units.sort((a, b) => a.displayName.localeCompare(b.displayName));
},
error => console.error(this.translate.instant('errorFetchingOrganizationalUnits1'), error)
);
@@ -125,7 +138,10 @@ export class PxeBootFilesComponent implements OnInit {
if (!this.selectedUnit) return;
this.http.get(`${this.baseUrl}/clients?organizationalUnit.id=${this.selectedUnit.id}`).subscribe(
response => {
- this.dataSource.data = response['hydra:member'];
+ this.dataSource.data = response['hydra:member'].map((client: any) => ({
+ ...client,
+ ogLive: client.pxeTemplate ? client.pxeTemplate['@id'] : null
+ }));
},
error => console.error(this.translate.instant('errorFetchingChildUnits'), error)
);
diff --git a/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.css b/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.css
index b0c1411..1eab271 100644
--- a/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.css
+++ b/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.css
@@ -39,4 +39,3 @@ mat-dialog-actions {
display: block;
margin-bottom: 10px;
}
-
diff --git a/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.html b/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.html
index 7c6c685..fc92271 100644
--- a/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.html
+++ b/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.html
@@ -5,8 +5,14 @@
{{ 'selectClassroomPrompt' | translate }}
-
- {{ unit.name }}
+
+
+
+
+ @for (unit of getFilteredUnits(); track unit.id) {
+ {{ unit.displayName }}
+ }
@@ -17,16 +23,20 @@
{{ allSelected ? 'Deseleccionar todos' : 'Seleccionar todos' }}
- 0" class="clients-grid">
-
- {{ client.name }}
-
-
-
-
{{ 'noClientsMessage' | translate }}
-
+ @if (clients.length > 0) {
+
+ @for (client of clients; track client.uuid) {
+
+ {{ client.name }}
+
+ }
+
+ } @else {
+
+
{{ 'noClientsMessage' | translate }}
+
+ }
diff --git a/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.ts b/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.ts
index c4bb1ef..c48c57f 100644
--- a/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.ts
+++ b/ogWebconsole/src/app/components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component.ts
@@ -23,6 +23,15 @@ 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,
@@ -43,7 +52,11 @@ export class AddClientsToSubnetComponent implements OnInit {
loadUnits() {
this.http.get
(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=500`).subscribe(
response => {
- this.units = response['hydra:member'];
+ this.units = response['hydra:member'].map((unit: any) => ({
+ ...unit,
+ displayName: unit.parent && unit.parent.name ? `${unit.parent.name} / ${unit.name}` : unit.name
+ }));
+ this.units.sort((a, b) => a.displayName.localeCompare(b.displayName));
this.loading = false;
},
error => console.error(this.translate.instant('errorFetchingOrganizationalUnits1'), error)
diff --git a/ogWebconsole/src/styles.css b/ogWebconsole/src/styles.css
index d399c19..e389072 100644
--- a/ogWebconsole/src/styles.css
+++ b/ogWebconsole/src/styles.css
@@ -197,3 +197,20 @@ mat-toolbar button[mat-icon-button] {
.modal-overlay-blur.error {
background: rgba(220, 53, 69, 0.8);
}
+
+/* Dropdown search elements for material select */
+.dropdown-search-wrapper {
+ padding: 10px;
+ position: sticky;
+ top: 0;
+ background-color: white;
+ z-index: 100;
+}
+
+.dropdown-search-input {
+ width: 100%;
+ padding: 8px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+}