multiple-ogdhcp #68
|
|
@ -1,5 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
## [1.4.0] - 2026-04-16
|
||||
### Added
|
||||
- Añadida gestión de múltiples Dhcps
|
||||
- Si el Dhcp tiene ordenadores añadidos en su fichero kea, se pueden sincronizar para añadirlos a la estructura de Grupos actual.
|
||||
|
||||
## [1.3.3] - 2026-04-13
|
||||
### Improved
|
||||
- Mejorada la visualizacion del selector de particiones mostrando el contenido de cache y la imagen asociada a la partición.
|
||||
|
|
|
|||
|
|
@ -35,8 +35,9 @@ import {
|
|||
import { EnvVarsComponent } from "./components/admin/env-vars/env-vars.component";
|
||||
import { MenusComponent } from "./components/menus/menus.component";
|
||||
import { OgDhcpSubnetsComponent } from "./components/ogdhcp/og-dhcp-subnets.component";
|
||||
import { StatusComponent } from "./components/ogdhcp/status/status.component";
|
||||
import { DhcpServersComponent } from './components/ogdhcp/dhcp-servers/dhcp-servers.component';
|
||||
import {
|
||||
|
||||
RunScriptAssistantComponent
|
||||
} from "./components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component";
|
||||
import { roleGuard } from './guards/role.guard';
|
||||
|
|
@ -58,7 +59,7 @@ const routes: Routes = [
|
|||
{ path: 'pxe-boot-file', component: PxeBootFilesComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'ogboot-status', component: OgbootStatusComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'subnets', component: OgDhcpSubnetsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'ogdhcp-status', component: StatusComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'dhcp-servers', component: DhcpServersComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'commands', component: CommandsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'commands-groups', component: CommandsGroupsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'commands-task', component: CommandsTaskComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
|
|
|
|||
|
|
@ -141,14 +141,17 @@ import { InputDialogComponent } from './components/task-logs/input-dialog/input-
|
|||
import { ManageOrganizationalUnitComponent } from './components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component';
|
||||
import { BackupImageComponent } from './components/repositories/backup-image/backup-image.component';
|
||||
import { ServerInfoDialogComponent } from "./components/ogdhcp/server-info-dialog/server-info-dialog.component";
|
||||
import { StatusComponent } from "./components/ogdhcp/status/status.component";
|
||||
import { OgDhcpSubnetsComponent } from "./components/ogdhcp/og-dhcp-subnets.component";
|
||||
import { SyncSubnetDialogComponent } from "./components/ogdhcp/sync-subnet-dialog/sync-subnet-dialog.component";
|
||||
import { SyncResultDialogComponent } from "./components/ogdhcp/sync-result-dialog/sync-result-dialog.component";
|
||||
import { CreateSubnetComponent } from "./components/ogdhcp/create-subnet/create-subnet.component";
|
||||
import { AddClientsToSubnetComponent } from "./components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component";
|
||||
import { ShowClientsComponent } from './components/ogdhcp/show-clients/show-clients.component';
|
||||
import { OperationResultDialogComponent } from './components/ogdhcp/operation-result-dialog/operation-result-dialog.component';
|
||||
import { ManageClientComponent } from './components/groups/shared/clients/manage-client/manage-client.component';
|
||||
import { ConvertImageComponent } from './components/repositories/convert-image/convert-image.component';
|
||||
import { DhcpServersComponent } from './components/ogdhcp/dhcp-servers/dhcp-servers.component';
|
||||
import { ManageDhcpServerComponent } from './components/ogdhcp/manage-dhcp-server/manage-dhcp-server.component';
|
||||
import {NgOptimizedImage, registerLocaleData} from '@angular/common';
|
||||
import localeEs from '@angular/common/locales/es';
|
||||
import { GlobalStatusComponent } from './components/global-status/global-status.component';
|
||||
|
|
@ -234,6 +237,8 @@ registerLocaleData(localeEs, 'es-ES');
|
|||
OgbootStatusComponent,
|
||||
OgDhcpSubnetsComponent,
|
||||
CreateSubnetComponent,
|
||||
DhcpServersComponent,
|
||||
ManageDhcpServerComponent,
|
||||
AddClientsToSubnetComponent,
|
||||
CommandsComponent,
|
||||
CommandDetailComponent,
|
||||
|
|
@ -252,7 +257,8 @@ registerLocaleData(localeEs, 'es-ES');
|
|||
DetailTaskComponent,
|
||||
TaskLogsComponent,
|
||||
ServerInfoDialogComponent,
|
||||
StatusComponent,
|
||||
SyncSubnetDialogComponent,
|
||||
SyncResultDialogComponent,
|
||||
ImagesComponent,
|
||||
CreateImageComponent,
|
||||
SoftwareComponent,
|
||||
|
|
|
|||
|
|
@ -17,18 +17,20 @@
|
|||
</div>
|
||||
|
||||
<mat-dialog-content [ngClass]="{'loading': loading}">
|
||||
<div class="spinner-container" *ngIf="loading">
|
||||
@if(loading) {
|
||||
<div class="spinner-container">
|
||||
<div class="loading-content">
|
||||
<mat-spinner class="loading-spinner"></mat-spinner>
|
||||
<p class="loading-text">Cargando estado del sistema...</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div *ngIf="!loading" class="main-content">
|
||||
@if(!loading){
|
||||
<div class="main-content">
|
||||
<div class="system-overview">
|
||||
<div class="overview-card clickable"
|
||||
[ngClass]="{'active': selectedSection === 'repositories'}"
|
||||
(click)="selectSection('repositories')">
|
||||
<div class="overview-card clickable" [ngClass]="{'active': selectedSection === 'repositories'}"
|
||||
(click)="selectSection('repositories')">
|
||||
<div class="overview-icon">
|
||||
<mat-icon>cloud</mat-icon>
|
||||
</div>
|
||||
|
|
@ -37,36 +39,36 @@
|
|||
<p>Total: <span class="status-badge info">{{ repositories.length }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overview-card clickable"
|
||||
[ngClass]="{'active': selectedSection === 'ogboot'}"
|
||||
(click)="selectSection('ogboot')">
|
||||
|
||||
<div class="overview-card clickable" [ngClass]="{'active': selectedSection === 'ogboot'}"
|
||||
(click)="selectSection('ogboot')">
|
||||
<div class="overview-icon">
|
||||
<mat-icon>storage</mat-icon>
|
||||
</div>
|
||||
<div class="overview-content">
|
||||
<h3>OgBoot Server</h3>
|
||||
<p *ngIf="!errorOgBoot">Estado: <span class="status-badge online">Operativo</span></p>
|
||||
<p *ngIf="errorOgBoot">Estado: <span class="status-badge offline">Error</span></p>
|
||||
@if(!errorOgBoot){
|
||||
<p>Estado: <span class="status-badge online">Operativo</span></p>
|
||||
}
|
||||
@else{
|
||||
<p>Estado: <span class="status-badge offline">Error</span></p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overview-card clickable"
|
||||
[ngClass]="{'active': selectedSection === 'dhcp'}"
|
||||
(click)="selectSection('dhcp')">
|
||||
|
||||
<div class="overview-card clickable" [ngClass]="{'active': selectedSection === 'dhcp'}"
|
||||
(click)="selectSection('dhcp')">
|
||||
<div class="overview-icon">
|
||||
<mat-icon>router</mat-icon>
|
||||
</div>
|
||||
<div class="overview-content">
|
||||
<h3>DHCP Server</h3>
|
||||
<p *ngIf="!errorDhcp">Estado: <span class="status-badge online">Operativo</span></p>
|
||||
<p *ngIf="errorDhcp">Estado: <span class="status-badge offline">Error</span></p>
|
||||
<h3>Servidores DHCP</h3>
|
||||
<p>Total: <span class="status-badge info">{{ dhcps.length }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overview-card clickable"
|
||||
[ngClass]="{'active': selectedSection === 'ogcore'}"
|
||||
(click)="selectSection('ogcore')">
|
||||
|
||||
<div class="overview-card clickable" [ngClass]="{'active': selectedSection === 'ogcore'}"
|
||||
(click)="selectSection('ogcore')">
|
||||
<div class="overview-icon">
|
||||
<mat-icon>dns</mat-icon>
|
||||
</div>
|
||||
|
|
@ -85,7 +87,7 @@
|
|||
<h3>No hay repositorios disponibles</h3>
|
||||
<p>No se encontraron repositorios configurados en el sistema.</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div *ngIf="repositories.length > 0" class="repositories-selector-container">
|
||||
<div class="repository-selector">
|
||||
<mat-form-field appearance="outline" class="repository-select-field">
|
||||
|
|
@ -98,8 +100,10 @@
|
|||
<mat-icon matSuffix>storage</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectedRepositoryUuid && !errorRepositories[selectedRepositoryUuid] && repositoryStatuses[selectedRepositoryUuid]" class="selected-repository-content">
|
||||
|
||||
<div
|
||||
*ngIf="selectedRepositoryUuid && !errorRepositories[selectedRepositoryUuid] && repositoryStatuses[selectedRepositoryUuid]"
|
||||
class="selected-repository-content">
|
||||
<div class="repository-item">
|
||||
<div class="section-header">
|
||||
<h2>{{ getSelectedRepositoryName() }}</h2>
|
||||
|
|
@ -108,13 +112,13 @@
|
|||
<span>Ver logs</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="repository-content">
|
||||
<app-status-tab [loading]="loading" [diskUsage]="repositoryStatuses[selectedRepositoryUuid].disk"
|
||||
[servicesStatus]="repositoryStatuses[selectedRepositoryUuid].services"
|
||||
[processesStatus]="repositoryStatuses[selectedRepositoryUuid].processes"
|
||||
[ramUsage]="repositoryStatuses[selectedRepositoryUuid].ram" [cpuUsage]="repositoryStatuses[selectedRepositoryUuid].cpu"
|
||||
[diskUsageChartData]="[
|
||||
[ramUsage]="repositoryStatuses[selectedRepositoryUuid].ram"
|
||||
[cpuUsage]="repositoryStatuses[selectedRepositoryUuid].cpu" [diskUsageChartData]="[
|
||||
{ name: 'Usado', value: repositoryStatuses[selectedRepositoryUuid].disk.used },
|
||||
{ name: 'Disponible', value: repositoryStatuses[selectedRepositoryUuid].disk.available }
|
||||
]" [ramUsageChartData]="[
|
||||
|
|
@ -125,30 +129,26 @@
|
|||
</app-status-tab>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="repository-logs-section" id="repository-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>Datos del repositorio: {{ getSelectedRepositoryName() }}</h3>
|
||||
<p>Información específica del repositorio seleccionado</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe
|
||||
[src]="getRepositoryIframeUrl()"
|
||||
width="100%"
|
||||
height="600"
|
||||
frameborder="0"
|
||||
<iframe [src]="getRepositoryIframeUrl()" width="100%" height="600" frameborder="0"
|
||||
class="repository-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div *ngIf="!selectedRepositoryUuid" class="no-repository-selected">
|
||||
<mat-icon class="no-data-icon">storage</mat-icon>
|
||||
<h3>Selecciona un repositorio</h3>
|
||||
<p>Elige un repositorio de la lista para ver su estado detallado.</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div *ngIf="selectedRepositoryUuid && errorRepositories[selectedRepositoryUuid]" class="error-container">
|
||||
<div class="error-card">
|
||||
<mat-icon class="error-icon">error_outline</mat-icon>
|
||||
|
|
@ -164,34 +164,29 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectedSection === 'ogboot'" class="section-content">
|
||||
<div *ngIf="!errorOgBoot && !loadingOgBoot" class="tab-content">
|
||||
<div class="section-header">
|
||||
<h2>OgBoot Server</h2>
|
||||
<button class="logs-button" (click)="scrollToLogs()">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>Ver logs</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<app-status-tab [loading]="loadingOgBoot" [diskUsage]="ogBootDiskUsage" [servicesStatus]="ogBootServicesStatus"
|
||||
[installedOgLives]="installedOgLives" [diskUsageChartData]="ogBootDiskUsageChartData" [view]="view"
|
||||
[colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="isDhcp"
|
||||
[isRepository]="false">
|
||||
</app-status-tab>
|
||||
|
||||
<div *ngIf="selectedSection === 'ogboot'" class="section-content">
|
||||
<div *ngIf="!errorOgBoot && !loadingOgBoot" class="tab-content">
|
||||
<div class="section-header">
|
||||
<h2>OgBoot Server</h2>
|
||||
<button class="logs-button" (click)="scrollToLogs()">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>Ver logs</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<app-status-tab [loading]="loadingOgBoot" [diskUsage]="ogBootDiskUsage"
|
||||
[servicesStatus]="ogBootServicesStatus" [installedOgLives]="installedOgLives"
|
||||
[diskUsageChartData]="ogBootDiskUsageChartData" [view]="view" [colorScheme]="colorScheme"
|
||||
[isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="isDhcp" [isRepository]="false">
|
||||
</app-status-tab>
|
||||
|
||||
<div class="logs-section" id="ogboot-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>Logs de OgBoot</h3>
|
||||
<p>Logs en tiempo real del servidor OgBoot</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe
|
||||
[src]="getOgBootIframeUrl()"
|
||||
width="100%"
|
||||
height="800"
|
||||
frameborder="0"
|
||||
class="ogboot-logs-iframe">
|
||||
<iframe [src]="getOgBootIframeUrl()" width="100%" height="800" frameborder="0" class="ogboot-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -215,74 +210,106 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectedSection === 'dhcp'" class="section-content">
|
||||
<div *ngIf="!errorDhcp && !loadingDhcp" class="tab-content">
|
||||
<div class="section-header">
|
||||
<h2>DHCP Server</h2>
|
||||
<button class="logs-button" (click)="scrollToLogs()">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>Ver logs</span>
|
||||
</button>
|
||||
@if(selectedSection === 'dhcp'){
|
||||
<div class="section-content">
|
||||
<div class="repositories-container">
|
||||
@if(dhcps.length === 0){
|
||||
<div class="no-repositories">
|
||||
<mat-icon class="no-data-icon">cloud_off</mat-icon>
|
||||
<h3>No hay dhcps disponibles</h3>
|
||||
<p>No se encontraron dhcps configurados en el sistema.</p>
|
||||
</div>
|
||||
}
|
||||
@else {
|
||||
<div class="repositories-selector-container">
|
||||
<div class="repository-selector">
|
||||
<mat-form-field appearance="outline" class="repository-select-field">
|
||||
<mat-label>Seleccionar dhcp</mat-label>
|
||||
<mat-select [(ngModel)]="selectedDhcpUuid" (selectionChange)="onDhcpChange($event.value)">
|
||||
<mat-option *ngFor="let dhcp of dhcps" [value]="dhcp.uuid">
|
||||
{{ dhcp.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon matSuffix>storage</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<app-status-tab [loading]="loadingDhcp" [diskUsage]="dhcpDiskUsage" [servicesStatus]="dhcpServicesStatus"
|
||||
[subnets]="subnets" [diskUsageChartData]="dhcpDiskUsageChartData" [view]="view" [colorScheme]="colorScheme"
|
||||
[isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="true" [isRepository]="false">
|
||||
</app-status-tab>
|
||||
|
||||
<div class="logs-section" id="dhcp-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>Logs de DHCP</h3>
|
||||
<p>Logs en tiempo real del servidor DHCP</p>
|
||||
|
||||
@if(selectedDhcpUuid && !errorDhcps[selectedDhcpUuid] && dhcpStatuses[selectedDhcpUuid]){
|
||||
<div class="selected-repository-content">
|
||||
<div class="repository-item">
|
||||
<div class="section-header">
|
||||
<h2>{{ getSelectedDhcpName() }}</h2>
|
||||
<button class="logs-button" (click)="scrollToLogs()">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>Ver logs</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="repository-content">
|
||||
<app-status-tab [loading]="loadingDhcps" [diskUsage]="dhcpStatuses[selectedDhcpUuid].disk"
|
||||
[servicesStatus]="dhcpStatuses[selectedDhcpUuid].services" [subnets]="subnets"
|
||||
[diskUsageChartData]="dhcpStatuses[selectedDhcpUuid].diskUsageChartData" [view]="view"
|
||||
[colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="true"
|
||||
[isRepository]="false">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logs-section" id="dhcp-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>Logs de DHCP</h3>
|
||||
<p>Logs en tiempo real del servidor DHCP</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe [src]="getDhcpIframeUrl()" width="100%" height="800" frameborder="0" class="dhcp-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe
|
||||
[src]="getDhcpIframeUrl()"
|
||||
width="100%"
|
||||
height="800"
|
||||
frameborder="0"
|
||||
class="dhcp-logs-iframe">
|
||||
</iframe>
|
||||
}
|
||||
<div *ngIf="!selectedDhcpUuid" class="no-repository-selected">
|
||||
<mat-icon class="no-data-icon">storage</mat-icon>
|
||||
<h3>Selecciona un dhcp</h3>
|
||||
<p>Elige un dhcp de la lista para ver su estado detallado.</p>
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectedDhcpUuid && errorDhcps[selectedDhcpUuid]" class="error-container">
|
||||
<div class="error-card">
|
||||
<mat-icon class="error-icon">error_outline</mat-icon>
|
||||
<h3>Error de conexión</h3>
|
||||
<p>No se pudo conectar con el dhcp seleccionado</p>
|
||||
<button mat-raised-button color="primary" (click)="retryDhcpStatus(selectedDhcpUuid)">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
Reintentar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="loadingDhcp" class="loading-container">
|
||||
<div class="loading-content">
|
||||
<mat-spinner class="loading-spinner"></mat-spinner>
|
||||
<p class="loading-text">Cargando estado de DHCP...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="errorDhcp" class="error-container">
|
||||
<div class="error-card">
|
||||
<mat-icon class="error-icon">error_outline</mat-icon>
|
||||
<h3>Error de conexión</h3>
|
||||
<p>No se pudo conectar con el servidor DHCP</p>
|
||||
<button mat-raised-button color="primary" (click)="loadDhcpStatus()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
Reintentar
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div *ngIf="selectedSection === 'ogcore'" class="section-content">
|
||||
<div class="tab-content">
|
||||
<div class="section-header">
|
||||
<h2>OgCore Server</h2>
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectedSection === 'ogcore'" class="section-content">
|
||||
<div class="tab-content">
|
||||
<div class="section-header">
|
||||
<h2>OgCore Server</h2>
|
||||
</div>
|
||||
|
||||
<div class="logs-section" id="ogcore-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>Logs de OgCore</h3>
|
||||
<p>Logs en tiempo real del servidor OgCore</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe
|
||||
[src]="getOgCoreIframeUrl()"
|
||||
width="100%"
|
||||
height="800"
|
||||
frameborder="0"
|
||||
class="ogcore-logs-iframe">
|
||||
<iframe [src]="getOgCoreIframeUrl()" width="100%" height="800" frameborder="0" class="ogcore-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -290,6 +317,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</mat-dialog-content>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ export class GlobalStatusComponent implements OnInit {
|
|||
baseUrl: string;
|
||||
loading: boolean = false;
|
||||
loadingOgBoot: boolean = false;
|
||||
loadingDhcp: boolean = false;
|
||||
loadingDhcps: boolean = false;
|
||||
loadingRepositories: boolean = false;
|
||||
errorOgBoot: boolean = false;
|
||||
errorDhcp: boolean = false;
|
||||
errorDhcps: { [key: string]: boolean } = {};
|
||||
errorRepositories: { [key: string]: boolean } = {};
|
||||
installedOgLives: any[] = [];
|
||||
subnets: any[] = [];
|
||||
|
|
@ -38,10 +38,12 @@ export class GlobalStatusComponent implements OnInit {
|
|||
ogBootServicesStatus: any = {};
|
||||
ogBootDiskUsageChartData: any[] = [];
|
||||
|
||||
dhcpApiUrl: string;
|
||||
dhcpsApiUrl: string;
|
||||
dhcps: any[] = [];
|
||||
dhcpDiskUsage: any = {};
|
||||
dhcpServicesStatus: any = {};
|
||||
dhcpStatuses: { [key: string]: any } = {};
|
||||
dhcpDiskUsageChartData: any[] = [];
|
||||
selectedDhcpUuid: string = '';
|
||||
isDhcp: boolean = false;
|
||||
isRepository: boolean = false;
|
||||
|
||||
|
|
@ -62,40 +64,62 @@ export class GlobalStatusComponent implements OnInit {
|
|||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
this.ogBootApiUrl = `${this.baseUrl}/og-boot/status`;
|
||||
this.dhcpApiUrl = `${this.baseUrl}/og-dhcp/status`;
|
||||
this.dhcpsApiUrl = `${this.baseUrl}/dhcp-servers`;
|
||||
this.ogCoreApiUrl = `${this.baseUrl}`;
|
||||
this.repositoriesUrl = `${this.baseUrl}/image-repositories`;
|
||||
|
||||
|
||||
this.ogBootDiskUsageChartData = [];
|
||||
this.dhcpDiskUsageChartData = [];
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.updateLastUpdateTime();
|
||||
|
||||
|
||||
this.loadOgBootStatus();
|
||||
this.loadDhcpStatus();
|
||||
this.loadDhcps(false);
|
||||
this.loadRepositories(false);
|
||||
|
||||
|
||||
this.syncSubnets();
|
||||
this.syncTemplates();
|
||||
this.syncOgLives();
|
||||
}
|
||||
|
||||
syncSubnets() {
|
||||
const timeoutId = setTimeout(() => {
|
||||
this.toastService.error('Error al sincronizar las subredes: tiempo de espera agotado');
|
||||
}, 3500);
|
||||
this.http.get<any>(`${this.dhcpsApiUrl}?page=1&itemsPerPage=10`).subscribe(data => {
|
||||
const dhcps = data['hydra:member'] || [];
|
||||
if (dhcps.length === 0) return;
|
||||
|
||||
this.http.post(`${this.baseUrl}/subnets/sync`, {}).subscribe({
|
||||
next: (response) => {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.success('Sincronización con componente DHCP exitosa');
|
||||
},
|
||||
error: (error) => {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al sincronizar las subredes');
|
||||
}
|
||||
let pending = dhcps.length;
|
||||
let hasError = false;
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (!hasError) {
|
||||
this.toastService.error('Error al sincronizar las subredes: tiempo de espera agotado');
|
||||
hasError = true;
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
dhcps.forEach((dhcp: any) => {
|
||||
this.http.post(`${this.baseUrl}/dhcp-servers/${dhcp.uuid}/sync`, {}).subscribe({
|
||||
next: () => {
|
||||
pending--;
|
||||
if (pending === 0 && !hasError) {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.success('Sincronización de subredes de servidores DHCP exitosa');
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
pending--;
|
||||
if (!hasError) {
|
||||
hasError = true;
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.error(error.error['hydra:description'] || `Error al sincronizar subredes del DHCP ${dhcp.name || dhcp.uuid}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}, error => {
|
||||
console.error('Error fetching DHCP servers for subnet sync', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -133,11 +157,11 @@ export class GlobalStatusComponent implements OnInit {
|
|||
|
||||
loadStatus(apiUrl: string, diskUsage: any, servicesStatus: any, diskUsageChartData: any[], installedOgLives: any[], isDhcp: boolean, errorState: string, showLoading: boolean = true): void {
|
||||
if (isDhcp) {
|
||||
this.loadingDhcp = true;
|
||||
this.loadingDhcps = true;
|
||||
} else {
|
||||
this.loadingOgBoot = true;
|
||||
}
|
||||
|
||||
|
||||
if (showLoading) {
|
||||
this.loading = true;
|
||||
}
|
||||
|
|
@ -147,7 +171,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
this.loading = false;
|
||||
}
|
||||
if (isDhcp) {
|
||||
this.loadingDhcp = false;
|
||||
this.loadingDhcps = false;
|
||||
} else {
|
||||
this.loadingOgBoot = false;
|
||||
}
|
||||
|
|
@ -185,7 +209,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
this.loading = false;
|
||||
}
|
||||
if (isDhcp) {
|
||||
this.loadingDhcp = false;
|
||||
this.loadingDhcps = false;
|
||||
} else {
|
||||
this.loadingOgBoot = false;
|
||||
}
|
||||
|
|
@ -197,7 +221,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
this.loading = false;
|
||||
}
|
||||
if (isDhcp) {
|
||||
this.loadingDhcp = false;
|
||||
this.loadingDhcps = false;
|
||||
} else {
|
||||
this.loadingOgBoot = false;
|
||||
}
|
||||
|
|
@ -212,11 +236,11 @@ export class GlobalStatusComponent implements OnInit {
|
|||
this.loading = true;
|
||||
}
|
||||
this.errorRepositories = {};
|
||||
|
||||
|
||||
this.http.get<any>(`${this.repositoriesUrl}?page=1&itemsPerPage=10`).subscribe(
|
||||
data => {
|
||||
this.repositories = data['hydra:member'] || [];
|
||||
|
||||
|
||||
if (this.repositories.length === 0) {
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
|
|
@ -260,14 +284,14 @@ export class GlobalStatusComponent implements OnInit {
|
|||
}
|
||||
|
||||
loadRepositoryStatus(repositoryUuid: string, callback: (errorOccurred: boolean) => void): void {
|
||||
console.log(`Cargando estado del repositorio: ${repositoryUuid}`);
|
||||
console.log(`Cargando estado del repositorio: ${repositoryUuid}`);
|
||||
const timeoutId = setTimeout(() => {
|
||||
callback(true);
|
||||
}, 5000);
|
||||
|
||||
|
||||
this.http.get<any>(`${this.baseUrl}/image-repositories/server/${repositoryUuid}/status`).subscribe(
|
||||
data => {
|
||||
|
||||
|
||||
if (!data.success) {
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
|
|
@ -276,7 +300,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
|
||||
const details = data.details;
|
||||
const { disk, services, ram, cpu, processes } = details;
|
||||
|
||||
|
||||
this.repositoryStatuses[repositoryUuid] = {
|
||||
disk: {
|
||||
...disk,
|
||||
|
|
@ -314,9 +338,124 @@ export class GlobalStatusComponent implements OnInit {
|
|||
this.loadStatus(this.ogBootApiUrl, this.ogBootDiskUsage, this.ogBootServicesStatus, this.ogBootDiskUsageChartData, this.installedOgLives, this.isDhcp, 'errorOgBoot', false);
|
||||
}
|
||||
|
||||
loadDhcpStatus(): void {
|
||||
this.isDhcp = true;
|
||||
this.loadStatus(this.dhcpApiUrl, this.dhcpDiskUsage, this.dhcpServicesStatus, this.dhcpDiskUsageChartData, this.installedOgLives, this.isDhcp, 'errorDhcp', false);
|
||||
|
||||
loadDhcps(showLoading: boolean = true): void {
|
||||
if (showLoading) {
|
||||
this.loading = true;
|
||||
}
|
||||
this.errorDhcps = {};
|
||||
|
||||
this.http.get<any>(`${this.dhcpsApiUrl}?page=1&itemsPerPage=10`).subscribe(
|
||||
data => {
|
||||
this.dhcps = data['hydra:member'] || [];
|
||||
|
||||
if (this.dhcps.length === 0) {
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let remainingDhcps = this.dhcps.length;
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
this.dhcps.forEach(dhcp => {
|
||||
if (!(dhcp.uuid in this.errorDhcps)) {
|
||||
this.errorDhcps[dhcp.uuid] = true;
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
this.dhcps.forEach(dhcp => {
|
||||
this.loadDhcpStatus(dhcp.uuid, (errorOccurred: boolean) => {
|
||||
remainingDhcps--;
|
||||
|
||||
this.errorDhcps[dhcp.uuid] = errorOccurred;
|
||||
|
||||
if (remainingDhcps === 0) {
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.loading = false;
|
||||
this.dhcps = [];
|
||||
this.toastService.error('Error al cargar la lista de Dhcps');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadDhcpStatus(dhcpUuid: string, callback: (errorOccurred: boolean) => void): void {
|
||||
console.log(`Cargando estado del Dhcp: ${dhcpUuid}`);
|
||||
const timeoutId = setTimeout(() => {
|
||||
callback(true);
|
||||
}, 5000);
|
||||
|
||||
this.http.get<any>(`${this.baseUrl}/dhcp-servers/server/${dhcpUuid}/status`).subscribe(
|
||||
data => {
|
||||
|
||||
if (!data.success) {
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
const disk = data.message.disk_usage;
|
||||
const services = data.message.services_status;
|
||||
let diskUsageChartData: any[] = [];
|
||||
diskUsageChartData.length = 0;
|
||||
diskUsageChartData.push(
|
||||
{ name: 'Usado', value: parseFloat(disk.used) },
|
||||
{ name: 'Disponible', value: parseFloat(disk.available) }
|
||||
);
|
||||
|
||||
this.subnets.length = 0;
|
||||
if (data.message.subnets) {
|
||||
this.subnets.push(...data.message.subnets);
|
||||
}
|
||||
|
||||
this.dhcpStatuses[dhcpUuid] = {
|
||||
disk: {
|
||||
...disk,
|
||||
used: parseFloat(disk.used.replace('GB', '')),
|
||||
available: parseFloat(disk.available.replace('GB', '')),
|
||||
total: parseFloat(disk.total.replace('GB', '')),
|
||||
percentage: disk.used_percentage
|
||||
},
|
||||
services: services,
|
||||
diskUsageChartData: diskUsageChartData
|
||||
};
|
||||
clearTimeout(timeoutId);
|
||||
callback(false);
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al cargar el estado del dhcp');
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
onDhcpChange(dhcpUuid: string): void {
|
||||
this.selectedDhcpUuid = dhcpUuid;
|
||||
if (dhcpUuid && !this.dhcpStatuses[dhcpUuid]) {
|
||||
this.loadDhcpStatus(dhcpUuid, (errorOccurred: boolean) => {
|
||||
if (errorOccurred) {
|
||||
this.errorDhcps[dhcpUuid] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedDhcpName(): string {
|
||||
const selectedDhcp = this.dhcps.find(dhcp => dhcp.uuid === this.selectedDhcpUuid);
|
||||
return selectedDhcp ? selectedDhcp.name : '';
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -352,8 +491,14 @@ export class GlobalStatusComponent implements OnInit {
|
|||
}
|
||||
|
||||
getDhcpIframeUrl(): SafeResourceUrl {
|
||||
const url = `${this.configService.logUrl}/d-solo/ogdhcp-logs/ogdhcp-logs?orgId=1&timezone=browser&refresh=5s&theme=dark&panelId=1&__feature.dashboardSceneSolo`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
const dhcpName = this.getSelectedDhcpName();
|
||||
if (dhcpName) {
|
||||
const encodedName = encodeURIComponent(dhcpName);
|
||||
const url = `${this.configService.logUrl}/d-solo/ogdhcp-logs/ogdhcp-logs?orgId=1&timezone=browser&refresh=5s&var-query0=&editIndex=0&var-hostname=${encodedName}&theme=dark&panelId=1&__feature.dashboardSceneSolo`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
}
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl('');
|
||||
|
||||
}
|
||||
|
||||
getOgCoreIframeUrl(): SafeResourceUrl {
|
||||
|
|
@ -365,7 +510,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
this.loading = true;
|
||||
this.updateLastUpdateTime();
|
||||
this.loadStatus(this.ogBootApiUrl, this.ogBootDiskUsage, this.ogBootServicesStatus, this.ogBootDiskUsageChartData, this.installedOgLives, false, 'errorOgBoot', true);
|
||||
this.loadStatus(this.dhcpApiUrl, this.dhcpDiskUsage, this.dhcpServicesStatus, this.dhcpDiskUsageChartData, this.installedOgLives, true, 'errorDhcp', true);
|
||||
this.loadDhcps(true);
|
||||
this.loadRepositories(true);
|
||||
this.syncSubnets();
|
||||
this.syncTemplates();
|
||||
|
|
@ -402,11 +547,22 @@ export class GlobalStatusComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
retryDhcpStatus(dhcpUuid: string): void {
|
||||
this.loadDhcpStatus(dhcpUuid, (errorOccurred: boolean) => {
|
||||
this.errorDhcps[dhcpUuid] = errorOccurred;
|
||||
if (!errorOccurred) {
|
||||
this.toastService.success('Estado del dhcp actualizado correctamente');
|
||||
} else {
|
||||
this.toastService.error('Error al cargar el estado del dhcp');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
selectSection(section: 'repositories' | 'ogboot' | 'dhcp' | 'ogcore'): void {
|
||||
this.selectedSection = section;
|
||||
|
||||
|
||||
switch (section) {
|
||||
case 'repositories':
|
||||
break;
|
||||
|
|
@ -416,9 +572,6 @@ export class GlobalStatusComponent implements OnInit {
|
|||
}
|
||||
break;
|
||||
case 'dhcp':
|
||||
if (!this.dhcpDiskUsage) {
|
||||
this.loadDhcpStatus();
|
||||
}
|
||||
break;
|
||||
case 'ogcore':
|
||||
break;
|
||||
|
|
@ -444,7 +597,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const checkAndScroll = () => {
|
||||
const logsSection = document.getElementById(logsSectionId);
|
||||
if (logsSection) {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export class CreateSubnetComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.data) {
|
||||
if (this.data && this.data.uuid) {
|
||||
this.loadData();
|
||||
this.isEditMode = true;
|
||||
}
|
||||
|
|
@ -67,7 +67,11 @@ export class CreateSubnetComponent implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
const payload = this.subnetForm.value;
|
||||
const payload: any = { ...this.subnetForm.value };
|
||||
|
||||
if (!this.isEditMode && this.data?.serverUuid) {
|
||||
payload.dhcpServer = `/dhcp-servers/${this.data.serverUuid}`;
|
||||
}
|
||||
|
||||
if (!this.isEditMode) {
|
||||
this.http.post(`${this.baseUrl}/subnets`, payload).subscribe({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
.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;
|
||||
}
|
||||
|
||||
.images-button-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
background-color: #303f9f;
|
||||
}
|
||||
|
||||
.action-button:disabled {
|
||||
background-color: #9fa8da;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
}
|
||||
|
||||
.search-string {
|
||||
flex: 2;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="initTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="dhcpServerTitleStep" text="Vista de Servidores DHCP">
|
||||
Servidores DHCP
|
||||
</h2>
|
||||
</div>
|
||||
<div class="images-button-row">
|
||||
<button
|
||||
class="action-button" (click)="addServer()"
|
||||
joyrideStep="addStep"
|
||||
text="Añadir Servidor DHCP">Añadir Servidor DHCP</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container" joyrideStep="searchStep" text="Búsqueda rápida de servidores">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>Buscar por nombre</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>Buscar por IP</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['ip']" (keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableDateStep" text="Tabla de Servidores DHCP">
|
||||
@for (column of columns; track column.columnDef) {
|
||||
<ng-container [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let server">
|
||||
@if (column.columnDef !== 'subnets') {
|
||||
{{ column.cell(server) }}
|
||||
}
|
||||
|
||||
@if (column.columnDef === 'subnets') {
|
||||
<button
|
||||
class="action-button"
|
||||
(click)="openSubnetsView(server)">
|
||||
Ver Subredes
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let server" style="text-align: center;" joyrideStep="actionsStep">
|
||||
<button mat-icon-button color="primary" (click)="editServer($event, server)" matTooltip="Editar servidor">
|
||||
<mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="deleteServer($event, server)" matTooltip="Eliminar servidor">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
<div class="paginator-container" joyrideStep="paginationStep" text="Paginación de resultados">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="[5, 10, 20, 40, 100]"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
import {Component, OnInit} from '@angular/core';
|
||||
import {MatTableDataSource} from "@angular/material/table";
|
||||
import {DatePipe} from "@angular/common";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
import { Router } from '@angular/router';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import {ManageDhcpServerComponent} from "../manage-dhcp-server/manage-dhcp-server.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-dhcp-servers',
|
||||
templateUrl: './dhcp-servers.component.html',
|
||||
styleUrl: './dhcp-servers.component.css'
|
||||
})
|
||||
export class DhcpServersComponent implements OnInit {
|
||||
baseUrl: string;
|
||||
private apiUrl: string;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
length: number = 0;
|
||||
itemsPerPage: number = 20;
|
||||
page: number = 0;
|
||||
loading: boolean = false;
|
||||
filters: { [key: string]: string } = {};
|
||||
datePipe: DatePipe = new DatePipe('es-ES');
|
||||
columns = [
|
||||
{
|
||||
columnDef: 'id',
|
||||
header: 'Id',
|
||||
cell: (server: any) => `${server.id}`
|
||||
},
|
||||
{
|
||||
columnDef: 'name',
|
||||
header: 'Nombre',
|
||||
cell: (server: any) => server.name
|
||||
},
|
||||
{
|
||||
columnDef: 'ip',
|
||||
header: 'Ip',
|
||||
cell: (server: any) => server.ip
|
||||
},
|
||||
{
|
||||
columnDef: 'apiPort',
|
||||
header: 'Puerto API',
|
||||
cell: (server: any) => server.apiPort
|
||||
},
|
||||
{
|
||||
columnDef: 'subnets',
|
||||
header: 'Subredes',
|
||||
cell: (server: any) => server.subnets
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
header: 'Fecha de creación',
|
||||
cell: (server: any) => `${this.datePipe.transform(server.createdAt, 'dd/MM/yyyy hh:mm:ss')}`
|
||||
}
|
||||
];
|
||||
displayedColumns: string[] = ['id', 'name', 'ip', 'apiPort', 'subnets', 'createdAt', 'actions'];
|
||||
|
||||
constructor(
|
||||
public dialog: MatDialog,
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService,
|
||||
private router: Router,
|
||||
private configService: ConfigService
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
this.apiUrl = `${this.baseUrl}/dhcp-servers`;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.search();
|
||||
}
|
||||
|
||||
addServer(): void {
|
||||
const dialogRef = this.dialog.open(ManageDhcpServerComponent, {
|
||||
width: '600px'
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
this.search();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
search(): void {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.apiUrl}?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
|
||||
data => {
|
||||
this.dataSource.data = data['hydra:member'];
|
||||
this.length = data['hydra:totalItems'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
editServer(event: MouseEvent, server: any): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(ManageDhcpServerComponent, {
|
||||
width: '600px',
|
||||
data: server['@id']
|
||||
}).afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
this.search();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteServer(event: MouseEvent, server: any): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(DeleteModalComponent, {
|
||||
width: '300px',
|
||||
data: { name: server.name },
|
||||
}).afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
this.http.delete(`${this.apiUrl}/${server.uuid}`).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Servidor eliminado con éxito');
|
||||
this.search();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al eliminar servidor:', error);
|
||||
this.toastService.error('Error al eliminar el servidor');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openSubnetsView(server: any) {
|
||||
// Navigate to subnets view filtered by this server uuid
|
||||
this.router.navigate(['/subnets'], { queryParams: { serverId: server.uuid } });
|
||||
}
|
||||
|
||||
onPageChange(event: any): void {
|
||||
this.page = event.pageIndex;
|
||||
this.itemsPerPage = event.pageSize;
|
||||
this.length = event.length;
|
||||
this.search();
|
||||
}
|
||||
|
||||
initTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'dhcpServerTitleStep',
|
||||
'addStep',
|
||||
'searchStep',
|
||||
'tableDateStep',
|
||||
'actionsStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<h2 mat-dialog-title>{{ data ? 'Editar servidor DHCP' : 'Añadir servidor DHCP' }}</h2>
|
||||
<mat-dialog-content>
|
||||
<form [formGroup]="dhcpServerForm">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Nombre</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Dirección IP</mat-label>
|
||||
<input matInput formControlName="ip" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Puerto API</mat-label>
|
||||
<input matInput formControlName="apiPort" type="number" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Comentarios</mat-label>
|
||||
<textarea matInput formControlName="comments" rows="3"></textarea>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button (click)="close()">Cancelar</button>
|
||||
<button mat-button color="primary" [disabled]="dhcpServerForm.invalid" (click)="save()">
|
||||
Guardar
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { ConfigService } from "@services/config.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-dhcp-server',
|
||||
templateUrl: './manage-dhcp-server.component.html',
|
||||
styleUrl: './manage-dhcp-server.component.css'
|
||||
})
|
||||
export class ManageDhcpServerComponent implements OnInit {
|
||||
baseUrl: string;
|
||||
dhcpServerForm: FormGroup<any>;
|
||||
serverId: string | null = null;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private http: HttpClient,
|
||||
public dialogRef: MatDialogRef<ManageDhcpServerComponent>,
|
||||
private toastService: ToastrService,
|
||||
private configService: ConfigService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
this.dhcpServerForm = this.fb.group({
|
||||
name: [null, Validators.required],
|
||||
ip: [null, Validators.required],
|
||||
apiPort: [8081, Validators.required],
|
||||
comments: [null],
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.data) {
|
||||
this.load()
|
||||
}
|
||||
}
|
||||
|
||||
load(): void {
|
||||
// data comes as /dhcp-servers/{uuid}
|
||||
this.http.get<any>(`${this.baseUrl}${this.data}`).subscribe({
|
||||
next: (response) => {
|
||||
this.dhcpServerForm.patchValue({
|
||||
name: response.name,
|
||||
ip: response.ip,
|
||||
apiPort: response.apiPort ?? 8081,
|
||||
comments: response.comments,
|
||||
});
|
||||
this.serverId = response['@id'];
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error fetching dhcp server:', err);
|
||||
this.toastService.error('Error cargando los datos');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const payload = {
|
||||
name: this.dhcpServerForm.value.name,
|
||||
ip: this.dhcpServerForm.value.ip,
|
||||
apiPort: parseInt(this.dhcpServerForm.value.apiPort),
|
||||
comments: this.dhcpServerForm.value.comments,
|
||||
};
|
||||
|
||||
if (this.serverId) {
|
||||
this.http.put(`${this.baseUrl}${this.serverId}`, payload).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Servidor DHCP actualizado correctamente');
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error?.['hydra:description'] || 'Ha ocurrido un error al editar');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.http.post(`${this.baseUrl}/dhcp-servers`, payload).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Servidor DHCP añadido correctamente');
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error?.['hydra:description'] || 'Ha ocurrido un error al crear');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,13 +4,17 @@
|
|||
</button>
|
||||
<div class="header-container-title">
|
||||
<h2 class="title" i18n="@@subnetsTitle" joyrideStep="titleStep"
|
||||
text="Desde aquí puedes gestionar las subredes configuradas en el servidor OgDHCP.">Administrar Subredes</h2>
|
||||
text="Desde aquí puedes gestionar las subredes configuradas en el servidor OgDHCP.">Administrar Subredes {{ serverName ? 'de ' + serverName : '' }}</h2>
|
||||
</div>
|
||||
<div class="subnets-button-row">
|
||||
<button class="action-button" (click)="openSubnetInfoDialog()" joyrideStep="viewInfoStep"
|
||||
text="Haz clic para ver información detallada de las subredes en el servidor.">Ver Información</button>
|
||||
<button class="action-button" (click)="addSubnet()" joyrideStep="addSubnetStep"
|
||||
text="Haz clic para añadir una nueva subred.">Añadir Subred</button>
|
||||
[disabled]="!serverId"
|
||||
text="Haz clic para ver información detallada de las subredes en el servidor.">Ver Info Externa</button>
|
||||
<button class="action-button" (click)="syncSubnets()" style="margin-left: 0.5rem;"
|
||||
[disabled]="!serverId"
|
||||
text="Sincronizar las subredes configuradas externamente con oggui.">Sincronizar</button>
|
||||
<button class="action-button" (click)="addSubnet()" joyrideStep="addSubnetStep" style="margin-left: 0.5rem;"
|
||||
text="Haz clic para añadir una nueva subred.">Crear Subred Local</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -39,43 +43,47 @@
|
|||
</div>
|
||||
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
text="Visualiza y administra las subredes listadas según los filtros aplicados.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let subnet">
|
||||
<ng-container *ngIf="column.columnDef === 'synchronized'">
|
||||
<mat-icon [color]="subnet[column.columnDef] ? 'primary' : 'warn'">
|
||||
{{ subnet[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
@if (!loading) {
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
text="Visualiza y administra las subredes listadas según los filtros aplicados.">
|
||||
@for (column of columns; track column.columnDef) {
|
||||
<ng-container [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let subnet">
|
||||
@if (column.columnDef === 'synchronized') {
|
||||
<mat-icon [color]="subnet[column.columnDef] ? 'primary' : 'warn'">
|
||||
{{ subnet[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
}
|
||||
|
||||
@if (column.columnDef === 'clients') {
|
||||
<button class="action-button" (click)="openShowClientsDialog(subnet)" >Ver clientes</button>
|
||||
}
|
||||
|
||||
@if (column.columnDef !== 'synchronized' && column.columnDef !== 'clients') {
|
||||
{{ column.cell(subnet) }}
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<ng-container *ngIf="column.columnDef === 'clients'">
|
||||
<button class="action-button" (click)="openShowClientsDialog(subnet)" >Ver clientes</button>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let subnet" style="text-align: center;" joyrideStep="actionsStep"
|
||||
text="Gestiona cada subred con opciones para editar, eliminar y más.">
|
||||
<button mat-icon-button color="primary" (click)="editSubnet(subnet)" i18n="@@editSubnet">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="primary"
|
||||
(click)="addClientsToSubnet(subnet)"><mat-icon>computer</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="toggleAction(subnet, 'delete')"><mat-icon>delete</mat-icon></button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="column.columnDef !== 'synchronized' && column.columnDef !== 'clients'">
|
||||
{{ column.cell(subnet) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let subnet" style="text-align: center;" joyrideStep="actionsStep"
|
||||
text="Gestiona cada subred con opciones para editar, eliminar y más.">
|
||||
<button mat-icon-button color="primary" (click)="editSubnet(subnet)" i18n="@@editSubnet">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="primary"
|
||||
(click)="addClientsToSubnet(subnet)"><mat-icon>computer</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="toggleAction(subnet, 'delete')"><mat-icon>delete</mat-icon></button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
}
|
||||
|
||||
<div class="paginator-container" joyrideStep="paginationStep"
|
||||
text="Navega entre las páginas de subredes usando el paginador.">
|
||||
|
|
|
|||
|
|
@ -7,11 +7,14 @@ import { HttpClient } from '@angular/common/http';
|
|||
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";
|
||||
import { Observable } from "rxjs";
|
||||
import { Observable, of } from "rxjs";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
import { DeleteModalComponent } from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import { ShowClientsComponent } from "./show-clients/show-clients.component";
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { SyncSubnetDialogComponent } from "./sync-subnet-dialog/sync-subnet-dialog.component";
|
||||
import { SyncResultDialogComponent } from "./sync-result-dialog/sync-result-dialog.component";
|
||||
|
||||
export interface Subnet {
|
||||
'@id': string;
|
||||
|
|
@ -47,6 +50,8 @@ export class OgDhcpSubnetsComponent implements OnInit {
|
|||
pageSizeOptions: number[] = [5, 10, 20];
|
||||
alertMessage: string | null = null;
|
||||
loading: boolean = false;
|
||||
serverId: string | null = null;
|
||||
serverName: string = '';
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator | undefined;
|
||||
|
||||
|
|
@ -63,26 +68,54 @@ export class OgDhcpSubnetsComponent implements OnInit {
|
|||
private apiUrl: string;
|
||||
|
||||
constructor(public dialog: MatDialog, private http: HttpClient, private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService, private configService: ConfigService) {
|
||||
private joyrideService: JoyrideService, private configService: ConfigService, private route: ActivatedRoute) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
this.apiUrl = `${this.baseUrl}/subnets`;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadAlert()
|
||||
this.loadSubnets()
|
||||
this.route.queryParams.subscribe(params => {
|
||||
if (params['serverId']) {
|
||||
this.serverId = params['serverId'];
|
||||
this.filters['dhcpServer.uuid'] = params['serverId'];
|
||||
this.loadServerName();
|
||||
}
|
||||
this.loadAlert();
|
||||
this.loadSubnets();
|
||||
});
|
||||
}
|
||||
|
||||
loadServerName() {
|
||||
this.http.get<any>(`${this.baseUrl}/dhcp-servers/${this.serverId}`).subscribe({
|
||||
next: (response) => {
|
||||
this.serverName = response.name;
|
||||
},
|
||||
error: () => {
|
||||
console.error('No se pudo resolver el nombre del servidor DHCP');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadSubnets() {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.baseUrl}/subnets?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`).subscribe({
|
||||
let url = `${this.baseUrl}/subnets?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`;
|
||||
|
||||
// Añadir filtros a la URL
|
||||
Object.keys(this.filters).forEach(key => {
|
||||
if (this.filters[key]) {
|
||||
url += `&${key}=${this.filters[key]}`;
|
||||
}
|
||||
});
|
||||
|
||||
this.http.get<any>(url).subscribe({
|
||||
next: (response) => {
|
||||
this.dataSource.data = response['hydra:member'];
|
||||
this.length = response['hydra:totalItems'];
|
||||
this.loading = false;
|
||||
},
|
||||
error: error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
const errorMsg = error.error?.['hydra:description'] || 'Error al cargar subredes';
|
||||
this.toastService.error(errorMsg);
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
|
|
@ -92,6 +125,11 @@ export class OgDhcpSubnetsComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
search() {
|
||||
this.page = 0;
|
||||
this.loadSubnets();
|
||||
}
|
||||
|
||||
toggleAction(subnet: any, action: string): void {
|
||||
switch (action) {
|
||||
case 'get':
|
||||
|
|
@ -159,7 +197,8 @@ export class OgDhcpSubnetsComponent implements OnInit {
|
|||
|
||||
addSubnet() {
|
||||
const dialogRef = this.dialog.open(CreateSubnetComponent, {
|
||||
width: '600px'
|
||||
width: '600px',
|
||||
data: { serverUuid: this.serverId }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
|
|
@ -179,7 +218,46 @@ export class OgDhcpSubnetsComponent implements OnInit {
|
|||
}
|
||||
|
||||
loadAlert(): Observable<any> {
|
||||
return this.http.get<any>(`${this.baseUrl}/og-dhcp/server/get-collection`);
|
||||
if (this.serverId) {
|
||||
return this.http.post<any>(`${this.baseUrl}/dhcp-servers/${this.serverId}/get-collection`, {});
|
||||
}
|
||||
return of({message: 'Seleccione un servidor DHCP para consultar el estado'});
|
||||
}
|
||||
|
||||
syncSubnets(): void {
|
||||
if (!this.serverId) {
|
||||
this.toastService.error('Debe seleccionar un servidor DHCP para sincronizar');
|
||||
return;
|
||||
}
|
||||
|
||||
const dialogRef = this.dialog.open(SyncSubnetDialogComponent, {
|
||||
width: '600px',
|
||||
data: { serverUuid: this.serverId }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result === undefined) {
|
||||
return; // dialog cancelled
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
const payload = result.parentOuId ? { parentOuId: result.parentOuId } : {};
|
||||
|
||||
this.http.post(`${this.baseUrl}/dhcp-servers/${this.serverId}/sync`, payload).subscribe({
|
||||
next: (response: any) => {
|
||||
this.dialog.open(SyncResultDialogComponent, {
|
||||
width: '600px',
|
||||
data: { skippedClients: response.skipped_clients || [] }
|
||||
});
|
||||
this.loadSubnets();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al sincronizar subredes', error);
|
||||
this.toastService.error(error.error?.['hydra:description'] || 'Error al sincronizar subredes');
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
openSubnetInfoDialog() {
|
||||
|
|
|
|||
|
|
@ -1,107 +0,0 @@
|
|||
.disk-usage-info {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
padding: 0.5rem 0.5rem 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.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;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.btn:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="titleStep" text="Esta sección muestra el estado general del servidor OgDhcp.">
|
||||
OgDhcp server Status
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard">
|
||||
<div class="disk-usage-container">
|
||||
<div class="disk-usage" joyrideStep="diskUsageStep" text="Visualiza el uso del disco del servidor.">
|
||||
<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" joyrideStep="servicesStatusStep"
|
||||
text="Aquí puedes ver el estado de los servicios importantes del servidor.">
|
||||
<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" joyrideStep="subnetsStep"
|
||||
text="Consulta la información de las subredes configuradas en el servidor.">
|
||||
<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>
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { StatusComponent } from './status.component';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import {LoadingComponent} from "../../../shared/loading/loading.component";
|
||||
import { ConfigService } from '@services/config.service';
|
||||
|
||||
describe('StatusComponent', () => {
|
||||
let component: StatusComponent;
|
||||
let fixture: ComponentFixture<StatusComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockConfigService = {
|
||||
apiUrl: 'http://mock-api-url',
|
||||
mercureUrl: 'http://mock-mercure-url'
|
||||
};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [StatusComponent, LoadingComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
ToastrModule.forRoot(),
|
||||
BrowserAnimationsModule,
|
||||
MatDividerModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatTableModule,
|
||||
MatPaginatorModule,
|
||||
MatTooltipModule,
|
||||
FormsModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatOptionModule,
|
||||
ReactiveFormsModule,
|
||||
MatSelectModule,
|
||||
MatDialogModule,
|
||||
NgxChartsModule,
|
||||
TranslateModule.forRoot(),
|
||||
JoyrideModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: {} },
|
||||
{ provide: ConfigService, useValue: mockConfigService }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(StatusComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-status',
|
||||
templateUrl: './status.component.html',
|
||||
styleUrl: './status.component.css'
|
||||
})
|
||||
export class StatusComponent {
|
||||
baseUrl: string;
|
||||
diskUsage: any = {};
|
||||
servicesStatus: any = {};
|
||||
subnets: any[] = [];
|
||||
diskUsageChartData: any[] = [];
|
||||
loading: boolean = false;
|
||||
|
||||
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, private joyrideService: JoyrideService, private configService: ConfigService) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadStatus();
|
||||
}
|
||||
loadStatus(): void {
|
||||
this.loading = true;
|
||||
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)
|
||||
}
|
||||
];
|
||||
this.loading = false;
|
||||
}, error => {
|
||||
this.loading = false;
|
||||
console.error('Error fetching status', error);
|
||||
});
|
||||
}
|
||||
|
||||
getServices(): { name: string, status: string }[] {
|
||||
return Object.keys(this.servicesStatus).map(key => ({
|
||||
name: key,
|
||||
status: this.servicesStatus[key]
|
||||
}));
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'diskUsageStep',
|
||||
'servicesStatusStep',
|
||||
'subnetsStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
.sync-result-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sync-result-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #2e7d32;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.sync-result-header-text {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.collision-box {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
background-color: #fff3e0;
|
||||
border: 1px solid #ffe0b2;
|
||||
}
|
||||
|
||||
.collision-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #e65100;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.collision-header-text {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.collision-description {
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.collision-list-container {
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
background: white;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.collision-list {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<h2 mat-dialog-title>Reporte de Sincronización</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<div class="sync-result-header">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
<span class="sync-result-header-text">La red y las subredes han sido sincronizadas o importadas
|
||||
correctamente.</span>
|
||||
</div>
|
||||
|
||||
@if (data.skippedClients && data.skippedClients.length > 0) {
|
||||
<div class="collision-box">
|
||||
<div class="collision-header">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span class="collision-header-text">Colisiones Detectadas ({{ data.skippedClients.length }})</span>
|
||||
</div>
|
||||
<p class="collision-description">
|
||||
Los siguientes ordenadores ya existían previamente en la jerarquía (en otras aulas o Unidades). Sólo se ha
|
||||
actualizado su dependencia de subred, pero no se han movido de su ubicación actual.
|
||||
</p>
|
||||
|
||||
<div class="collision-list-container">
|
||||
<ul class="collision-list">
|
||||
@for (client of data.skippedClients; track client) {
|
||||
<li>{{ client }}</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions class="action-container" style="justify-content: flex-end;">
|
||||
<button class="submit-button" (click)="onClose()" cdkFocusInitial>Aceptar</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
export interface SyncResultData {
|
||||
skippedClients: string[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-sync-result-dialog',
|
||||
templateUrl: './sync-result-dialog.component.html',
|
||||
styleUrls: ['./sync-result-dialog.component.css']
|
||||
})
|
||||
export class SyncResultDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<SyncResultDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: SyncResultData
|
||||
) {}
|
||||
|
||||
onClose(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
.dialog-title {
|
||||
margin-bottom: 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<h2 mat-dialog-title>Sincronización Autodescubrimiento</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<p class="dialog-title">
|
||||
Seleccione una Unidad Organizativa Padre (OU) donde agrupar las subredes y clientes que se detecten automáticamente en el servicio DHCP.
|
||||
</p>
|
||||
|
||||
<div class="spacing-container">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Unidad Organizativa (Opcional)</mat-label>
|
||||
<mat-select [(ngModel)]="selectedOuUuid" [disabled]="loading">
|
||||
@for (ou of organizationalUnits; track ou.uuid) {
|
||||
<mat-option [value]="ou.uuid">
|
||||
{{ ou.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
@if (loading) {
|
||||
<mat-hint>Cargando...</mat-hint>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions class="action-container action-buttons">
|
||||
<button class="ordinary-button cancel-button" (click)="onCancel()">Cancelar</button>
|
||||
<button class="ordinary-button" (click)="onSyncWithoutOu()">Solo Subredes</button>
|
||||
<button class="submit-button" (click)="onSync()" [disabled]="!selectedOuUuid" cdkFocusInitial>Sincronizar todo</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sync-subnet-dialog',
|
||||
templateUrl: './sync-subnet-dialog.component.html',
|
||||
styleUrls: ['./sync-subnet-dialog.component.css']
|
||||
})
|
||||
export class SyncSubnetDialogComponent implements OnInit {
|
||||
organizationalUnits: any[] = [];
|
||||
selectedOuUuid: string | null = null;
|
||||
loading: boolean = false;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<SyncSubnetDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private http: HttpClient,
|
||||
private configService: ConfigService,
|
||||
private toastService: ToastrService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadParentOUs();
|
||||
}
|
||||
|
||||
loadParentOUs(): void {
|
||||
this.loading = true;
|
||||
const url = `${this.configService.apiUrl}/organizational-units?page=1&itemsPerPage=100`;
|
||||
|
||||
this.http.get<any>(url).subscribe({
|
||||
next: (response) => {
|
||||
// Filter out types that act as 'classrooms' assuming 'group' or 'classroom', we only want tree parents.
|
||||
// Actually, we can just list all OUs and let the admin decide where to put them, but conventionally groups are ignored.
|
||||
this.organizationalUnits = (response['hydra:member'] || []).filter((ou: any) => ou.type !== 'group' && ou.type !== 'classroom');
|
||||
this.loading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error('Error cargando las Unidades Organizativas');
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onCancel(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
onSync(): void {
|
||||
this.dialogRef.close({ parentOuId: this.selectedOuUuid });
|
||||
}
|
||||
|
||||
onSyncWithoutOu(): void {
|
||||
this.dialogRef.close({ parentOuId: null });
|
||||
}
|
||||
}
|
||||
|
|
@ -59,11 +59,11 @@
|
|||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
|
||||
<mat-list-item routerLink="/subnets" (click)="onItemClick()" matTooltip="{{ 'TOOLTIP_SUBNETS' | translate }}"
|
||||
<mat-list-item routerLink="/dhcp-servers" (click)="onItemClick()" matTooltip="{{ 'TOOLTIP_DHCP_SERVERS' | translate }}"
|
||||
matTooltipShowDelay="1000" *ngIf="auth.userCategory !== 'ou-operator' && auth.userCategory !== 'ou-minimal'">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">lan</mat-icon>
|
||||
<span> {{ 'subnets' | translate }}</span>
|
||||
<span> {{ 'dhcpServers' | translate }}</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
|
|
|
|||
|
|
@ -501,6 +501,8 @@
|
|||
"TOOLTIP_DHCP_STATUS": "Current status of the DHCP service",
|
||||
"subnets": "Subnets",
|
||||
"TOOLTIP_SUBNETS": "Manage and create subnets",
|
||||
"dhcpServers": "DHCP Servers",
|
||||
"TOOLTIP_DHCP_SERVERS": "Manage and register DHCP servers",
|
||||
"boot": "Boot",
|
||||
"TOOLTIP_BOOT": "Configure and manage boot options",
|
||||
"ogLive": "ogLive",
|
||||
|
|
|
|||
|
|
@ -502,6 +502,8 @@
|
|||
"TOOLTIP_DHCP_STATUS": "Estado actual del servicio DHCP",
|
||||
"subnets": "Subredes",
|
||||
"TOOLTIP_SUBNETS": "Gestionar y crear subredes",
|
||||
"dhcpServers": "Servidores DHCP",
|
||||
"TOOLTIP_DHCP_SERVERS": "Gestionar y registrar servidores DHCP",
|
||||
"boot": "Boot",
|
||||
"TOOLTIP_BOOT": "Configurar y administrar opciones de arranque",
|
||||
"ogLive": "ogLive",
|
||||
|
|
|
|||
Loading…
Reference in New Issue