refs #614. Integration DHCP

develop-jenkins
Manuel Aranda Rosales 2024-10-14 13:17:47 +02:00
parent b78be472e1
commit d0b6da5616
25 changed files with 703 additions and 165 deletions

View File

@ -0,0 +1,14 @@
FROM node:22.1.0
WORKDIR /app
RUN npm install -g npm@latest
RUN npm install -g @angular/cli@^12.0.0
COPY . /app
RUN npm install
EXPOSE 4200
CMD ["ng", "serve", "--host", "0.0.0.0", "--disable-host-check"]

View File

@ -20,6 +20,7 @@ import { CommandsComponent } from './components/commands/main-commands/commands.
import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component';
import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component';
import { TaskLogsComponent } from './components/commands/commands-task/task-logs/task-logs.component';
import { StatusComponent } from "./components/ogdhcp/og-dhcp-subnets/status/status.component";
const routes: Routes = [
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
{
@ -36,7 +37,8 @@ const routes: Routes = [
{ path: 'pxe-boot-file', component: PxeBootFilesComponent },
{ path: 'ogboot-status', component: OgbootStatusComponent },
{ path: 'dhcp', component: OgdhcpComponent },
{ path: 'dhcp-subnets', component: OgDhcpSubnetsComponent },
{ path: 'subnets', component: OgDhcpSubnetsComponent },
{ path: 'ogdhcp-status', component: StatusComponent },
{ path: 'commands', component: CommandsComponent },
{ path: 'commands-groups', component: CommandsGroupsComponent },
{ path: 'commands-task', component: CommandsTaskComponent },

View File

@ -100,6 +100,8 @@ import { ClientTabViewComponent } from './components/groups/components/client-ta
import { AdvancedSearchComponent } from './components/groups/components/advanced-search/advanced-search.component';
import { TaskLogsComponent } from './components/commands/commands-task/task-logs/task-logs.component';
import { OrganizationalUnitTabViewComponent } from './components/groups/components/organizational-unit-tab-view/organizational-unit-tab-view.component';
import { ServerInfoDialogComponent } from './components/ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component';
import { StatusComponent } from './components/ogdhcp/og-dhcp-subnets/status/status.component';
@NgModule({
declarations: [
AppComponent,
@ -156,7 +158,9 @@ import { OrganizationalUnitTabViewComponent } from './components/groups/componen
ClientTabViewComponent,
AdvancedSearchComponent,
TaskLogsComponent,
OrganizationalUnitTabViewComponent
OrganizationalUnitTabViewComponent,
ServerInfoDialogComponent,
StatusComponent
],
bootstrap: [AppComponent],
imports: [BrowserModule,

View File

@ -58,6 +58,6 @@
</mat-tab>
</mat-tab-group>
</mat-dialog-content>
<mat-dialog-actions>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close (click)="onNoClick()" i18n="@@close-button">Cerrar</button>
</mat-dialog-actions>

View File

@ -26,12 +26,12 @@ export class ClientViewComponent {
networkData = [
{property: 'Menú', value: this.data.client.menu ? this.data.client.menu.description : ''},
{property: 'Perfil hardware', value: this.data.client.hardwareProfile ? this.data.client.hardwareProfile.description : ''},
{property: 'Subred', value: this.data.client.subnet},
{property: 'OGlive', value: ''},
{property: 'Autoexec', value: ''},
{property: 'Repositorio', value: ''},
{property: 'Validacion', value: ''},
{property: 'Página login', value: ''},
{property: 'Página validacion', value: ''},
{property: 'Fecha de creación', value: this.data.client.createdAt},
{property: 'Creado por', value: this.data.client.createdBy}
];

View File

@ -2,15 +2,12 @@
<mat-expansion-panel hideToggle>
<mat-expansion-panel-header>
<mat-panel-title> Sincronización ogBoot </mat-panel-title>
<mat-panel-description>
<mat-icon [style.color]="getIcon().color">{{ getIcon().name }}</mat-icon>
</mat-panel-description>
</mat-expansion-panel-header>
<p *ngIf="alertMessage">Oglives creados en servidor ogBoot: {{ alertMessage }}</p>
<p *ngIf="alertMessage">Oglives creados en servidor ogCore (base de datos): {{ length }}</p>
<div class="example-button-row">
<button mat-flat-button color="primary" (click)="syncOgCore()"> Sincronizar OgCore</button>
<button mat-flat-button color="primary" (click)="syncOgBoot()"> Sincronizar OgCore</button>
</div>
</mat-expansion-panel>
</mat-accordion>

View File

@ -199,15 +199,7 @@ export class ImagesComponent implements OnInit {
});
}
getIcon(): { name: string, color: string } {
if (this.alertMessage) {
return { name: 'check_circle', color: 'green' };
} else {
return { name: 'cancel', color: 'red' };
}
}
syncOgCore(): void {
syncOgBoot(): void {
this.http.post(`${this.apiUrl}/sync`, {})
.subscribe(response => {
this.toastService.success('Sincronización completada');

View File

@ -10,33 +10,33 @@
}.dashboard {
padding: 20px;
}
.disk-usage-container {
display: flex;
align-items: flex-start;
margin-bottom: 20px;
}
.disk-usage {
flex: 2;
margin-right: 20px;
}
.services-status {
flex: 1;
}
.services-status ul {
list-style-type: none;
padding: 0;
}
.services-status li {
margin: 5px 0;
display: flex;
align-items: center;
}
.status-led {
width: 10px;
height: 10px;
@ -44,29 +44,29 @@
display: inline-block;
margin-right: 10px;
}
.status-led.active {
background-color: green;
}
.status-led.inactive {
background-color: red;
}
.installed-oglives {
margin-top: 20px;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
}
th {
background-color: #f4f4f4;
}
@ -77,13 +77,13 @@
gap: 10px; /* Espacio entre botones */
margin-top: 50px;
}
.btn:first-child {
margin-left: 0;
}
.btn:last-child {
margin-right: 0;
}
}

View File

@ -1,18 +1,26 @@
<h2 mat-dialog-title>Añade clientes a a {{data.subnetName}}</h2>
<h2 mat-dialog-title>Añade clientes a {{data.subnetName}}</h2>
<mat-dialog-content>
<div *ngIf="loading" class="loading-container">
<mat-spinner></mat-spinner>
<p>Cargando clientes...</p>
</div>
<mat-form-field appearance="fill" *ngIf="!loading" style="width: 100%;">
<mat-label>Añadir clientes</mat-label>
<mat-select [(value)]="selectedClients" multiple>
<mat-option *ngFor="let client of clients" [value]="client">
{{ client.name }} (IP: {{ client.ip }}, MAC: {{ client.mac }})
<mat-form-field appearance="fill" class="search-select">
<input type="text" matInput [formControl]="clientControl" [matAutocomplete]="clientAuto" placeholder="Seleccione un cliente">
<mat-autocomplete #clientAuto="matAutocomplete" [displayWith]="displayFnClient" (optionSelected)="onOptionClientSelected($event.option.value)">
<mat-option *ngFor="let client of filteredClients | async" [value]="client">
{{ client.name }}
</mat-option>
</mat-select>
</mat-autocomplete>
</mat-form-field>
<div *ngIf="selectedClients.length > 0">
<h3>Clientes seleccionados:</h3>
<ul>
<li *ngFor="let client of selectedClients">
{{ client.name }}
<button mat-icon-button color="warn" (click)="removeClient(client)">
<mat-icon>delete</mat-icon>
</button>
</li>
</ul>
</div>
</mat-dialog-content>
<mat-dialog-actions>

View File

@ -1,6 +1,10 @@
import { Component, OnInit, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import {Observable, startWith} from "rxjs";
import {map} from "rxjs/operators";
import {FormControl} from "@angular/forms";
import {ToastrService} from "ngx-toastr";
@Component({
selector: 'app-add-clients-to-subnet',
@ -12,46 +16,87 @@ export class AddClientsToSubnetComponent implements OnInit {
clients: any[] = [];
selectedClients: any[] = [];
loading: boolean = true;
filters: { [key: string]: string } = {};
filteredClients!: Observable<any[]>;
clientControl = new FormControl();
constructor(
private http: HttpClient,
public dialogRef: MatDialogRef<AddClientsToSubnetComponent>,
@Inject(MAT_DIALOG_DATA) public data: { subnetUuid: string, subnetName: string }
private toastService: ToastrService,
@Inject(MAT_DIALOG_DATA) public data: { subnetUuid: string, subnetName: string }
) {}
ngOnInit(): void {
console.log('Selected subnet UUID:', this.data);
this.loading = true;
this.http.get<any>(`${this.baseUrl}/clients?page=1&itemsPerPage=30`).subscribe(
this.loadClients();
this.filteredClients = this.clientControl.valueChanges.pipe(
startWith(''),
map(value => (typeof value === 'string' ? value : value?.name)),
map(name => (name ? this._filterClients(name) : this.clients.slice()))
);
}
loadClients() {
this.http.get<any>( `${this.baseUrl}/clients?&page=1&itemsPerPage=10000&exists[subnet]=false`).subscribe(
response => {
this.clients = response['hydra:member'];
this.loading = false;
},
error => {
console.error('Error fetching clients:', error);
console.error('Error fetching parent units:', error);
this.loading = false;
}
);
}
save() {
const postData = {
clients: JSON.stringify(this.selectedClients.map(client => client.uuid))
};
this.selectedClients.forEach(client => {
const postData = {
client: client['@id']
};
this.http.post(`${this.baseUrl}/og-dhcp/server/${this.data.subnetUuid}/post-host`, postData).subscribe(
response => {
console.log('Clients assigned successfully:', response);
this.dialogRef.close(this.selectedClients);
},
error => {
console.error('Error assigning clients:', error);
}
);
this.http.post(`${this.baseUrl}/og-dhcp/server/${this.data.subnetUuid}/post-host`, postData).subscribe(
response => {
this.toastService.success(`Cliente ${client.name} asignado correctamente`);
},
error => {
console.error(`Error al asignar el cliente ${client.name}:`, error);
this.toastService.error(`Error al asignar el cliente ${client.name}: ${error.error['hydra:description']}`);
}
);
});
this.dialogRef.close(this.selectedClients);
}
close() {
this.dialogRef.close();
}
removeClient(client: any) {
const index = this.selectedClients.indexOf(client);
if (index >= 0) {
this.selectedClients.splice(index, 1);
}
}
private _filterClients(name: string): any[] {
const filterValue = name.toLowerCase();
return this.clients.filter(client => client.name.toLowerCase().includes(filterValue));
}
displayFnClient(client: any): string {
return client && client.name ? client.name : '';
}
onOptionClientSelected(client: any) {
if (!this.selectedClients.includes(client)) {
this.selectedClients.push(client);
}
this.clientControl.setValue('');
}
}

View File

@ -1,8 +1,36 @@
.full-width {
width: 100%;
}
form{
width: 100%;
}
form{
padding: 20px;
}
.spacing-container {
margin-top: 20px;
margin-bottom: 16px;
}
.list-item-content {
display: flex;
align-items: flex-start; /* Alinea el contenido al inicio */
justify-content: space-between; /* Espacio entre los textos y los íconos */
width: 100%; /* Asegúrate de que el contenido ocupe todo el ancho */
}
.text-content {
flex-grow: 1;
margin-right: 16px;
margin-left: 10px;
}
.icon-container {
display: flex;
align-items: center;
}
.right-icon {
margin-left: 8px;
cursor: pointer;
}
padding: 20px;
}

View File

@ -1,27 +1,56 @@
<h2 mat-dialog-title>Añadir configuración de red</h2>
<h2 mat-dialog-title>{{ isEditMode ? 'Editar' : 'Añadir' }} subred</h2>
<mat-dialog-content>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Nombre</mat-label>
<input matInput [(ngModel)]="name" placeholder="Nombre de la subred" required>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Netmask</mat-label>
<input matInput [(ngModel)]="netmask" placeholder="Netmask" required>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Dirección IP</mat-label>
<input matInput [(ngModel)]="ipAddress" placeholder="Dirección IP" required>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Next Server</mat-label>
<input matInput [(ngModel)]="nextServer" placeholder="Next Server" required>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Boot File Name</mat-label>
<input matInput [(ngModel)]="bootFileName" placeholder="Boot File Name" required>
</mat-form-field>
<mat-tab-group>
<mat-tab label="Subred">
<div class="spacing-container">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Nombre</mat-label>
<input matInput [(ngModel)]="name" placeholder="Nombre de la subred" required>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Netmask</mat-label>
<input matInput [(ngModel)]="netmask" placeholder="Netmask" required>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Dirección IP</mat-label>
<input matInput [(ngModel)]="ipAddress" placeholder="Dirección IP" required>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Next Server</mat-label>
<input matInput [(ngModel)]="nextServer" placeholder="Next Server" required>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Boot File Name</mat-label>
<input matInput [(ngModel)]="bootFileName" placeholder="Boot File Name" required>
</mat-form-field>
</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-actions align="end">
<button mat-button (click)="onNoClick()">Cancelar</button>
<button mat-button (click)="addNetworkConfig()" cdkFocusInitial>Añadir</button>
<button mat-button (click)="addNetworkConfig()" cdkFocusInitial>Guardar</button>
</mat-dialog-actions>

View File

@ -1,7 +1,8 @@
import { HttpClient } from '@angular/common/http';
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog';
import { ToastrService } from 'ngx-toastr';
import {DeleteModalComponent} from "../../../../shared/delete_modal/delete-modal/delete-modal.component";
@Component({
selector: 'app-create-subnet',
@ -10,21 +11,42 @@ import { ToastrService } from 'ngx-toastr';
})
export class CreateSubnetComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
subnetId: string | null = null;
name: string = '';
netmask: string = '';
ipAddress: string = '';
nextServer: string = '';
bootFileName: string = '';
syncronized: boolean = false;
serverId: number = 0;
clients: any[] = [];
isEditMode: boolean = false;
constructor(
private toastService: ToastrService,
private http: HttpClient,
public dialogRef: MatDialogRef<CreateSubnetComponent>,
public dialog: MatDialog,
@Inject(MAT_DIALOG_DATA) public data: any
) { }
ngOnInit(): void {
if (this.data) {
this.loadData();
this.isEditMode = true;
}
}
loadData() {
this.subnetId = this.data.uuid;
this.name = this.data.name;
this.netmask = this.data.netmask;
this.ipAddress = this.data.ipAddress;
this.nextServer = this.data.nextServer;
this.bootFileName = this.data.bootFileName;
this.syncronized = this.data.syncronized;
this.serverId = this.data.serverId;
this.clients = this.data.clients
}
onNoClick(): void {
@ -40,17 +62,52 @@ export class CreateSubnetComponent implements OnInit {
bootFileName: this.bootFileName
};
this.http.post(`${this.baseUrl}/subnets`, payload)
.subscribe({
next: (response) => {
console.log('Success:', response);
this.toastService.success('Configuración de red añadida exitosamente');
this.dialogRef.close();
},
error: (error) => {
console.error('Error:', error);
this.toastService.error(error.error['hydra:description']);
}
});
if (!this.data){
this.http.post(`${this.baseUrl}/subnets`, payload)
.subscribe({
next: (response) => {
console.log('Success:', response);
this.toastService.success('Configuración de red añadida exitosamente');
this.dialogRef.close();
},
error: (error) => {
console.error('Error:', error);
this.toastService.error(error.error['hydra:description']);
}
});
} else {
this.http.patch(`${this.baseUrl}/subnets/${this.subnetId}`, payload)
.subscribe({
next: (response) => {
console.log('Success:', response);
this.toastService.success('Configuración de red actualizada exitosamente');
this.dialogRef.close();
},
error: (error) => {
console.error('Error:', error);
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,16 +1,13 @@
<mat-accordion class="example-headers-align">
<mat-expansion-panel hideToggle>
<mat-expansion-panel-header>
<mat-panel-title> Información de Subnets </mat-panel-title>
<mat-panel-description>
<mat-icon [style.color]="getIcon().color">{{ getIcon().name }}</mat-icon>
</mat-panel-description>
<mat-panel-title> Información en servidor ogDHCP </mat-panel-title>
</mat-expansion-panel-header>
<p *ngIf="alertMessage">Subnets sincronizadas: {{ alertMessage }}</p>
<p *ngIf="alertMessage">Número de subnets en base de datos: {{ length }}</p>
<div class="example-button-row">
<button mat-flat-button color="primary" (click)="syncSubnets()"> Sincronizar Subnets</button>
<button mat-flat-button color="primary" (click)="syncSubnets()"> Sincronizar base de datos</button>
</div>
<div class="example-button-row">
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()">Ver Información</button>
</div>
</mat-expansion-panel>
</mat-accordion>
@ -25,7 +22,7 @@
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill" class="search-string">
<mat-label i18n="@@searchLabel">Buscar nombre de imagen</mat-label>
<mat-label i18n="@@searchLabel">Buscar nombre de la subred</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
@ -42,13 +39,35 @@
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill" class="search-string">
<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>
<mat-divider class="divider"></mat-divider>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let subnet">
<ng-container >
<ng-container *ngIf="column.columnDef === 'synchronized'">
<mat-icon [color]="subnet[column.columnDef] ? 'primary' : 'warn'">
{{ subnet[column.columnDef] ? 'check_circle' : 'cancel' }}
</mat-icon>
</ng-container>
<ng-container *ngIf="column.columnDef === 'clients'">
<button mat-button [matMenuTriggerFor]="menu">Ver clientes</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let client of subnet.clients">
{{ client.name }}
</button>
</mat-menu>
</ng-container>
<ng-container *ngIf="column.columnDef !== 'synchronized' && column.columnDef !== 'clients'">
{{ column.cell(subnet) }}
</ng-container>
</td>
@ -59,10 +78,15 @@
<td mat-cell *matCellDef="let subnet" style="text-align: center;">
<button mat-icon-button color="primary" (click)="editSubnet(subnet)" i18n="@@editSubnet">
<mat-icon>edit</mat-icon></button>
<button mat-icon-button color="warn" (click)="deleteSubnet(subnet)"><mat-icon>delete</mat-icon></button>
<button mat-icon-button (click)="addClientsToSubnet(subnet)">
<mat-icon>computer</mat-icon>
<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 (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

@ -7,6 +7,7 @@ import { HttpClient } from '@angular/common/http';
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
import { ToastrService } from 'ngx-toastr';
import { AddClientsToSubnetComponent } from './add-clients-to-subnet/add-clients-to-subnet.component';
import {ServerInfoDialogComponent} from "./server-info-dialog/server-info-dialog.component";
export interface Subnet {
'@id': string;
@ -16,6 +17,9 @@ export interface Subnet {
ipAddress: string;
nextServer: string;
bootFileName: string;
synchronized: boolean;
serverId: number;
clients: [];
createdAt: string;
createdBy: string;
uuid: string;
@ -29,7 +33,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', 'actions'];
displayedColumns: string[] = ['id', 'name', 'netmask', 'ipAddress', 'nextServer', 'bootFileName', 'synchronized', 'serverId', 'clients', 'actions'];
dataSource = new MatTableDataSource<Subnet>([]);
length = 0;
itemsPerPage: number = 10;
@ -47,16 +51,22 @@ export class OgDhcpSubnetsComponent {
{ 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: 'IP Servidor DHCP', cell: (subnet: Subnet) => subnet.serverId },
{ columnDef: 'clients', header: 'Lista de clientes', cell: (subnet: Subnet) => `${subnet.clients}`},
];
private apiUrl = `${this.baseUrl}/subnets`;
constructor(public dialog: MatDialog, private http: HttpClient, private toastService: ToastrService) {}
ngOnInit() {
this.loadSubnets();
this.loadAlert()
}
loadSubnets() {
this.http.get<any>(`${this.baseUrl}/subnets?page=1&itemsPerPage=${this.itemsPerPage}`).subscribe({
this.http.get<any>(`${this.baseUrl}/subnets?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`).subscribe({
next: (response) => {
this.dataSource.data = response['hydra:member'];
this.length = response['hydra:totalItems'];
@ -72,75 +82,129 @@ export class OgDhcpSubnetsComponent {
}
syncSubnets() {
console.log('Sincronizando subnets...');
this.http.post(`${this.apiUrl}/sync`, {})
.subscribe(response => {
this.toastService.success('Sincronización completada');
this.loadSubnets()
}, error => {
console.error('Error al sincronizar', error);
this.toastService.error('Error al sincronizar');
});
}
toggleAction(subnet: any, action:string): void {
switch (action) {
case 'get':
this.http.post(`${this.baseUrl}/og-dhcp/server/${subnet.uuid}/get`, {}).subscribe({
next: () => {
this.toastService.success('Subred sincronizada con servidor');
this.loadSubnets()
},
error: (error) => {
console.error('Error al crear subred en servidor', error);
this.toastService.error(error.error['hydra:description']);
}
});
break;
case 'post':
this.http.post(`${this.baseUrl}/og-dhcp/server/${subnet.uuid}/post`, {}).subscribe({
next: () => {
this.toastService.success('Petición de instalación enviada');
this.loadSubnets()
},
error: (error) => {
console.error('Error al crear subred en servidor', error);
this.toastService.error(error.error['hydra:description']);
}
});
break;
case 'put':
this.http.put(`${this.baseUrl}/og-dhcp/server/${subnet.uuid}/put`, {}).subscribe({
next: () => {
this.toastService.success('Petición de actualizacion enviada');
this.loadSubnets();
},
error: (error) => {
console.error('Error al actualizar la subred en el servidor', error);
this.toastService.error(error.error['hydra:description']);
}
});
break;
case 'delete':
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: subnet.name }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.http.delete(`${this.baseUrl}/og-dhcp/server/${subnet.uuid}/delete`, {}).subscribe({
next: () => {
this.toastService.success('Subred eliminada exitosamente');
this.loadSubnets();
},
error: (error) => {
console.error('Error al eliminar la subred', error);
this.toastService.error(error.error['hydra:description']);
}
});
}})
break;
default:
console.error('Acción no soportada:', action);
break;
}
}
addSubnet() {
console.log('Añadiendo nueva subnet...');
const dialogRef = this.dialog.open(CreateSubnetComponent, {
width: '400px'
width: '600px'
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
this.loadSubnets()
});
}
editSubnet(subnet: Subnet) {
console.log('Editando subnet:', subnet);
const dialogRef = this.dialog.open(CreateSubnetComponent, {
width: '400px',
data: { subnet }
width: '600px',
data: subnet
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
this.loadSubnets()
});
}
deleteSubnet(subnet: Subnet): void {
console.log(subnet);
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: subnet.name }
});
loadAlert() {
this.http.get(`${this.baseUrl}/og-dhcp/server/get-collection`)
.subscribe(response => {
// @ts-ignore
this.alertMessage = response.message
}, error => {
console.error('Error al cargar la información del alert', error);
});
}
dialogRef.afterClosed().subscribe(result => {
if (result) {
const apiUrl = `${this.baseUrl}${subnet['@id']}`;
this.http.delete(apiUrl).subscribe({
next: () => {
console.log('Subnet deleted successfully');
this.dataSource.data = this.dataSource.data.filter(s => s !== subnet);
this.length = this.dataSource.data.length;
this.toastService.success('Subnet deleted successfully');
},
error: (error) => {
console.error('Error deleting subnet:', error);
}
});
} else {
console.log('Subnet deletion cancelled');
openSubnetInfoDialog() {
this.loadAlert()
this.dialog.open(ServerInfoDialogComponent, {
width: '600px',
data: {
alertMessage: this.alertMessage,
length: this.length
}
});
}
updateSubnet() {
console.log('Update Subnet action selected');
}
addClientsToSubnet(subnet: Subnet) {
const dialogRef = this.dialog.open(AddClientsToSubnetComponent, {
width: '600px',
data: { subnetUuid: subnet.uuid, subnetName: subnet.name }
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
this.loadSubnets();
});
}
@ -148,13 +212,6 @@ export class OgDhcpSubnetsComponent {
onPageChange(event: any) {
this.page = event.pageIndex;
this.itemsPerPage = event.pageSize;
}
getIcon(): { name: string, color: string } {
if (this.alertMessage) {
return { name: 'check_circle', color: 'green' };
} else {
return { name: 'cancel', color: 'red' };
}
this.loadSubnets();
}
}

View File

@ -0,0 +1,7 @@
<h2 mat-dialog-title>Información de Subnets</h2>
<mat-dialog-content>
<pre>{{ data | json }}</pre>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close>Cerrar</button>
</mat-dialog-actions>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ServerInfoDialogComponent } from './server-info-dialog.component';
describe('ServerInfoDialogComponent', () => {
let component: ServerInfoDialogComponent;
let fixture: ComponentFixture<ServerInfoDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ServerInfoDialogComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ServerInfoDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,12 @@
import {Component, Inject} from '@angular/core';
import {MAT_DIALOG_DATA} from "@angular/material/dialog";
@Component({
selector: 'app-server-info-dialog',
templateUrl: './server-info-dialog.component.html',
styleUrl: './server-info-dialog.component.css'
})
export class ServerInfoDialogComponent {
constructor(@Inject(MAT_DIALOG_DATA) public data: any) {}
}

View File

@ -0,0 +1,93 @@
.disk-usage-info{
display: flex;
justify-content: start;
margin-top: 10px;
}
p {
margin-left: 15px;
}.dashboard {
padding: 20px;
}
.disk-usage-container {
display: flex;
align-items: flex-start;
margin-bottom: 20px;
}
.disk-usage {
flex: 2;
margin-right: 20px;
}
.services-status {
flex: 1;
}
.services-status ul {
display: flex;
flex-direction: column;
justify-content: center;
list-style-type: none;
padding: 0;
}
.services-status li {
justify-content: left;
margin: 5px 0;
display: flex;
align-items: center;
}
.status-led {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
}
.status-led.active {
background-color: green;
}
.status-led.inactive {
background-color: red;
}
.installed-oglives {
margin-top: 20px;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
}
th {
background-color: #f4f4f4;
}
.button-container {
display: flex;
flex-direction: column;
gap: 10px; /* Espacio entre botones */
margin-top: 50px;
}
.btn:first-child {
margin-left: 0;
}
.btn:last-child {
margin-right: 0;
}

View File

@ -0,0 +1,61 @@
<div class="dashboard">
<h2>OgBoot server Status</h2>
<div class="disk-usage-container">
<div class="disk-usage">
<h3>Uso de disco</h3>
<ngx-charts-pie-chart
[view]="view"
[scheme]="colorScheme"
[results]="diskUsageChartData"
[gradient]="gradient"
[doughnut]="isDoughnut"
[labels]="showLabels"
[legend]="showLegend">
</ngx-charts-pie-chart>
<div class="disk-usage-info">
<p>Total: {{ diskUsage.total }}</p>
<p>Ocupado: {{ diskUsage.used }}</p>
<p>Disponible: {{ diskUsage.available }}</p>
<p>Libre: {{ diskUsage.percentage }}</p>
</div>
</div>
<div class="services-status">
<h3>Servicios</h3>
<ul>
<li *ngFor="let service of getServices()">
<span
class="status-led"
[ngClass]="{ 'active': service.status === 'active', 'inactive': service.status !== 'active' }"
></span>
{{ service.name }}: {{ service.status }}
</li>
</ul>
</div>
</div>
<div class="installed-oglives">
<h3>Subredes</h3>
<table>
<thead>
<tr>
<th>ID</th>
<th>Boot file name</th>
<th>Next server</th>
<th>Ip</th>
<th>Ordenadores</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let subnet of subnets">
<td>{{ subnet.id }}</td>
<td>{{ subnet['boot-file-name'] }}</td>
<td>{{ subnet['next-server'] }}</td>
<td>{{ subnet.subnet }}</td>
<td>{{ subnet.reservations.length }}</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { StatusComponent } from './status.component';
describe('StatusComponent', () => {
let component: StatusComponent;
let fixture: ComponentFixture<StatusComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [StatusComponent]
})
.compileComponents();
fixture = TestBed.createComponent(StatusComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,58 @@
import { Component } from '@angular/core';
import {HttpClient} from "@angular/common/http";
@Component({
selector: 'app-status',
templateUrl: './status.component.html',
styleUrl: './status.component.css'
})
export class StatusComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
diskUsage: any = {};
servicesStatus: any = {};
subnets: any[] = [];
diskUsageChartData: any[] = [];
view: [number, number] = [1100, 500];
gradient: boolean = true;
showLegend: boolean = true;
showLabels: boolean = true;
isDoughnut: boolean = true;
colorScheme: any = {
domain: ['#FF6384', '#3f51b5']
};
constructor(private http: HttpClient) {}
ngOnInit(): void {
this.loadStatus();
}
loadStatus(): void {
this.http.get<any>(`${this.baseUrl}/og-dhcp/status`).subscribe(data => {
this.diskUsage = data.message.disk_usage;
this.servicesStatus = data.message.services_status;
this.subnets = data.message.subnets;
this.diskUsageChartData = [
{
name: 'Usado',
value: parseFloat(this.diskUsage.used)
},
{
name: 'Disponible',
value: parseFloat(this.diskUsage.available)
}
];
}, error => {
console.error('Error fetching status', error);
});
}
getServices(): { name: string, status: string }[] {
return Object.keys(this.servicesStatus).map(key => ({
name: key,
status: this.servicesStatus[key]
}));
}
}

View File

@ -60,7 +60,13 @@
<!-- Submenu items ogdhcp -->
<mat-nav-list *ngIf="showOgDhcpSub" style="padding-left: 20px;">
<mat-list-item routerLink="/dhcp-subnets">
<mat-list-item routerLink="/ogdhcp-status">
<span class="entry">
<mat-icon class="icon">analytics</mat-icon>
<span i18n="@@gallery">Estado</span>
</span>
</mat-list-item>
<mat-list-item routerLink="/subnets">
<span class="entry">
<mat-icon class="icon">lan</mat-icon>
<span i18n="@@gallery">Subnets</span>
@ -141,5 +147,5 @@
</span>
</mat-list-item>
</mat-nav-list>

View File

@ -16,11 +16,9 @@ export class SidebarComponent {
showCommandSub: boolean = false;
toggleOgBootSub() {
alert('El correcto funcionamiento de este componente está sujeto a la disponibilidad de la API de ogBoot');
this.showOgBootSub = !this.showOgBootSub;
}
toggleOgDhcpSub() {
alert('El correcto funcionamiento de este componente está sujeto a la disponibilidad de la API de ogDHCP');
this.showOgDhcpSub = !this.showOgDhcpSub;
}
toggleCommandSub() {