Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop

develop-jenkins
Alvaro Puente Mella 2024-10-14 13:42:22 +02:00
commit df67445b53
29 changed files with 734 additions and 385 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,8 +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 { PartitionAssistantComponent } from './components/commands/commands-modals/partition-assistant/partition-assistant.component';
import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component';
import { StatusComponent } from "./components/ogdhcp/og-dhcp-subnets/status/status.component";
const routes: Routes = [
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
{
@ -38,14 +37,13 @@ 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 },
{ path: 'commands-logs', component: TaskLogsComponent },
{ path: 'calendars', component: CalendarComponent },
{ path: 'partitionAssistant', component: PartitionAssistantComponent},
{ path: 'client/:id', component: ClientMainViewComponent },
],
},
{

View File

@ -100,8 +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 { PartitionAssistantComponent } from './components/commands/commands-modals/partition-assistant/partition-assistant.component';
import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-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';
import {MatSliderModule} from '@angular/material/slider';
@NgModule({
declarations: [
@ -160,8 +160,8 @@ import {MatSliderModule} from '@angular/material/slider';
AdvancedSearchComponent,
TaskLogsComponent,
OrganizationalUnitTabViewComponent,
PartitionAssistantComponent,
ClientMainViewComponent
ServerInfoDialogComponent,
StatusComponent
],
bootstrap: [AppComponent],
imports: [BrowserModule,

View File

@ -1,79 +0,0 @@
.title {
font-size: 24px;
}
.calendar-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
}
.divider {
margin: 20px 0;
}
.lists-container {
padding: 16px;
}
.imagesLists-container {
flex: 1;
}
.card.unidad-card {
height: 100%;
box-sizing: border-box;
}
table {
width: 100%;
margin-top: 50px;
}
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 5px;
box-sizing: border-box;
}
.search-string {
flex: 2;
padding: 5px;
}
.search-boolean {
flex: 1;
padding: 5px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
}
.paginator-container {
display: flex;
justify-content: end;
margin-bottom: 30px;
}
.mat-chip-readonly-true {
background-color: #4CAF50 !important;
color: white !important;
}
.mat-chip-readonly-false {
background-color: #F44336 !important;
color: white !important;
}

View File

@ -1,20 +0,0 @@
<div class="header-container">
<h2 class="title">Asistente de particionado</h2>
</div>
<mat-divider class="divider"></mat-divider>
<div class="client" *ngIf="clientInfo && clientInfo.name">
<mat-card class="result-card">
<mat-card-title>Cliente: {{clientInfo.name}}</mat-card-title>
<mat-card-subtitle>Tipo: {{clientInfo.type}}</mat-card-subtitle>
<mat-card-content>
<p><strong>IP:</strong> {{clientInfo.ip}}</p>
<p><strong>MAC:</strong> {{clientInfo.mac}}</p>
<p><strong>Número de serie:</strong> {{clientInfo.serialNumber}}</p>
<p><strong>Interfaz de red:</strong> {{clientInfo.netiface}}</p>
<p><strong>Driver de red:</strong> {{clientInfo.netDriver}}</p>
</mat-card-content>
</mat-card>
</div>

View File

@ -1,55 +0,0 @@
import { HttpClient } from '@angular/common/http';
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
import { ToastrService } from 'ngx-toastr';
import { DataService } from '../../main-commands/data.service';
interface ClientInfo {
name: string;
type: string;
ip: string;
mac: string;
serialNumber: string;
netiface: string;
netDriver: string;
}
@Component({
selector: 'app-partition-assistant',
templateUrl: './partition-assistant.component.html',
styleUrl: './partition-assistant.component.css'
})
export class PartitionAssistantComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
client: string[] = [];
clientInfo: ClientInfo | undefined;
constructor(
private dataService: DataService,
public dialog: MatDialog,
private toastService: ToastrService,
private http: HttpClient,
@Inject(MAT_DIALOG_DATA) public data: { clients: string[] , command?: any }
) {
console.log('clients', data.clients[0]);
}
ngOnInit(): void {
this.getClientInfo(this.data.clients[0]);
}
getClientInfo(uuid: string): void {
this.http.get<ClientInfo>(`${this.baseUrl}/clients/${uuid}`)
.subscribe(
(response: ClientInfo) => {
this.clientInfo = response;
},
error => {
console.error('Error fetching client info:', error);
}
);
}
}

View File

@ -6,7 +6,6 @@ import { CreatePxeBootFileComponent } from '../../../ogboot/pxe-boot-files/creat
import { HttpClient } from '@angular/common/http';
import { CommandDetailComponent } from '../../../commands/main-commands/detail-command/command-detail.component';
import { RouterLink } from '@angular/router';
import { PartitionAssistantComponent } from '../../../commands/commands-modals/partition-assistant/partition-assistant.component';
@Component({
selector: 'app-acctions-modal',
templateUrl: './acctions-modal.component.html',
@ -56,32 +55,6 @@ export class AcctionsModalComponent {
onCommandClick(command: any): void {
const payload = {
clients: this.selectedElements.map((uuid: any) => `/clients/${uuid}`)
};
const apiUrl = `${this.baseUrl}/commands/${command.uuid}/execute`;
console.log(this.selectedElements.length)
if (command.uuid === '01924d28-5880-734f-9187-f6b0f6c0c8d7' && this.selectedElements.length == 1) {
const dialogRef = this.dialog.open(PartitionAssistantComponent, { data: { clients: this.selectedElements }, width: '150%', height: '100%' });
}
if (command.uuid === '01924d28-5880-734f-9187-f6b0f6c0c8d7' && this.selectedElements.length != 1) {
this.toastService.error('Please select only one client to execute this command');
}
else{
this.http.post(apiUrl, payload).subscribe({
next: () => {
console.log('Command executed successfully');
this.loadCommands();
this.toastService.success('Command executed successfully');
},
error: (error) => {
console.error('Error executing command:', error);
}
});
}
}
chunkArray(arr: any[], chunkSize: number): any[] {

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

@ -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,10 +16,14 @@ 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>,
private toastService: ToastrService,
@Inject(MAT_DIALOG_DATA) public data: { subnetUuid: string, subnetName: string }
) {}
@ -23,35 +31,72 @@ export class AddClientsToSubnetComponent implements OnInit {
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() {
this.selectedClients.forEach(client => {
const postData = {
clients: JSON.stringify(this.selectedClients.map(client => client.uuid))
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);
this.toastService.success(`Cliente ${client.name} asignado correctamente`);
},
error => {
console.error('Error assigning clients:', 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

@ -3,6 +3,34 @@
}
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;
}

View File

@ -1,5 +1,9 @@
<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-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>
@ -20,8 +24,33 @@
<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,6 +62,7 @@ export class CreateSubnetComponent implements OnInit {
bootFileName: this.bootFileName
};
if (!this.data){
this.http.post(`${this.baseUrl}/subnets`, payload)
.subscribe({
next: (response) => {
@ -52,5 +75,39 @@ export class CreateSubnetComponent implements OnInit {
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,36 +82,55 @@ export class OgDhcpSubnetsComponent {
}
syncSubnets() {
console.log('Sincronizando subnets...');
}
addSubnet() {
console.log('Añadiendo nueva subnet...');
const dialogRef = this.dialog.open(CreateSubnetComponent, {
width: '400px'
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
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');
});
}
editSubnet(subnet: Subnet) {
console.log('Editando subnet:', subnet);
const dialogRef = this.dialog.open(CreateSubnetComponent, {
width: '400px',
data: { subnet }
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
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']);
}
deleteSubnet(subnet: Subnet): void {
console.log(subnet);
});
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 }
@ -109,38 +138,73 @@ export class OgDhcpSubnetsComponent {
dialogRef.afterClosed().subscribe(result => {
if (result) {
const apiUrl = `${this.baseUrl}${subnet['@id']}`;
this.http.delete(apiUrl).subscribe({
this.http.delete(`${this.baseUrl}/og-dhcp/server/${subnet.uuid}/delete`, {}).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');
this.toastService.success('Subred eliminada exitosamente');
this.loadSubnets();
},
error: (error) => {
console.error('Error deleting subnet:', error);
console.error('Error al eliminar la subred', error);
this.toastService.error(error.error['hydra:description']);
}
});
} else {
console.log('Subnet deletion cancelled');
}})
break;
default:
console.error('Acción no soportada:', action);
break;
}
}
addSubnet() {
const dialogRef = this.dialog.open(CreateSubnetComponent, {
width: '600px'
});
dialogRef.afterClosed().subscribe(result => {
this.loadSubnets()
});
}
updateSubnet() {
console.log('Update Subnet action selected');
editSubnet(subnet: Subnet) {
const dialogRef = this.dialog.open(CreateSubnetComponent, {
width: '600px',
data: subnet
});
dialogRef.afterClosed().subscribe(result => {
this.loadSubnets()
});
}
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);
});
}
openSubnetInfoDialog() {
this.loadAlert()
this.dialog.open(ServerInfoDialogComponent, {
width: '600px',
data: {
alertMessage: this.alertMessage,
length: this.length
}
});
}
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>

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() {