Merge branch 'develop' for 0.6.2 release
commit
1540156ffb
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,6 +1,14 @@
|
|||
# Changelog
|
||||
|
||||
## [0.6.0] - 2024-11-21
|
||||
## [0.6.1] - 2024-11-19
|
||||
|
||||
### Improved
|
||||
- Introduced a new automatic sync mode for the ogdhcp and ogBoot components.
|
||||
- Improve test coverage.
|
||||
- New view for clients inside the classroom on the main page.
|
||||
|
||||
|
||||
## [0.6.0] - 2024-11-19
|
||||
|
||||
### Added
|
||||
- Added functionality to execute actions from the menu in the general groups screen.
|
||||
|
@ -30,3 +38,4 @@
|
|||
|
||||
---
|
||||
|
||||
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let translateService: TranslateService;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
TranslateModule.forRoot()
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
translateService = TestBed.inject(TranslateService);
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
|
@ -21,4 +25,42 @@ describe('AppComponent', () => {
|
|||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'ogWebconsole'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('ogWebconsole');
|
||||
});
|
||||
|
||||
it('should set the language from localStorage on creation', () => {
|
||||
spyOn(localStorage, 'getItem').and.returnValue('en'); // Simula que el idioma guardado es "en"
|
||||
spyOn(translateService, 'use');
|
||||
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(localStorage.getItem).toHaveBeenCalledWith('language');
|
||||
expect(translateService.use).toHaveBeenCalledWith('en');
|
||||
});
|
||||
|
||||
it('should default to Spanish if no language is saved in localStorage', () => {
|
||||
spyOn(localStorage, 'getItem').and.returnValue(null); // Simula que no hay idioma guardado
|
||||
spyOn(translateService, 'use');
|
||||
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(localStorage.getItem).toHaveBeenCalledWith('language');
|
||||
expect(translateService.use).toHaveBeenCalledWith('es');
|
||||
});
|
||||
|
||||
it('should set language to Spanish in sessionStorage on ngOnInit', () => {
|
||||
spyOn(sessionStorage, 'setItem');
|
||||
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
|
||||
app.ngOnInit();
|
||||
expect(sessionStorage.setItem).toHaveBeenCalledWith('language', 'es');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -218,17 +218,75 @@ mat-card {
|
|||
.classroom-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px; /* Espacio entre elementos */
|
||||
justify-content: flex-start; /* Alinea los elementos a la izquierda */
|
||||
gap: 16px;
|
||||
justify-content: flex-start; /* Opcional: para alinear a la izquierda */
|
||||
}
|
||||
|
||||
.classroom-item {
|
||||
flex: 0 1 calc(16.66% - 16px); /* 6 columnas (para pantallas grandes) */
|
||||
box-sizing: border-box; /* Incluye padding y borde en el cálculo del ancho */
|
||||
flex: 0 1 calc(16.66% - 16px); /* 6 columnas */
|
||||
max-width: calc(16.66% - 16px);
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.classroom-card {
|
||||
width: 100%; /* Asegura que el card ocupe todo el espacio del item */
|
||||
.classroom-pc {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
background-color: #f4f4f4;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.pc-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.pc-details {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.client-name {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.client-ip,
|
||||
.client-mac {
|
||||
color: #666;
|
||||
font-size: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pc-actions {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pc-og-live {
|
||||
border: 2px solid #4caf50;
|
||||
}
|
||||
|
||||
.pc-busy {
|
||||
border: 2px solid #ff9800;
|
||||
}
|
||||
|
||||
.pc-off {
|
||||
border: 2px solid #f44336;
|
||||
}
|
||||
|
||||
.pc-linux {
|
||||
border: 2px solid #9c27b0;
|
||||
}
|
||||
|
||||
.pc-windows {
|
||||
border: 2px solid #2196f3;
|
||||
}
|
||||
|
||||
/* Pantallas medianas: 4 columnas */
|
||||
|
@ -261,33 +319,3 @@ mat-card {
|
|||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-og-live {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.card-busy {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.card-windows {
|
||||
background-color: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.card-linux {
|
||||
background-color: #9c27b0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.card-macos {
|
||||
background-color: #ff9800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.card-off {
|
||||
background-color: #9e9e9e;
|
||||
color: white;
|
||||
}
|
||||
|
|
|
@ -111,32 +111,32 @@
|
|||
<!-- Mostrar cuadrícula si es un aula -->
|
||||
<div *ngIf="selectedDetail?.type === 'classroom'" class="classroom-grid">
|
||||
<div *ngFor="let pc of selectedDetail.clients" class="classroom-item">
|
||||
<mat-card class="classroom-card" [ngClass]="{
|
||||
'card-og-live': pc.status === 'og-live',
|
||||
'card-busy': pc.status === 'busy',
|
||||
'card-windows': pc.status === 'windows' || pc.status === 'windows-session',
|
||||
'card-linux': pc.status === 'linux' || pc.status === 'linux-session',
|
||||
'card-macos': pc.status === 'macos',
|
||||
'card-off': pc.status === 'off'
|
||||
}">
|
||||
<mat-card-header>
|
||||
<mat-card-title class="client-name">{{ pc.name }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p class="client-text">{{ pc.ip }}</p>
|
||||
<p class="client-text">{{ pc.mac }}</p>
|
||||
</mat-card-content>
|
||||
<mat-card-actions align="end">
|
||||
<div class="classroom-pc" [ngClass]="{
|
||||
'pc-og-live': pc.status === 'og-live',
|
||||
'pc-busy': pc.status === 'busy',
|
||||
'pc-windows': pc.status === 'windows' || pc.status === 'windows-session',
|
||||
'pc-linux': pc.status === 'linux' || pc.status === 'linux-session',
|
||||
'pc-macos': pc.status === 'macos',
|
||||
'pc-off': pc.status === 'off'
|
||||
}">
|
||||
<img mat-card-image src="assets/images/client.png" alt="PC Icon" class="pc-image">
|
||||
<div class="pc-details">
|
||||
<span class="client-name">{{ pc.name }}</span>
|
||||
<span class="client-ip">{{ pc.ip }}</span>
|
||||
<span class="client-mac">{{ pc.mac }}</span>
|
||||
</div>
|
||||
<div class="pc-actions">
|
||||
<button mat-icon-button color="primary" (click)="onEditClick($event, 'client', pc.uuid)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="onDeleteClick($event, pc.uuid, pc.name, 'client')">
|
||||
<button mat-icon-button color="warn" (click)="onDeleteClick($event, pc.uuid, pc.name, 'client')">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { GroupsComponent } from './groups.component';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import { AdvancedSearchComponent } from './components/advanced-search/advanced-search.component';
|
||||
import { ClientTabViewComponent } from './components/client-tab-view/client-tab-view.component';
|
||||
import { OrganizationalUnitTabViewComponent } from './components/organizational-unit-tab-view/organizational-unit-tab-view.component';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
|
||||
describe('GroupsComponent', () => {
|
||||
let component: GroupsComponent;
|
||||
let fixture: ComponentFixture<GroupsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [GroupsComponent, AdvancedSearchComponent, ClientTabViewComponent, OrganizationalUnitTabViewComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
ToastrModule.forRoot(),
|
||||
BrowserAnimationsModule,
|
||||
MatDividerModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatTableModule,
|
||||
MatPaginatorModule,
|
||||
MatTooltipModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatDialogModule,
|
||||
MatSelectModule,
|
||||
MatTabsModule,
|
||||
MatAutocompleteModule,
|
||||
MatListModule,
|
||||
MatCardModule,
|
||||
MatMenuModule,
|
||||
TranslateModule.forRoot(),
|
||||
JoyrideModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call search on ngOnInit', () => {
|
||||
spyOn(component, 'search');
|
||||
component.ngOnInit();
|
||||
expect(component.search).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call getFilters on ngOnInit', () => {
|
||||
spyOn(component, 'getFilters');
|
||||
component.ngOnInit();
|
||||
expect(component.getFilters).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call search method', () => {
|
||||
spyOn(component, 'search');
|
||||
component.search();
|
||||
expect(component.search).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call getFilters method', () => {
|
||||
spyOn(component, 'getFilters');
|
||||
component.getFilters();
|
||||
expect(component.getFilters).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onTabChange method', () => {
|
||||
spyOn(component, 'onTabChange');
|
||||
const event = { index: 2 } as any;
|
||||
component.onTabChange(event);
|
||||
expect(component.onTabChange).toHaveBeenCalledWith(event);
|
||||
});
|
||||
|
||||
it('should call onSelectUnidad method', () => {
|
||||
spyOn(component, 'onSelectUnidad');
|
||||
const unidad = { id: '1', name: 'Test' } as any;
|
||||
component.onSelectUnidad(unidad);
|
||||
expect(component.onSelectUnidad).toHaveBeenCalledWith(unidad);
|
||||
});
|
||||
|
||||
it('should call onSelectChild method', () => {
|
||||
spyOn(component, 'onSelectChild');
|
||||
const child = { id: '1', name: 'Test', type: 'unit' } as any;
|
||||
component.onSelectChild(child);
|
||||
expect(component.onSelectChild).toHaveBeenCalledWith(child);
|
||||
});
|
||||
|
||||
it('should call navigateToBreadcrumb method', () => {
|
||||
spyOn(component, 'navigateToBreadcrumb');
|
||||
component.navigateToBreadcrumb(1);
|
||||
expect(component.navigateToBreadcrumb).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should call loadChildrenAndClients method', () => {
|
||||
spyOn(component, 'loadChildrenAndClients');
|
||||
component.loadChildrenAndClients('1');
|
||||
expect(component.loadChildrenAndClients).toHaveBeenCalledWith('1');
|
||||
});
|
||||
|
||||
it('should call onDeleteClick method', () => {
|
||||
spyOn(component, 'onDeleteClick');
|
||||
const event = new MouseEvent('click');
|
||||
component.onDeleteClick(event, 'uuid', 'name', 'client');
|
||||
expect(component.onDeleteClick).toHaveBeenCalledWith(event, 'uuid', 'name', 'client');
|
||||
});
|
||||
|
||||
it('should call onEditClick method', () => {
|
||||
spyOn(component, 'onEditClick');
|
||||
const event = new MouseEvent('click');
|
||||
component.onEditClick(event, 'client', 'uuid');
|
||||
expect(component.onEditClick).toHaveBeenCalledWith(event, 'client', 'uuid');
|
||||
});
|
||||
|
||||
it('should call onShowClick method', () => {
|
||||
spyOn(component, 'onShowClick');
|
||||
const event = new MouseEvent('click');
|
||||
component.onShowClick(event, { type: 'unit' });
|
||||
expect(component.onShowClick).toHaveBeenCalledWith(event, { type: 'unit' });
|
||||
});
|
||||
|
||||
it('should call onTreeClick method', () => {
|
||||
spyOn(component, 'onTreeClick');
|
||||
const event = new MouseEvent('click');
|
||||
component.onTreeClick(event, { type: 'unit' });
|
||||
expect(component.onTreeClick).toHaveBeenCalledWith(event, { type: 'unit' });
|
||||
});
|
||||
|
||||
it('should call onExecuteCommand method', () => {
|
||||
spyOn(component, 'onExecuteCommand');
|
||||
const event = new MouseEvent('click');
|
||||
component.onExecuteCommand(event, 'child', 'name', 'type');
|
||||
expect(component.onExecuteCommand).toHaveBeenCalledWith(event, 'child', 'name', 'type');
|
||||
});
|
||||
|
||||
it('should call openSnackBar method', () => {
|
||||
spyOn(component, 'openSnackBar');
|
||||
component.openSnackBar(true, 'message');
|
||||
expect(component.openSnackBar).toHaveBeenCalledWith(true, 'message');
|
||||
});
|
||||
|
||||
it('should call openBottomSheet method', () => {
|
||||
spyOn(component, 'openBottomSheet');
|
||||
component.openBottomSheet();
|
||||
expect(component.openBottomSheet).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call roomMap method', () => {
|
||||
spyOn(component, 'roomMap');
|
||||
component.roomMap();
|
||||
expect(component.roomMap).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call applyFilter method', () => {
|
||||
spyOn(component, 'applyFilter');
|
||||
component.applyFilter();
|
||||
expect(component.applyFilter).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onPageChange method', () => {
|
||||
spyOn(component, 'onPageChange');
|
||||
const event = { pageIndex: 1, pageSize: 10 } as any;
|
||||
component.onPageChange(event);
|
||||
expect(component.onPageChange).toHaveBeenCalledWith(event);
|
||||
});
|
||||
|
||||
it('should call saveFilters method', () => {
|
||||
spyOn(component, 'saveFilters');
|
||||
component.saveFilters();
|
||||
expect(component.saveFilters).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call loadSelectedFilter method', () => {
|
||||
spyOn(component, 'loadSelectedFilter');
|
||||
component.loadSelectedFilter(['name', 'uuid']);
|
||||
expect(component.loadSelectedFilter).toHaveBeenCalledWith(['name', 'uuid']);
|
||||
});
|
||||
|
||||
it('should call onCheckboxChange method', () => {
|
||||
spyOn(component, 'onCheckboxChange');
|
||||
const event = { checked: true } as any;
|
||||
component.onCheckboxChange(event, 'name', 'uuid');
|
||||
expect(component.onCheckboxChange).toHaveBeenCalledWith(event, 'name', 'uuid');
|
||||
});
|
||||
|
||||
it('should call toggleSelectAll method', () => {
|
||||
spyOn(component, 'toggleSelectAll');
|
||||
component.toggleSelectAll();
|
||||
expect(component.toggleSelectAll).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call isSelected method', () => {
|
||||
spyOn(component, 'isSelected');
|
||||
component.isSelected('name');
|
||||
expect(component.isSelected).toHaveBeenCalledWith('name');
|
||||
});
|
||||
|
||||
it('should call sendActions method', () => {
|
||||
spyOn(component, 'sendActions');
|
||||
component.sendActions();
|
||||
expect(component.sendActions).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call iniciarTour method', () => {
|
||||
spyOn(component, 'iniciarTour');
|
||||
component.iniciarTour();
|
||||
expect(component.iniciarTour).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -1,17 +1,22 @@
|
|||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
mat-dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
mat-dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
|
|
@ -2,23 +2,16 @@
|
|||
|
||||
<mat-dialog-content>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Unidad Organizativa</mat-label>
|
||||
<mat-select [formControl]="unitControl" (selectionChange)="onUnitChange($event.value)">
|
||||
<mat-label>Seleccione aula</mat-label>
|
||||
<mat-select [formControl]="unitControl" (selectionChange)="loadChildUnits($event.value)">
|
||||
<mat-option *ngFor="let unit of units" [value]="unit.uuid">{{ unit.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Subunidad Organizativa</mat-label>
|
||||
<mat-select [formControl]="childUnitControl" (selectionChange)="onChildUnitChange($event.value)">
|
||||
<mat-option *ngFor="let child of childUnits" [value]="child.uuid">{{ child.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<label>Clientes</label>
|
||||
<div *ngIf="clients.length > 0">
|
||||
<mat-checkbox *ngFor="let client of clients"
|
||||
<mat-checkbox *ngFor="let client of clients"
|
||||
(change)="toggleClientSelection(client.uuid)"
|
||||
[checked]="selectedClients.includes(client.uuid)">
|
||||
{{ client.name }}
|
||||
|
|
|
@ -12,7 +12,6 @@ import { ToastrService } from 'ngx-toastr';
|
|||
export class AddClientsToSubnetComponent implements OnInit {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
units: any[] = [];
|
||||
childUnits: any[] = [];
|
||||
clients: any[] = [];
|
||||
selectedClients: string[] = [];
|
||||
loading: boolean = true;
|
||||
|
@ -32,38 +31,22 @@ export class AddClientsToSubnetComponent implements OnInit {
|
|||
}
|
||||
|
||||
loadUnits() {
|
||||
this.http.get<any>(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=50`).subscribe(
|
||||
this.http.get<any>(`${this.baseUrl}/organizational-units?type=classroom&page=1&itemsPerPage=50`).subscribe(
|
||||
response => {
|
||||
this.units = response['hydra:member'].filter((unit: { type: string; }) => unit.type === 'organizational-unit');
|
||||
this.units = response['hydra:member'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => console.error('Error fetching organizational units:', error)
|
||||
);
|
||||
}
|
||||
|
||||
onUnitChange(unitId: string): void {
|
||||
const unit = this.units.find(unit => unit.uuid === unitId);
|
||||
this.childUnits = unit ? this.getAllChildren(unit) : [];
|
||||
this.clients = [];
|
||||
this.childUnitControl.setValue(null);
|
||||
this.selectedClients = [];
|
||||
}
|
||||
|
||||
getAllChildren(unit: any): any[] {
|
||||
let allChildren = [];
|
||||
if (unit.children && unit.children.length > 0) {
|
||||
for (const child of unit.children) {
|
||||
allChildren.push(child);
|
||||
allChildren = allChildren.concat(this.getAllChildren(child));
|
||||
}
|
||||
}
|
||||
return allChildren;
|
||||
}
|
||||
|
||||
onChildUnitChange(childUnitId: string): void {
|
||||
const childUnit = this.childUnits.find(unit => unit.uuid === childUnitId);
|
||||
this.clients = childUnit && childUnit.clients ? childUnit.clients : [];
|
||||
this.selectedClients = [];
|
||||
loadChildUnits(unitId: string) {
|
||||
this.http.get<any>(`${this.baseUrl}/clients?parent.id${unitId}`).subscribe(
|
||||
response => {
|
||||
this.clients = response['hydra:member'];
|
||||
},
|
||||
error => console.error('Error fetching child units:', error)
|
||||
);
|
||||
}
|
||||
|
||||
toggleClientSelection(clientId: string): void {
|
||||
|
@ -75,18 +58,6 @@ export class AddClientsToSubnetComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
toggleSelectAll(): void {
|
||||
if (this.areAllClientsSelected()) {
|
||||
this.selectedClients = [];
|
||||
} else {
|
||||
this.selectedClients = this.clients.map(client => client.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
areAllClientsSelected(): boolean {
|
||||
return this.selectedClients.length === this.clients.length;
|
||||
}
|
||||
|
||||
save() {
|
||||
this.selectedClients.forEach(clientId => {
|
||||
const postData = { client: `/clients/${clientId}` };
|
||||
|
|
|
@ -38,7 +38,6 @@ table {
|
|||
}
|
||||
|
||||
.header-container {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
@ -58,20 +57,14 @@ table {
|
|||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.example-headers-align .mat-expansion-panel-header-description {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
mat-spinner {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.example-button-row {
|
||||
display: table-cell;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.example-button-row .mat-mdc-button-base {
|
||||
margin: 8px 8px 8px 0;
|
||||
.subnets-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 20px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
|
|
@ -1,23 +1,10 @@
|
|||
<mat-accordion class="example-headers-align">
|
||||
<mat-expansion-panel hideToggle>
|
||||
<mat-expansion-panel-header joyrideStep="serverInfoStep" text="Despliega este contenedor para acceder a la información y opciones de sincronización en el servidor OgDHCP.">
|
||||
<mat-panel-title> Información en servidor ogDHCP </mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<div class="example-button-row">
|
||||
<button mat-flat-button color="primary" (click)="syncSubnets()" joyrideStep="syncDbStep" text="Sincroniza la base de datos del servidor OgDHCP para actualizar la información de las subredes."> Sincronizar base de datos</button>
|
||||
</div>
|
||||
<div class="example-button-row">
|
||||
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()" joyrideStep="viewInfoStep" text="Haz clic para ver información detallada de las subredes en el servidor.">Ver Información</button>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
|
||||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" i18n="@@subnetsTitle" joyrideStep="titleStep" text="Desde aquí puedes gestionar las subredes configuradas en el servidor OgDHCP.">Administrar Subredes</h2>
|
||||
<div class="subnets-button-row">
|
||||
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()" joyrideStep="viewInfoStep" text="Haz clic para ver información detallada de las subredes en el servidor.">Ver Información</button>
|
||||
<button mat-flat-button color="primary" (click)="addSubnet()" joyrideStep="addSubnetStep" text="Haz clic para añadir una nueva subred.">Añadir Subred</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -52,8 +39,8 @@
|
|||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="Visualiza y administra las subredes listadas según los filtros aplicados.">
|
||||
<mat-spinner *ngIf="loading"></mat-spinner>
|
||||
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="Visualiza y administra las subredes listadas según los filtros aplicados.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let subnet">
|
||||
|
@ -84,15 +71,8 @@
|
|||
<button mat-icon-button color="primary" (click)="editSubnet(subnet)" i18n="@@editSubnet">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="primary" (click)="addClientsToSubnet(subnet)"><mat-icon>computer</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="toggleAction(subnet, 'delete')"><mat-icon>delete</mat-icon></button>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item [disabled]="subnet.synchronized" (click)="toggleAction(subnet, 'post')">Crear en og-dhcp</button>
|
||||
<button mat-menu-item (click)="addClientsToSubnet(subnet)">Añadir cliente</button>
|
||||
<button mat-menu-item (click)="toggleAction(subnet, 'put')">Actualizar datos en og-dhcp</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ import { MatDialog } from '@angular/material/dialog';
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { of } from 'rxjs';
|
||||
import { MatAccordion, MatExpansionPanel, MatExpansionPanelHeader, MatExpansionPanelTitle, MatExpansionPanelDescription } from '@angular/material/expansion';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
|
@ -28,22 +28,18 @@ describe('OgDhcpSubnetsComponent', () => {
|
|||
mockDialog = jasmine.createSpyObj('MatDialog', ['open']);
|
||||
mockHttpClient = jasmine.createSpyObj('HttpClient', ['get', 'post', 'put', 'delete']);
|
||||
mockToastrService = jasmine.createSpyObj('ToastrService', ['success', 'error']);
|
||||
|
||||
mockHttpClient.get.and.returnValue(of([]));
|
||||
mockHttpClient.get.and.returnValue(of({ 'hydra:member': [], 'hydra:totalItems': 0 }));
|
||||
mockHttpClient.post.and.returnValue(of({}));
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [OgDhcpSubnetsComponent],
|
||||
imports: [
|
||||
MatAccordion,
|
||||
MatExpansionPanel,
|
||||
MatExpansionPanelHeader,
|
||||
MatExpansionPanelTitle,
|
||||
MatExpansionPanelDescription,
|
||||
MatIcon,
|
||||
MatDivider,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule,
|
||||
MatPaginatorModule,
|
||||
imports: [
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
MatDividerModule,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule,
|
||||
MatPaginatorModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
MatInputModule,
|
||||
|
@ -54,8 +50,8 @@ describe('OgDhcpSubnetsComponent', () => {
|
|||
providers: [
|
||||
{ provide: MatDialog, useValue: mockDialog },
|
||||
{ provide: HttpClient, useValue: mockHttpClient },
|
||||
{ provide: ToastrService, useValue: mockToastrService }
|
||||
]
|
||||
{ provide: ToastrService, useValue: mockToastrService },
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
@ -69,4 +65,10 @@ describe('OgDhcpSubnetsComponent', () => {
|
|||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call syncSubnets and handle success', () => {
|
||||
component.syncSubnets();
|
||||
expect(mockHttpClient.post).toHaveBeenCalledWith(`${component.baseUrl}/subnets/sync`, {});
|
||||
expect(mockToastrService.success).toHaveBeenCalledWith('Sincronización con componente DHCP exitosa');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -35,7 +35,7 @@ export interface Subnet {
|
|||
})
|
||||
export class OgDhcpSubnetsComponent {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
displayedColumns: string[] = ['id', 'name', 'netmask', 'ipAddress', 'nextServer', 'bootFileName', 'synchronized', 'serverId', 'clients', 'actions'];
|
||||
displayedColumns: string[] = ['id', 'name', 'netmask', 'ipAddress', 'synchronized', 'serverId', 'clients', 'actions'];
|
||||
dataSource = new MatTableDataSource<Subnet>([]);
|
||||
length = 0;
|
||||
itemsPerPage: number = 10;
|
||||
|
@ -43,6 +43,7 @@ export class OgDhcpSubnetsComponent {
|
|||
filters: { [key: string]: string } = {};
|
||||
pageSizeOptions: number[] = [5, 10, 20];
|
||||
alertMessage: string | null = null;
|
||||
loading:boolean = false;
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator | undefined;
|
||||
|
||||
|
@ -51,8 +52,6 @@ export class OgDhcpSubnetsComponent {
|
|||
{ columnDef: 'name', header: 'Name', cell: (subnet: Subnet) => subnet.name },
|
||||
{ columnDef: 'netmask', header: 'Netmask', cell: (subnet: Subnet) => subnet.netmask },
|
||||
{ columnDef: 'ipAddress', header: 'IP Address', cell: (subnet: Subnet) => subnet.ipAddress },
|
||||
{ columnDef: 'nextServer', header: 'Next Server', cell: (subnet: Subnet) => subnet.nextServer },
|
||||
{ columnDef: 'bootFileName', header: 'Boot File Name', cell: (subnet: Subnet) => subnet.bootFileName },
|
||||
{ columnDef: 'synchronized', header: 'Sincronizado', cell: (subnet: Subnet) => `${subnet.synchronized}` },
|
||||
{ columnDef: 'serverId', header: 'Id Servidor DHCP', cell: (subnet: Subnet) => subnet.serverId },
|
||||
{ columnDef: 'clients', header: 'Lista de clientes', cell: (subnet: Subnet) => `${subnet.clients}` },
|
||||
|
@ -64,8 +63,11 @@ export class OgDhcpSubnetsComponent {
|
|||
private joyrideService: JoyrideService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.loading = true;
|
||||
this.loadSubnets();
|
||||
this.loadAlert()
|
||||
this.syncSubnets()
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
loadSubnets() {
|
||||
|
@ -87,7 +89,7 @@ export class OgDhcpSubnetsComponent {
|
|||
syncSubnets() {
|
||||
this.http.post(`${this.apiUrl}/sync`, {})
|
||||
.subscribe(response => {
|
||||
this.toastService.success('Sincronización completada');
|
||||
this.toastService.success('Sincronización con componente DHCP exitosa');
|
||||
this.loadSubnets()
|
||||
}, error => {
|
||||
console.error('Error al sincronizar', error);
|
||||
|
@ -224,9 +226,6 @@ export class OgDhcpSubnetsComponent {
|
|||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'serverInfoStep',
|
||||
'syncDbStep',
|
||||
'viewInfoStep',
|
||||
'titleStep',
|
||||
'addSubnetStep',
|
||||
'searchNameStep',
|
||||
|
|
Loading…
Reference in New Issue