refs #1558. Add client/subnet new UX modal
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details

deb-pkg
Manuel Aranda Rosales 2025-02-27 11:05:26 +01:00
parent bae2069661
commit b29a3f58f1
20 changed files with 243 additions and 156 deletions

View File

@ -27,6 +27,14 @@
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Vista tarjetas</mat-label>
<mat-select formControlName="groupsView" required>
<mat-option *ngFor="let option of views" [value]="option.value" >
{{ option.name }}
</mat-option>
</mat-select>
</mat-form-field>
</form> </form>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions class="action-container"> <mat-dialog-actions class="action-container">

View File

@ -24,6 +24,11 @@ export class AddUserModalComponent implements OnInit {
organizationalUnits: any[] = []; organizationalUnits: any[] = [];
userId: string | null = null; userId: string | null = null;
protected views = [
{value: 'card', name: 'Tarjetas'},
{value: 'list', name: 'Listado'},
];
constructor( constructor(
public dialogRef: MatDialogRef<AddUserModalComponent>, public dialogRef: MatDialogRef<AddUserModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: any,
@ -36,6 +41,7 @@ export class AddUserModalComponent implements OnInit {
username: ['', Validators.required], username: ['', Validators.required],
password: ['', Validators.required], password: ['', Validators.required],
role: ['', Validators.required], role: ['', Validators.required],
groupsView: ['card', Validators.required],
organizationalUnits: [[]] organizationalUnits: [[]]
}); });
} }
@ -55,15 +61,14 @@ export class AddUserModalComponent implements OnInit {
load(): void { load(): void {
this.dataService.getUser(this.data).subscribe({ this.dataService.getUser(this.data).subscribe({
next: (response) => { next: (response) => {
console.log(response);
const organizationalUnitIds = response.allowedOrganizationalUnits.map((unit: any) => unit['@id']); const organizationalUnitIds = response.allowedOrganizationalUnits.map((unit: any) => unit['@id']);
// Patch the values to the form
this.userForm.patchValue({ this.userForm.patchValue({
username: response.username, username: response.username,
role: response.userGroups[0]['@id'], role: response.userGroups.length > 0 ? response.userGroups[0]['@id'] : null,
organizationalUnits: organizationalUnitIds organizationalUnits: organizationalUnitIds,
groupsView: response.groupsView
}); });
this.userId = response['@id']; this.userId = response['@id'];
@ -85,7 +90,8 @@ export class AddUserModalComponent implements OnInit {
allowedOrganizationalUnits: this.userForm.value.organizationalUnit, allowedOrganizationalUnits: this.userForm.value.organizationalUnit,
password: this.userForm.value.password, password: this.userForm.value.password,
enabled: true, enabled: true,
userGroups: [this.userForm.value.role ] userGroups: [this.userForm.value.role ],
groupsView: this.userForm.value.groupsView
}; };
if (this.userId) { if (this.userId) {

View File

@ -32,6 +32,11 @@ export class UsersComponent implements OnInit {
header: 'Nombre de Usuario', header: 'Nombre de Usuario',
cell: (user: any) => `${user.username}` cell: (user: any) => `${user.username}`
}, },
{
columnDef: 'groupsView',
header: 'Vista de Grupos',
cell: (user: any) => `${user.groupsView}`
},
{ {
columnDef: 'allowedOrganizationalUnits', columnDef: 'allowedOrganizationalUnits',
header: 'Unidades Organizacionales Permitidas', header: 'Unidades Organizacionales Permitidas',

View File

@ -27,3 +27,16 @@ mat-dialog-actions {
gap: 1em; gap: 1em;
padding: 1.5em; padding: 1.5em;
} }
.clients-grid {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3 columnas */
gap: 5px; /* Espaciado entre elementos */
}
.checkbox-group label {
font-weight: bold;
display: block;
margin-bottom: 10px;
}

View File

@ -1,3 +1,5 @@
<app-loading [isLoading]="loading"></app-loading>
<h2 mat-dialog-title>Añade clientes a {{data.subnetName}}</h2> <h2 mat-dialog-title>Añade clientes a {{data.subnetName}}</h2>
<mat-dialog-content> <mat-dialog-content>
@ -10,7 +12,12 @@
<div class="checkbox-group"> <div class="checkbox-group">
<label>Clientes</label> <label>Clientes</label>
<div *ngIf="clients.length > 0">
<button class="action-button" (click)="toggleSelectAll()" [disabled]="clients.length === 0">
{{ allSelected ? 'Deseleccionar todos' : 'Seleccionar todos' }}
</button>
<div *ngIf="clients.length > 0" class="clients-grid">
<mat-checkbox *ngFor="let client of clients" <mat-checkbox *ngFor="let client of clients"
(change)="toggleClientSelection(client.uuid)" (change)="toggleClientSelection(client.uuid)"
[checked]="selectedClients.includes(client.uuid)"> [checked]="selectedClients.includes(client.uuid)">

View File

@ -18,6 +18,7 @@ export class AddClientsToSubnetComponent implements OnInit {
loading: boolean = true; loading: boolean = true;
unitControl = new FormControl(); unitControl = new FormControl();
childUnitControl = new FormControl(); childUnitControl = new FormControl();
allSelected: boolean = false;
constructor( constructor(
private http: HttpClient, private http: HttpClient,
@ -43,7 +44,7 @@ export class AddClientsToSubnetComponent implements OnInit {
} }
loadChildUnits(event: any) { loadChildUnits(event: any) {
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${event.value.id}`).subscribe( this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${event.value.id}&exists[subnet]=false`).subscribe(
response => { response => {
this.clients = response['hydra:member']; this.clients = response['hydra:member'];
}, },
@ -51,37 +52,49 @@ export class AddClientsToSubnetComponent implements OnInit {
); );
} }
toggleClientSelection(clientId: string): void {
const index = this.selectedClients.indexOf(clientId);
if (index >= 0) {
this.selectedClients.splice(index, 1);
} else {
this.selectedClients.push(clientId);
}
}
save() { save() {
this.loading = true;
const postData = { clients: this.selectedClients.map(clientId => `/clients/${clientId}`) }; const postData = { clients: this.selectedClients.map(clientId => `/clients/${clientId}`) };
this.http.post(`${this.baseUrl}/og-dhcp/server/${this.data.subnetUuid}/post-host`, postData).subscribe( this.http.post(`${this.baseUrl}/og-dhcp/server/${this.data.subnetUuid}/post-host`, postData).subscribe(
(response: any) => { (response: any) => {
this.dialog.open(OperationResultDialogComponent, { this.dialog.open(OperationResultDialogComponent, {
width: '600px', width: '800px',
data: { data: {
success: response.success || [], success: response.success || [],
errors: response.errors || [] errors: response.errors || []
} }
}); });
this.loading = false;
this.dialogRef.close(this.selectedClients); this.dialogRef.close(this.selectedClients);
}, },
error => { error => {
console.error(`Error al asignar el cliente:`, error);
this.toastService.error(`Error al asignar el cliente: ${error.error['hydra:description']}`); this.toastService.error(`Error al asignar el cliente: ${error.error['hydra:description']}`);
} }
); );
} }
toggleSelectAll() {
if (this.allSelected) {
this.selectedClients = [];
} else {
this.selectedClients = this.clients.map(client => client.uuid);
}
this.allSelected = !this.allSelected;
}
toggleClientSelection(uuid: string) {
const index = this.selectedClients.indexOf(uuid);
if (index === -1) {
this.selectedClients.push(uuid);
} else {
this.selectedClients.splice(index, 1);
}
this.allSelected = this.selectedClients.length === this.clients.length;
}
close() { close() {
this.dialogRef.close(); this.dialogRef.close();
} }

View File

@ -40,3 +40,10 @@ form {
gap: 1em; gap: 1em;
padding: 1.5em; padding: 1.5em;
} }
.step-title {
font-family: 'Roboto', sans-serif;
color: #3f51b5;
margin: 40px 0 15px 0;
display: block;
}

View File

@ -1,8 +1,6 @@
<h2 mat-dialog-title>{{ isEditMode ? 'Editar' : 'Añadir' }} subred</h2> <h2 mat-dialog-title>{{ isEditMode ? 'Editar' : 'Añadir' }} subred</h2>
<mat-dialog-content> <mat-dialog-content>
<mat-tab-group>
<mat-tab label="Subred">
<div class="spacing-container"> <div class="spacing-container">
<mat-form-field appearance="fill" class="full-width"> <mat-form-field appearance="fill" class="full-width">
<mat-label>Nombre</mat-label> <mat-label>Nombre</mat-label>
@ -17,12 +15,8 @@
<input matInput [(ngModel)]="ipAddress" placeholder="Dirección IP" required> <input matInput [(ngModel)]="ipAddress" placeholder="Dirección IP" required>
</mat-form-field> </mat-form-field>
<!-- Parámetros Avanzados --> <mat-divider></mat-divider>
<mat-expansion-panel> <span class="step-title">Parámetros avanzados</span>
<mat-expansion-panel-header>
<mat-panel-title>Parámetros avanzados</mat-panel-title>
</mat-expansion-panel-header>
<mat-form-field appearance="fill" class="full-width"> <mat-form-field appearance="fill" class="full-width">
<mat-label>Next Server</mat-label> <mat-label>Next Server</mat-label>
<input matInput [(ngModel)]="nextServer" placeholder="Next Server"> <input matInput [(ngModel)]="nextServer" placeholder="Next Server">
@ -35,31 +29,7 @@
<mat-label>Router</mat-label> <mat-label>Router</mat-label>
<input matInput [(ngModel)]="router" placeholder="Router"> <input matInput [(ngModel)]="router" placeholder="Router">
</mat-form-field> </mat-form-field>
</mat-expansion-panel>
</div> </div>
</mat-tab>
<mat-tab *ngIf="isEditMode" label="Clientes">
<mat-list>
<ng-container *ngFor="let client of clients">
<mat-list-item>
<div class="list-item-content">
<mat-icon matListItemIcon>computer</mat-icon>
<div class="text-content">
<div matListItemTitle>{{ client.name }}</div>
<div matListItemLine>{{ client.mac }}</div>
</div>
<div class="icon-container">
<button mat-icon-button color="warn" class="right-icon" (click)="deleteClient(client)">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</mat-list-item>
</ng-container>
</mat-list>
</mat-tab>
</mat-tab-group>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions class="action-container"> <mat-dialog-actions class="action-container">

View File

@ -18,9 +18,7 @@ export class CreateSubnetComponent implements OnInit {
nextServer: string = ''; nextServer: string = '';
bootFileName: string = ''; bootFileName: string = '';
router: string = ''; router: string = '';
syncronized: boolean = false;
serverId: number = 0; serverId: number = 0;
clients: any[] = [];
isEditMode: boolean = false; isEditMode: boolean = false;
constructor( constructor(
@ -46,9 +44,7 @@ export class CreateSubnetComponent implements OnInit {
this.nextServer = this.data.nextServer; this.nextServer = this.data.nextServer;
this.bootFileName = this.data.bootFileName; this.bootFileName = this.data.bootFileName;
this.router = this.data.router; this.router = this.data.router;
this.syncronized = this.data.syncronized;
this.serverId = this.data.serverId; this.serverId = this.data.serverId;
this.clients = this.data.clients
} }
onNoClick(): void { onNoClick(): void {
@ -69,12 +65,10 @@ export class CreateSubnetComponent implements OnInit {
this.http.post(`${this.baseUrl}/subnets`, payload) this.http.post(`${this.baseUrl}/subnets`, payload)
.subscribe({ .subscribe({
next: (response) => { next: (response) => {
console.log('Success:', response);
this.toastService.success('Configuración de red añadida exitosamente'); this.toastService.success('Configuración de red añadida exitosamente');
this.dialogRef.close(); this.dialogRef.close();
}, },
error: (error) => { error: (error) => {
console.error('Error:', error);
this.toastService.error(error.error['hydra:description']); this.toastService.error(error.error['hydra:description']);
} }
}); });
@ -82,35 +76,13 @@ export class CreateSubnetComponent implements OnInit {
this.http.patch(`${this.baseUrl}/subnets/${this.subnetId}`, payload) this.http.patch(`${this.baseUrl}/subnets/${this.subnetId}`, payload)
.subscribe({ .subscribe({
next: (response) => { next: (response) => {
console.log('Success:', response);
this.toastService.success('Configuración de red actualizada exitosamente'); this.toastService.success('Configuración de red actualizada exitosamente');
this.dialogRef.close(); this.dialogRef.close();
}, },
error: (error) => { error: (error) => {
console.error('Error:', error);
this.toastService.error(error.error['hydra:description']); this.toastService.error(error.error['hydra:description']);
} }
}); });
} }
} }
deleteClient(client: any): void {
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: client.name }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.http.delete(`${this.baseUrl}/og-dhcp/server/${this.subnetId}/delete-host/${client.uuid}`, {}).subscribe({
next: () => {
this.toastService.success('Cliente eliminado exitosamente');
this.dialogRef.close();
},
error: (error) => {
this.toastService.error(error.error['hydra:description']);
}
});
}})
}
} }

View File

@ -1,3 +1,5 @@
<app-loading [isLoading]="loading"></app-loading>
<div class="header-container"> <div class="header-container">
<button mat-icon-button color="primary" (click)="iniciarTour()"> <button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon> <mat-icon>help</mat-icon>
@ -36,13 +38,6 @@
<mat-icon matSuffix>search</mat-icon> <mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint> <mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchBootFileStep"
text="Busca subredes según el nombre del archivo de arranque.">
<mat-label i18n="@@searchLabel">Buscar Boot file name</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['bootFileName']" i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
</mat-form-field>
</div> </div>
<app-loading [isLoading]="loading"></app-loading> <app-loading [isLoading]="loading"></app-loading>

View File

@ -35,7 +35,7 @@ export interface Subnet {
templateUrl: './og-dhcp-subnets.component.html', templateUrl: './og-dhcp-subnets.component.html',
styleUrls: ['./og-dhcp-subnets.component.css'] styleUrls: ['./og-dhcp-subnets.component.css']
}) })
export class OgDhcpSubnetsComponent { export class OgDhcpSubnetsComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
displayedColumns: string[] = ['id', 'name', 'netmask', 'ipAddress', 'synchronized', 'serverId', 'clients', 'actions']; displayedColumns: string[] = ['id', 'name', 'netmask', 'ipAddress', 'synchronized', 'serverId', 'clients', 'actions'];
dataSource = new MatTableDataSource<Subnet>([]); dataSource = new MatTableDataSource<Subnet>([]);
@ -79,7 +79,7 @@ export class OgDhcpSubnetsComponent {
this.length = response['hydra:totalItems']; this.length = response['hydra:totalItems'];
}, },
error: error => { error: error => {
console.error('Error al cargar plantillas PXE:', error); this.toastService.error(error.error['hydra:description']);
} }
}); });
@ -94,7 +94,6 @@ export class OgDhcpSubnetsComponent {
this.toastService.success('Sincronización con componente DHCP exitosa'); this.toastService.success('Sincronización con componente DHCP exitosa');
this.loadSubnets() this.loadSubnets()
}, error => { }, error => {
console.error('Error al sincronizar', error);
this.toastService.error('Error al sincronizar'); this.toastService.error('Error al sincronizar');
}); });
} }
@ -214,7 +213,7 @@ export class OgDhcpSubnetsComponent {
height: '100vh', height: '100vh',
maxWidth: '100vw', maxWidth: '100vw',
maxHeight: '100vh', maxHeight: '100vh',
data: { subnetId: subnet.id, subnetName: subnet.name } data: { subnetId: subnet.id, subnetName: subnet.name, subnetUuid: subnet.uuid }
}); });
dialogRef.afterClosed().subscribe(result => { dialogRef.afterClosed().subscribe(result => {
@ -224,7 +223,7 @@ export class OgDhcpSubnetsComponent {
addClientsToSubnet(subnet: Subnet) { addClientsToSubnet(subnet: Subnet) {
const dialogRef = this.dialog.open(AddClientsToSubnetComponent, { const dialogRef = this.dialog.open(AddClientsToSubnetComponent, {
width: '600px', width: '800px',
data: { subnetUuid: subnet.uuid, subnetName: subnet.name } data: { subnetUuid: subnet.uuid, subnetName: subnet.name }
}); });

View File

@ -1,14 +1,34 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import {MAT_DIALOG_DATA } from '@angular/material/dialog';
import { OperationResultDialogComponent } from './operation-result-dialog.component'; import { OperationResultDialogComponent } from './operation-result-dialog.component';
import {ShowClientsComponent} from "../show-clients/show-clients.component";
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";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {FormsModule} from "@angular/forms";
import {MatInputModule} from "@angular/material/input";
import {MatDialog, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
import {MatTableModule} from "@angular/material/table";
import {TranslateModule} from "@ngx-translate/core";
import {JoyrideModule} from "ngx-joyride";
import {HttpClient} from "@angular/common/http";
import {ToastrService} from "ngx-toastr";
import {LoadingComponent} from "../../../shared/loading/loading.component";
describe('OperationResultDialogComponent', () => { describe('OperationResultDialogComponent', () => {
let component: OperationResultDialogComponent; let component: ShowClientsComponent;
let fixture: ComponentFixture<OperationResultDialogComponent>; let fixture: ComponentFixture<ShowClientsComponent>;
let mockDialog: jasmine.SpyObj<MatDialog>;
let mockHttpClient: jasmine.SpyObj<HttpClient>;
let mockToastrService: jasmine.SpyObj<ToastrService>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [OperationResultDialogComponent], declarations: [OperationResultDialogComponent, LoadingComponent],
imports: [MatDialogModule], imports: [MatDialogModule],
providers: [ providers: [
{ provide: MatDialogRef, useValue: {} }, { provide: MatDialogRef, useValue: {} },
@ -17,7 +37,36 @@ describe('OperationResultDialogComponent', () => {
}) })
.compileComponents(); .compileComponents();
fixture = TestBed.createComponent(OperationResultDialogComponent); await TestBed.configureTestingModule({
declarations: [ShowClientsComponent],
imports: [
MatExpansionModule,
MatIconModule,
MatDividerModule,
MatFormFieldModule,
MatSelectModule,
MatPaginatorModule,
BrowserAnimationsModule,
FormsModule,
MatInputModule,
MatDialogModule,
MatTableModule,
TranslateModule.forRoot(),
JoyrideModule.forRoot(),
],
providers: [
{ provide: MatDialog, useValue: mockDialog },
{ provide: HttpClient, useValue: mockHttpClient },
{ provide: ToastrService, useValue: mockToastrService },
{
provide: MatDialogRef,
useValue: {}
},
],
})
.compileComponents();
fixture = TestBed.createComponent(ShowClientsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -7,13 +7,6 @@ import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
template: ` template: `
<h2 mat-dialog-title>Resultado de la operación</h2> <h2 mat-dialog-title>Resultado de la operación</h2>
<mat-dialog-content> <mat-dialog-content>
<div class="success-box" *ngIf="data.success.length > 0">
<h3>Éxitos</h3>
<ul>
<li *ngFor="let success of data.success">{{ success.client }} </li>
</ul>
</div>
<div class="error-box" *ngIf="data.errors.length > 0"> <div class="error-box" *ngIf="data.errors.length > 0">
<h3>Errores</h3> <h3>Errores</h3>
<ul> <ul>
@ -22,6 +15,12 @@ import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
</li> </li>
</ul> </ul>
</div> </div>
<div class="success-box" *ngIf="data.success.length > 0">
<h3>Éxitos</h3>
<ul>
<li *ngFor="let success of data.success">{{ success.client }} </li>
</ul>
</div>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end"> <mat-dialog-actions align="end">
<button mat-button color="primary" (click)="close()">Cerrar</button> <button mat-button color="primary" (click)="close()">Cerrar</button>

View File

@ -40,19 +40,6 @@ form {
gap: 1em; gap: 1em;
padding: 1.5em; padding: 1.5em;
} }
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 10px;
border-bottom: 1px solid #ddd;
}
.header-container-title {
flex-grow: 1;
text-align: left;
margin-left: 1em;
}
.lists-container { .lists-container {
padding: 16px; padding: 16px;
@ -68,7 +55,12 @@ form {
} }
.search-string { .search-string {
flex: 2; flex: 1;
padding: 5px;
}
.search-select {
flex: 1;
padding: 5px; padding: 5px;
} }

View File

@ -1,6 +1,6 @@
<app-loading [isLoading]="loading"></app-loading> <app-loading [isLoading]="loading"></app-loading>
<h2 mat-dialog-title>Buscar clientes en {{data.subnetName}}</h2> <h2 mat-dialog-title>Gestionar clientes en {{data.subnetName}}</h2>
<mat-dialog-content> <mat-dialog-content>
<div class="search-container"> <div class="search-container">
@ -38,6 +38,17 @@
</button> </button>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint> <mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="form-field search-select" >
<mat-label>Estados</mat-label>
<mat-select [(ngModel)]="filters['status']" (selectionChange)="loadData()">
<mat-option *ngFor="let option of status" [value]="option.value" >
{{ option.name }}
</mat-option>
</mat-select>
<button *ngIf="filters['status']" mat-icon-button matSuffix aria-label="Clear tree search" (click)="filters['status'] = ''; loadData()">
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
</div> </div>
<app-loading [isLoading]="loading"></app-loading> <app-loading [isLoading]="loading"></app-loading>
@ -54,12 +65,25 @@
{{ client.ogLive?.date | date }} {{ client.ogLive?.date | date }}
</ng-container> </ng-container>
<ng-container *ngIf="column.columnDef !== 'status' && column.columnDef !== 'ogLive'"> <ng-container *ngIf="column.columnDef === 'organizationalUnit'">
{{ client.organizationalUnit?.path }}
</ng-container>
<ng-container *ngIf="column.columnDef !== 'status' && column.columnDef !== 'ogLive' && column.columnDef !== 'organizationalUnit'">
{{ column.cell(client) }} {{ column.cell(client) }}
</ng-container> </ng-container>
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let client" style="text-align: center;">
<button mat-icon-button color="warn" (click)="deleteClient(client)">
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table> </table>
@ -70,7 +94,6 @@
(page)="onPageChange($event)"> (page)="onPageChange($event)">
</mat-paginator> </mat-paginator>
</div> </div>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions class="action-container"> <mat-dialog-actions class="action-container">

View File

@ -29,8 +29,6 @@ describe('ShowClientsComponent', () => {
mockDialog = jasmine.createSpyObj('MatDialog', ['open']); mockDialog = jasmine.createSpyObj('MatDialog', ['open']);
mockHttpClient = jasmine.createSpyObj('HttpClient', ['get', 'post', 'put', 'delete']); mockHttpClient = jasmine.createSpyObj('HttpClient', ['get', 'post', 'put', 'delete']);
mockToastrService = jasmine.createSpyObj('ToastrService', ['success', 'error']); mockToastrService = jasmine.createSpyObj('ToastrService', ['success', 'error']);
mockHttpClient.get.and.returnValue(of({ 'hydra:member': [], 'hydra:totalItems': 0 }));
mockHttpClient.post.and.returnValue(of({}));
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ShowClientsComponent, LoadingComponent], declarations: [ShowClientsComponent, LoadingComponent],

View File

@ -3,8 +3,8 @@ import { ToastrService } from "ngx-toastr";
import { HttpClient } from "@angular/common/http"; import { HttpClient } from "@angular/common/http";
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog"; import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog";
import { MatTableDataSource } from "@angular/material/table"; import { MatTableDataSource } from "@angular/material/table";
import { Subnet } from "../og-dhcp-subnets.component";
import { Client } from "../../groups/model/model"; import { Client } from "../../groups/model/model";
import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/delete-modal.component";
@Component({ @Component({
selector: 'app-show-clients', selector: 'app-show-clients',
@ -21,16 +21,27 @@ export class ShowClientsComponent implements OnInit {
loading: boolean = false; loading: boolean = false;
filters: { [key: string]: string } = {}; filters: { [key: string]: string } = {};
protected status = [
{value: 'off', name: 'Apagado'},
{value: 'initializing', name: 'Inicializando'},
{value: 'og-live', name: 'Og Live'},
{value: 'linux', name: 'Linux'},
{value: 'linux-session', name: 'Linux Session'},
{value: 'windows', name: 'Windows'},
{value: 'mac', name: 'Mac'},
];
columns = [ columns = [
{ columnDef: 'id', header: 'ID', cell: (client: Client) => client.id }, { columnDef: 'id', header: 'ID', cell: (client: Client) => client.id },
{ columnDef: 'status', header: 'Estado', cell: (client: Client) => client.status }, { columnDef: 'status', header: 'Estado', cell: (client: Client) => client.status },
{ columnDef: 'name', header: 'Name', cell: (client: Client) => client.name }, { columnDef: 'name', header: 'Name', cell: (client: Client) => client.name },
{ columnDef: 'ip', header: 'Ip', cell: (client: Client) => client.ip }, { columnDef: 'ip', header: 'Ip', cell: (client: Client) => client.ip },
{ columnDef: 'mac', header: 'Mac', cell: (client: Client) => client.mac }, { columnDef: 'mac', header: 'Mac', cell: (client: Client) => client.mac },
{ columnDef: 'organizationalUnit', header: 'Ruta', cell: (client: Client) => client.organizationalUnit },
{ columnDef: 'ogLive', header: 'OgLive', cell: (client: Client) => client.ogLive?.date }, { columnDef: 'ogLive', header: 'OgLive', cell: (client: Client) => client.ogLive?.date },
]; ];
displayedColumns: string[] = ['id', 'status', 'name', 'ip', 'mac', 'ogLive']; displayedColumns: string[] = ['id', 'status', 'name', 'ip', 'mac', 'organizationalUnit', 'ogLive', 'actions'];
constructor( constructor(
private toastService: ToastrService, private toastService: ToastrService,
@ -61,6 +72,26 @@ export class ShowClientsComponent implements OnInit {
console.log(this.dataSource.data) console.log(this.dataSource.data)
} }
deleteClient(client: any): void {
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: client.name }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.http.delete(`${this.baseUrl}/og-dhcp/server/${this.data.subnetUuid}/delete-host/${client.uuid}`, {}).subscribe({
next: () => {
this.toastService.success('Cliente eliminado exitosamente de la red');
this.loadData()
},
error: (error) => {
this.toastService.error(error.error['hydra:description']);
}
});
}})
}
onNoClick(): void { onNoClick(): void {
this.dialogRef.close(); this.dialogRef.close();
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B