Merge branch 'develop' for 0.6.2 release
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
testing/ogGui-multibranch/pipeline/tag This commit is unstable Details

pull/10/head opengnsysy_devel-0.0.11
Alvaro Puente Mella 2024-11-22 13:21:03 +01:00
commit 1540156ffb
12 changed files with 451 additions and 186 deletions

View File

@ -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 @@
---

View File

@ -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');
});
});

View File

@ -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;
}

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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;
}

View File

@ -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 }}

View File

@ -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}` };

View File

@ -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;
}

View File

@ -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>

View File

@ -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');
});
});

View File

@ -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',