feat: UI search filters, network speeds and createImage for data partitions
parent
7d71ca92f3
commit
361b98d221
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -73,76 +73,7 @@
|
|||
<div class="main-container">
|
||||
<!-- FILTERS AND TREE VIEW -->
|
||||
<div class="filters-and-tree-container">
|
||||
<!-- Filters -->
|
||||
<div class="filters-panel" joyrideStep="filtersPanelStep" text="{{ 'filtersPanelStepText' | translate }}">
|
||||
<div class="filters-container">
|
||||
<mat-form-field class="filter-form-field" appearance="outline">
|
||||
<mat-label>{{ 'searchTree' | translate }}</mat-label>
|
||||
<input matInput #treeSearchInput (input)="onTreeFilterInput($event)"
|
||||
[placeholder]="'placeholderOrgUnitTypes' | translate" />
|
||||
@if (treeSearchInput.value) {
|
||||
<button mat-icon-button matSuffix [attr.aria-label]="'clearTreeSearch' | translate"
|
||||
(click)="clearTreeSearch(treeSearchInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field class="filter-form-field" appearance="outline">
|
||||
<mat-label>{{ 'searchClient' | translate }}</mat-label>
|
||||
<input matInput #clientSearchInput (input)="onClientFilterInput($event)"
|
||||
[placeholder]="'searchNameIpStatusMac' | translate" />
|
||||
@if (clientSearchInput.value) {
|
||||
<button mat-icon-button matSuffix [attr.aria-label]="'clearClientSearch' | translate"
|
||||
(click)="clearClientSearch(clientSearchInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field search-select" appearance="outline">
|
||||
<mat-select placeholder="{{ 'searchState' | translate }}" #clientSearchStatusInput
|
||||
(selectionChange)="onClientFilterStatusInput($event.value)">
|
||||
@for (option of status; track option) {
|
||||
<mat-option [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
@if (clientSearchStatusInput.value) {
|
||||
<button mat-icon-button matSuffix [attr.aria-label]="'clearTreeSearch' | translate"
|
||||
(click)="clearStatusFilter($event, clientSearchStatusInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
<div class="direct-clients-toggle">
|
||||
<mat-slide-toggle
|
||||
[(ngModel)]="showOnlyDirectClients"
|
||||
(change)="onDirectClientsToggleChange()"
|
||||
matTooltip="{{ 'directClientsToggleTooltip' | translate }}"
|
||||
matTooltipShowDelay="1000">
|
||||
{{ 'directClientsToggle' | translate }}
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<!-- Funcionalidad actualmente deshabilitada-->
|
||||
<!-- <mat-form-field appearance="outline">
|
||||
<mat-select (selectionChange)="loadSelectedFilter($event.value)" [placeholder]="'buttonLoadFilter' | translate" disabled>
|
||||
<mat-option *ngFor="let savedFilter of savedFilterNames" [value]="savedFilter">
|
||||
{{ savedFilter[0] }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ 'filterByType' | translate }}</mat-label>
|
||||
<mat-select [(value)]="selectedTreeFilter" (selectionChange)="filterTree(searchTerm, $event.value)" disabled>
|
||||
<mat-option [value]=""> {{ 'all' | translate }} </mat-option>
|
||||
<mat-option value="classrooms-group">{{ 'classroomsGroup' | translate }}</mat-option>
|
||||
<mat-option value="classroom">{{ 'classrooms' | translate }}</mat-option>
|
||||
<mat-option value="group">{{ 'computerGroups' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field> -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tree -->
|
||||
<!-- Tree -->
|
||||
<div class="tree-container" joyrideStep="treePanelStep" text="{{ 'treePanelStepText' | translate }}">
|
||||
<div class="tree-header">
|
||||
<h3 class="tree-title">
|
||||
|
|
@ -150,14 +81,69 @@
|
|||
{{ 'organizationalStructure' | translate }}
|
||||
</h3>
|
||||
<div class="tree-actions">
|
||||
<button mat-icon-button (click)="expandAll()" matTooltip="{{ 'expandAll' | translate }}">
|
||||
<mat-icon>unfold_more</mat-icon>
|
||||
<button mat-icon-button (click)="toggleSearchFilters()" matTooltip="{{ 'search' | translate }}" matTooltipShowDelay="1000">
|
||||
<mat-icon>search</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button (click)="collapseAll()" matTooltip="{{ 'collapseAll' | translate }}">
|
||||
<mat-icon>unfold_less</mat-icon>
|
||||
<button mat-icon-button (click)="toggleExpandAll()" matTooltip="{{ (isTreeExpanded ? 'collapseAll' : 'expandAll') | translate }}">
|
||||
<mat-icon>{{ isTreeExpanded ? 'unfold_less' : 'unfold_more' }}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
@if (showSearchFilters) {
|
||||
<div class="filters-panel" joyrideStep="filtersPanelStep" text="{{ 'filtersPanelStepText' | translate }}">
|
||||
<div class="filters-container">
|
||||
<mat-form-field class="filter-form-field" appearance="outline">
|
||||
<mat-label>{{ 'searchTree' | translate }}</mat-label>
|
||||
<input matInput #treeSearchInput (input)="onTreeFilterInput($event)"
|
||||
[placeholder]="'placeholderOrgUnitTypes' | translate" />
|
||||
@if (treeSearchInput.value) {
|
||||
<button mat-icon-button matSuffix [attr.aria-label]="'clearTreeSearch' | translate"
|
||||
(click)="clearTreeSearch(treeSearchInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field class="filter-form-field" appearance="outline">
|
||||
<mat-label>{{ 'searchClient' | translate }}</mat-label>
|
||||
<input matInput #clientSearchInput (input)="onClientFilterInput($event)"
|
||||
[placeholder]="'searchNameIpStatusMac' | translate" />
|
||||
@if (clientSearchInput.value) {
|
||||
<button mat-icon-button matSuffix [attr.aria-label]="'clearClientSearch' | translate"
|
||||
(click)="clearClientSearch(clientSearchInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field search-select" appearance="outline">
|
||||
<mat-select placeholder="{{ 'searchState' | translate }}" #clientSearchStatusInput
|
||||
(selectionChange)="onClientFilterStatusInput($event.value)">
|
||||
@for (option of status; track option) {
|
||||
<mat-option [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
@if (clientSearchStatusInput.value) {
|
||||
<button mat-icon-button matSuffix [attr.aria-label]="'clearTreeSearch' | translate"
|
||||
(click)="clearStatusFilter($event, clientSearchStatusInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
<div class="direct-clients-toggle">
|
||||
<mat-slide-toggle
|
||||
[(ngModel)]="showOnlyDirectClients"
|
||||
(change)="onDirectClientsToggleChange()"
|
||||
matTooltip="{{ 'directClientsToggleTooltip' | translate }}"
|
||||
matTooltipShowDelay="1000">
|
||||
{{ 'directClientsToggle' | translate }}
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<mat-tree [dataSource]="treeDataSource" [treeControl]="treeControl" class="modern-tree">
|
||||
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id, 'tree-node': true}"
|
||||
*matTreeNodeDef="let node; when: hasChild" matTreeNodePadding (click)="onNodeClick($event, node)">
|
||||
|
|
@ -420,7 +406,7 @@
|
|||
<span class="client-ip">{{ client.ip }}</span>
|
||||
<span class="client-mac">{{ client.mac }}</span>
|
||||
@if (client.netSpeed) {
|
||||
<span class="badge-net-speed" [matTooltip]="client.netSpeed + ' Mbps'">
|
||||
<span class="badge-net-speed speed-{{client.netSpeed}} " [matTooltip]="client.netSpeed + ' Mbps'">
|
||||
<mat-icon class="net-speed-icon">speed</mat-icon> {{ client.netSpeed }} Mbps
|
||||
</span>
|
||||
}
|
||||
|
|
@ -546,7 +532,7 @@
|
|||
<span class="client-ip">{{ client.ip }}</span>
|
||||
<span class="client-mac">{{ client.mac }}</span>
|
||||
@if (client.netSpeed) {
|
||||
<span class="badge-net-speed list-view-badge" [matTooltip]="client.netSpeed + ' Mbps'">
|
||||
<span class="badge-net-speed speed-{{client.netSpeed}} list-view-badge" [matTooltip]="client.netSpeed + ' Mbps'">
|
||||
<mat-icon class="net-speed-icon">speed</mat-icon> {{ client.netSpeed }} Mbps
|
||||
</span>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string>();
|
||||
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<any>(`${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();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -28,9 +28,13 @@
|
|||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="selectClassStep"
|
||||
text="{{ 'selectClassDescription' | translate }}">
|
||||
<mat-label>{{ 'labelSelectClass' | translate }}</mat-label>
|
||||
<mat-select (selectionChange)="loadChildUnits($event)">
|
||||
@for (unit of units; track unit.id) {
|
||||
<mat-option [value]="unit">{{ unit.name }}</mat-option>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -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<any>(`${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<any>(`${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)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -39,4 +39,3 @@ mat-dialog-actions {
|
|||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,14 @@
|
|||
<mat-dialog-content>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'selectClassroomPrompt' | translate }}</mat-label>
|
||||
<mat-select [formControl]="unitControl" (selectionChange)="loadChildUnits($event)">
|
||||
<mat-option *ngFor="let unit of units" [value]="unit">{{ unit.name }}</mat-option>
|
||||
<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>
|
||||
|
||||
|
|
@ -17,16 +23,20 @@
|
|||
{{ allSelected ? 'Deseleccionar todos' : 'Seleccionar todos' }}
|
||||
</button>
|
||||
|
||||
<div *ngIf="clients.length > 0" class="clients-grid">
|
||||
<mat-checkbox *ngFor="let client of clients"
|
||||
(change)="toggleClientSelection(client.uuid)"
|
||||
[checked]="selectedClients.includes(client.uuid)">
|
||||
{{ client.name }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div *ngIf="clients.length === 0">
|
||||
<p>{{ 'noClientsMessage' | translate }}</p>
|
||||
</div>
|
||||
@if (clients.length > 0) {
|
||||
<div class="clients-grid">
|
||||
@for (client of clients; track client.uuid) {
|
||||
<mat-checkbox (change)="toggleClientSelection(client.uuid)"
|
||||
[checked]="selectedClients.includes(client.uuid)">
|
||||
{{ client.name }}
|
||||
</mat-checkbox>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div>
|
||||
<p>{{ 'noClientsMessage' | translate }}</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
|
|
|
|||
|
|
@ -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<any>(`${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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue