Compare commits
20 Commits
Author | SHA1 | Date |
---|---|---|
|
1fbec200ab | |
|
253af06ad5 | |
|
5dc1677851 | |
|
73a69f79c9 | |
|
858a204036 | |
|
844d3dc0f0 | |
|
23bf2b51ea | |
|
e4e6a8907e | |
|
0096daca42 | |
|
de56a23a2b | |
|
3bae27d88e | |
|
265b4888c3 | |
|
ce1a06d51b | |
|
a0d833726d | |
|
c7919cf412 | |
|
cbe15bba4d | |
|
b6e8134810 | |
|
6205f3ad2f | |
|
436267cfb9 | |
|
e9a00119aa |
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,4 +1,19 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
## [0.11.2] - 2025-4-16
|
||||||
|
### Fixed
|
||||||
|
- Se ha corregido un error en la actualizacion del estado de los pcs en la vista tarjetas.
|
||||||
|
|
||||||
|
---
|
||||||
|
## [0.11.1] - 2025-4-16
|
||||||
|
### Improved
|
||||||
|
- Nuevos campos en la tabla de clientes. Tipo de firmware y mac.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Se ha corregido error al crear OUs, que no refrescaba la web.
|
||||||
|
- Se ha corregido error en el formulario de creacion de imagenes. Si se seleccionaba una imagen para un versionado, no dejaba deseleccionar.
|
||||||
|
- Se ha corregido un bug en el particionador que impedia ejecutar, cuando eliminabamos una particion.
|
||||||
|
|
||||||
|
---
|
||||||
## [0.11.0] - 2025-4-11
|
## [0.11.0] - 2025-4-11
|
||||||
### Added
|
### Added
|
||||||
- Se ha diseñado el nuevo formulario para poder ejecutar script. Sistema mejorado con variables etiquetadas.
|
- Se ha diseñado el nuevo formulario para poder ejecutar script. Sistema mejorado con variables etiquetadas.
|
||||||
|
|
|
@ -9,5 +9,5 @@ Package: oggui
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Maintainer: Nicolas Arenas <nicolas.arenas@qindel.com>
|
Maintainer: Nicolas Arenas <nicolas.arenas@qindel.com>
|
||||||
Depends: ${shlibs:Depends}, ${misc:Depends}, nginx
|
Depends: ${shlibs:Depends}, ${misc:Depends}, nginx
|
||||||
Description: OpenGnsys GUI
|
Description: OpenGnsys GUI created for the Opengnsys Team
|
||||||
Una interfaz gráfica para OpenGnsys.
|
Opengnsys Graphical Intercface
|
||||||
|
|
|
@ -13,6 +13,25 @@ OGMERCURE_URL="$RET"
|
||||||
USER="opengnsys"
|
USER="opengnsys"
|
||||||
CONFIG_FILE="/opt/opengnsys/oggui/browser/assets/config.json"
|
CONFIG_FILE="/opt/opengnsys/oggui/browser/assets/config.json"
|
||||||
|
|
||||||
|
restore_config_if_modified() {
|
||||||
|
local new="$1"
|
||||||
|
local backup="$1.bak"
|
||||||
|
|
||||||
|
if [ -f "$backup" ]; then
|
||||||
|
if ! cmp -s "$new" "$backup"; then
|
||||||
|
echo ">>> Archivo modificado por el usuario detectado en $new"
|
||||||
|
echo " - Guardando archivo nuevo como ${new}.new"
|
||||||
|
mv -f "$new" "${new}.new"
|
||||||
|
echo " - Restaurando archivo anterior desde backup"
|
||||||
|
mv -f "$backup" "$new"
|
||||||
|
else
|
||||||
|
echo ">>> El archivo $new no ha cambiado desde la última versión, eliminando backup"
|
||||||
|
rm -f "$backup"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Detectar si es una instalación nueva o una actualización
|
# Detectar si es una instalación nueva o una actualización
|
||||||
if [ "$1" = "configure" ] && [ -z "$2" ]; then
|
if [ "$1" = "configure" ] && [ -z "$2" ]; then
|
||||||
if [ ! -f "$CONFIG_FILE" ]; then
|
if [ ! -f "$CONFIG_FILE" ]; then
|
||||||
|
@ -29,6 +48,11 @@ if [ "$1" = "configure" ] && [ -z "$2" ]; then
|
||||||
elif [ "$1" = "configure" ] && [ -n "$2" ]; then
|
elif [ "$1" = "configure" ] && [ -n "$2" ]; then
|
||||||
cd /opt/opengnsys/oggui
|
cd /opt/opengnsys/oggui
|
||||||
echo "Actualización desde la versión $2"
|
echo "Actualización desde la versión $2"
|
||||||
|
# Si upgrade recupero los archivos de configuracion
|
||||||
|
echo ">>> Backup de archivos de configuración reales en /opt/opengnsys"
|
||||||
|
restore_config_if_modified "/opt/opengnsys/oggui/etc/nginx/oggui.conf"
|
||||||
|
restore_config_if_modified "$CONFIG_FILE"
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Cambiar la propiedad de los archivos al usuario especificado
|
# Cambiar la propiedad de los archivos al usuario especificado
|
||||||
|
|
|
@ -2,6 +2,16 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
backup_file_if_exists() {
|
||||||
|
local original="$1"
|
||||||
|
local backup="$1.bak"
|
||||||
|
|
||||||
|
if [ -e "$original" ]; then
|
||||||
|
echo " - Guardando backup de $original en $backup"
|
||||||
|
cp -a "$original" "$backup"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
CONFIG_FILE="/opt/opengnsys/oggui/browser/assets/config.json"
|
||||||
# Asegurarse de que el usuario exista
|
# Asegurarse de que el usuario exista
|
||||||
USER="opengnsys"
|
USER="opengnsys"
|
||||||
HOME_DIR="/opt/opengnsys"
|
HOME_DIR="/opt/opengnsys"
|
||||||
|
@ -12,4 +22,11 @@ else
|
||||||
useradd -m -d "$HOME_DIR" -s /bin/bash "$USER"
|
useradd -m -d "$HOME_DIR" -s /bin/bash "$USER"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Si upgrade hago backup del archivo de configuración
|
||||||
|
if [ "$1" = "upgrade" ]; then
|
||||||
|
echo ">>> Backup de archivos de configuración reales en /opt/opengnsys"
|
||||||
|
backup_file_if_exists "/opt/opengnsys/oggui/etc/nginx/sites-available/oggui.conf"
|
||||||
|
backup_file_if_exists "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
exit 0
|
exit 0
|
|
@ -8,12 +8,17 @@
|
||||||
[matMenuTriggerFor]="commandMenu">
|
[matMenuTriggerFor]="commandMenu">
|
||||||
{{ buttonText }}
|
{{ buttonText }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button mat-menu-item *ngSwitchCase="'menu-item'" [matMenuTriggerFor]="commandMenu" [disabled]="disabled">
|
||||||
|
<mat-icon>{{ icon }}</mat-icon>
|
||||||
|
<span>{{ buttonText }}</span>
|
||||||
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<mat-menu #commandMenu="matMenu">
|
<mat-menu #commandMenu="matMenu">
|
||||||
<button mat-menu-item [disabled]="command.disabled
|
<button mat-menu-item [disabled]="command.disabled
|
||||||
|| (command.slug === 'create-image' && clientData.length > 1)"
|
|| (command.slug === 'create-image' && clientData.length > 1)" *ngFor="let command of arrayCommands"
|
||||||
*ngFor="let command of arrayCommands" (click)="onCommandSelect(command.slug)">
|
(click)="onCommandSelect(command.slug)">
|
||||||
{{ command.name }}
|
{{ command.name }}
|
||||||
</button>
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
|
@ -11,7 +11,7 @@ import { ConfigService } from '@services/config.service';
|
||||||
})
|
})
|
||||||
export class ExecuteCommandComponent implements OnInit {
|
export class ExecuteCommandComponent implements OnInit {
|
||||||
@Input() clientData: any[] = [];
|
@Input() clientData: any[] = [];
|
||||||
@Input() buttonType: 'icon' | 'text' = 'icon';
|
@Input() buttonType: 'icon' | 'text' | 'menu-item' = 'icon';
|
||||||
@Input() buttonText: string = 'Ejecutar Comandos';
|
@Input() buttonText: string = 'Ejecutar Comandos';
|
||||||
@Input() icon: string = 'terminal';
|
@Input() icon: string = 'terminal';
|
||||||
@Input() disabled: boolean = false;
|
@Input() disabled: boolean = false;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { HttpClient } from '@angular/common/http';
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ConfigService } from '@services/config.service';
|
import { ConfigService } from '@services/config.service';
|
||||||
import { MatTabChangeEvent } from '@angular/material/tabs';
|
import { MatTabChangeEvent } from '@angular/material/tabs';
|
||||||
|
import {ToastrService} from "ngx-toastr";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-global-status',
|
selector: 'app-global-status',
|
||||||
|
@ -40,6 +41,7 @@ export class GlobalStatusComponent implements OnInit {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
|
private toastService: ToastrService,
|
||||||
private http: HttpClient
|
private http: HttpClient
|
||||||
) {
|
) {
|
||||||
this.baseUrl = this.configService.apiUrl;
|
this.baseUrl = this.configService.apiUrl;
|
||||||
|
@ -50,6 +52,56 @@ export class GlobalStatusComponent implements OnInit {
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.loadOgBootStatus();
|
this.loadOgBootStatus();
|
||||||
|
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.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 al sincronizar las subredes DHCP');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
syncTemplates() {
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
this.toastService.error('Error al sincronizar las plantillas Pxe: tiempo de espera agotado');
|
||||||
|
}, 3500);
|
||||||
|
|
||||||
|
this.http.post(`${this.baseUrl}/pxe-templates/sync`, {})
|
||||||
|
.subscribe(response => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
this.toastService.success('Sincronización de las plantillas Pxe completada');
|
||||||
|
}, error => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
this.toastService.error('Error al sincronizar las plantillas Pxe');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
syncOgLives(): void {
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
this.toastService.error('Error al sincronizar las imagenes ogLive : tiempo de espera agotado');
|
||||||
|
}, 3500);
|
||||||
|
|
||||||
|
this.http.post(`${this.baseUrl}/og-lives/sync`, {})
|
||||||
|
.subscribe(response => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
this.toastService.success('Sincronización con los ogLives completada');
|
||||||
|
}, error => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
this.toastService.error('Error al sincronizar imágenes ogLive');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
|
@ -24,6 +24,10 @@
|
||||||
<mat-select [(ngModel)]="selectedImage" name="selectedImage" (selectionChange)="resetCanonicalName()" required>
|
<mat-select [(ngModel)]="selectedImage" name="selectedImage" (selectionChange)="resetCanonicalName()" required>
|
||||||
<mat-option *ngFor="let image of images" [value]="image">{{ image?.name }}</mat-option>
|
<mat-option *ngFor="let image of images" [value]="image">{{ image?.name }}</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
|
<button *ngIf="selectedImage" mat-icon-button matSuffix aria-label="Clear client search"
|
||||||
|
(click)="selectedImage = null; resetCanonicalName()">
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
<mat-hint>Seleccione la imagen para sobreescribir si se requiere. </mat-hint>
|
<mat-hint>Seleccione la imagen para sobreescribir si se requiere. </mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -288,7 +288,9 @@ export class PartitionAssistantComponent {
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
const totalPartitionSize = this.selectedDisk.partitions.reduce((sum: any, partition: { size: any; }) => sum + partition.size, 0);
|
const totalPartitionSize = this.selectedDisk.partitions
|
||||||
|
.filter((partition: any) => !partition.removed)
|
||||||
|
.reduce((sum: any, partition: any) => sum + partition.size, 0);
|
||||||
|
|
||||||
if (totalPartitionSize > this.selectedDisk.totalDiskSize) {
|
if (totalPartitionSize > this.selectedDisk.totalDiskSize) {
|
||||||
this.toastService.error('El tamaño total de las particiones en el disco seleccionado excede el tamaño total del disco.');
|
this.toastService.error('El tamaño total de las particiones en el disco seleccionado excede el tamaño total del disco.');
|
||||||
|
|
|
@ -198,6 +198,9 @@
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
<span>{{ 'delete' | translate }}</span>
|
<span>{{ 'delete' | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<app-execute-command [clientData]="selectedNode?.clients || []" [buttonType]="'menu-item'"
|
||||||
|
[buttonText]="'Ejecutar comandos'" [icon]="'terminal'" [disabled]="!((selectedNode?.clients ?? []).length > 0)">
|
||||||
|
</app-execute-command>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -245,7 +248,8 @@
|
||||||
<div *ngFor="let client of arrayClients" class="client-item">
|
<div *ngFor="let client of arrayClients" class="client-item">
|
||||||
<div class="client-card">
|
<div class="client-card">
|
||||||
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(client)"
|
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(client)"
|
||||||
[checked]="selection.isSelected(client)" [disabled]="client.status === 'busy' || client.status === 'off' || client.status === 'disconnected'">
|
[checked]="selection.isSelected(client)"
|
||||||
|
[disabled]="client.status === 'busy' || client.status === 'off' || client.status === 'disconnected'">
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
<img style="margin-top: 0.5em;" [src]="'assets/images/computer_' + client.status + '.svg'"
|
<img style="margin-top: 0.5em;" [src]="'assets/images/computer_' + client.status + '.svg'"
|
||||||
alt="Client Icon" class="client-image" />
|
alt="Client Icon" class="client-image" />
|
||||||
|
@ -314,7 +318,8 @@
|
||||||
</th>
|
</th>
|
||||||
<td mat-cell *matCellDef="let row">
|
<td mat-cell *matCellDef="let row">
|
||||||
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(row)"
|
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(row)"
|
||||||
[checked]="selection.isSelected(row)" [disabled]="row.status === 'busy' || row.status === 'off' || row.status === 'disconnected'">
|
[checked]="selection.isSelected(row)"
|
||||||
|
[disabled]="row.status === 'busy' || row.status === 'off' || row.status === 'disconnected'">
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -343,9 +348,22 @@
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>IP </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>IP </th>
|
||||||
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}"
|
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}"
|
||||||
matTooltipPosition="left" matTooltipShowDelay="500">
|
matTooltipPosition="left" matTooltipShowDelay="500">
|
||||||
{{ client.ip }}
|
<div style="display: flex; flex-direction: column;">
|
||||||
|
<span>{{ client.ip }}</span>
|
||||||
|
<span style="font-size: 0.75rem; color: gray;">{{ client.mac }}</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="firmwareType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'firmwareType' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let client">
|
||||||
|
<mat-chip s>
|
||||||
|
{{ client.firmwareType ? client.firmwareType : 'N/A' }}
|
||||||
|
</mat-chip>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="oglive">
|
<ng-container matColumnDef="oglive">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> OG Live </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> OG Live </th>
|
||||||
<td mat-cell *matCellDef="let client"> {{ client.ogLive?.date | date }} </td>
|
<td mat-cell *matCellDef="let client"> {{ client.ogLive?.date | date }} </td>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, OnInit, OnDestroy, ViewChild, QueryList, ViewChildren } from '@angular/core';
|
import {Component, OnInit, OnDestroy, ViewChild, QueryList, ViewChildren, ChangeDetectorRef} from '@angular/core';
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
@ -87,7 +87,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
{ value: 'mac', name: 'Mac' },
|
{ value: 'mac', name: 'Mac' },
|
||||||
];
|
];
|
||||||
|
|
||||||
displayedColumns: string[] = ['select', 'status', 'ip', 'name', 'oglive', 'subnet', 'pxeTemplate', 'actions'];
|
displayedColumns: string[] = ['select', 'status', 'ip', 'firmwareType', 'name', 'oglive', 'subnet', 'pxeTemplate', 'actions'];
|
||||||
|
|
||||||
private _sort!: MatSort;
|
private _sort!: MatSort;
|
||||||
|
|
||||||
|
@ -110,7 +110,8 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
private joyrideService: JoyrideService,
|
private joyrideService: JoyrideService,
|
||||||
private breakpointObserver: BreakpointObserver,
|
private breakpointObserver: BreakpointObserver,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
private configService: ConfigService
|
private configService: ConfigService,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
) {
|
) {
|
||||||
this.baseUrl = this.configService.apiUrl;
|
this.baseUrl = this.configService.apiUrl;
|
||||||
this.mercureUrl = this.configService.mercureUrl;
|
this.mercureUrl = this.configService.mercureUrl;
|
||||||
|
@ -175,23 +176,35 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateClientStatus(clientUuid: string, newStatus: string): void {
|
private updateClientStatus(clientUuid: string, status: string): void {
|
||||||
const clientIndex = this.selectedClients.data.findIndex(client => client['@id'] === clientUuid);
|
let updated = false;
|
||||||
|
|
||||||
if (clientIndex !== -1) {
|
const index = this.arrayClients.findIndex(client => client['@id'] === clientUuid);
|
||||||
|
if (index !== -1) {
|
||||||
|
const updatedClient = {...this.arrayClients[index], status};
|
||||||
|
this.arrayClients = [
|
||||||
|
...this.arrayClients.slice(0, index),
|
||||||
|
updatedClient,
|
||||||
|
...this.arrayClients.slice(index + 1)
|
||||||
|
];
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableIndex = this.selectedClients.data.findIndex(client => client['@id'] === clientUuid);
|
||||||
|
|
||||||
|
if (tableIndex !== -1) {
|
||||||
const updatedClients = [...this.selectedClients.data];
|
const updatedClients = [...this.selectedClients.data];
|
||||||
|
|
||||||
updatedClients[clientIndex] = {
|
updatedClients[tableIndex] = {
|
||||||
...updatedClients[clientIndex],
|
...updatedClients[tableIndex],
|
||||||
status: newStatus
|
status: status
|
||||||
};
|
};
|
||||||
|
|
||||||
this.selectedClients.data = updatedClients;
|
this.selectedClients.data = updatedClients;
|
||||||
this.arrayClients = updatedClients;
|
}
|
||||||
|
|
||||||
console.log(`Estado actualizado para el cliente ${clientUuid}: ${newStatus}`);
|
if (updated) {
|
||||||
} else {
|
this.cd.detectChanges();
|
||||||
console.warn(`Cliente con UUID ${clientUuid} no encontrado en la lista.`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,13 +382,15 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
onNodeClick(event: MouseEvent, node: TreeNode): void {
|
onNodeClick(event: MouseEvent, node: TreeNode): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.selectedNode = node;
|
this.selectedNode = node;
|
||||||
this.fetchClientsForNode(node);
|
const selectedClientsBeforeEdit = this.selection.selected.map(client => client.uuid);
|
||||||
|
this.fetchClientsForNode(node, selectedClientsBeforeEdit);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMenuClick(event: Event, node: any): void {
|
onMenuClick(event: Event, node: any): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.selectedNode = node;
|
this.selectedNode = node;
|
||||||
this.fetchClientsForNode(node);
|
const selectedClientsBeforeEdit = this.selection.selected.map(client => client.uuid);
|
||||||
|
this.fetchClientsForNode(node, selectedClientsBeforeEdit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -386,6 +401,9 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}&page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params }).subscribe({
|
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}&page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params }).subscribe({
|
||||||
next: (response: any) => {
|
next: (response: any) => {
|
||||||
this.selectedClients.data = response['hydra:member'];
|
this.selectedClients.data = response['hydra:member'];
|
||||||
|
if (this.selectedNode) {
|
||||||
|
this.selectedNode.clients = response['hydra:member'];
|
||||||
|
}
|
||||||
this.length = response['hydra:totalItems'];
|
this.length = response['hydra:totalItems'];
|
||||||
this.arrayClients = this.selectedClients.data;
|
this.arrayClients = this.selectedClients.data;
|
||||||
this.hasClients = this.selectedClients.data.length > 0;
|
this.hasClients = this.selectedClients.data.length > 0;
|
||||||
|
@ -419,7 +437,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
width: '900px',
|
width: '900px',
|
||||||
});
|
});
|
||||||
dialogRef.afterClosed().subscribe((newUnit) => {
|
dialogRef.afterClosed().subscribe((newUnit) => {
|
||||||
if (newUnit?.uuid) {
|
if (newUnit) {
|
||||||
this.refreshData(newUnit.uuid);
|
this.refreshData(newUnit.uuid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -105,6 +105,7 @@ export class ManageClientComponent implements OnInit {
|
||||||
this.parentUnitsWithPaths = this.parentUnits.map(unit => ({
|
this.parentUnitsWithPaths = this.parentUnits.map(unit => ({
|
||||||
id: unit['@id'],
|
id: unit['@id'],
|
||||||
name: unit.name,
|
name: unit.name,
|
||||||
|
netiface: unit.networkSettings?.netiface,
|
||||||
path: this.dataService.getOrganizationalUnitPath(unit, this.parentUnits),
|
path: this.dataService.getOrganizationalUnitPath(unit, this.parentUnits),
|
||||||
repository: unit.networkSettings?.repository?.['@id'],
|
repository: unit.networkSettings?.repository?.['@id'],
|
||||||
hardwareProfile: unit.networkSettings?.hardwareProfile?.['@id'],
|
hardwareProfile: unit.networkSettings?.hardwareProfile?.['@id'],
|
||||||
|
@ -226,7 +227,8 @@ export class ManageClientComponent implements OnInit {
|
||||||
repository: selectedUnit.repository || null,
|
repository: selectedUnit.repository || null,
|
||||||
hardwareProfile: selectedUnit.hardwareProfile || null,
|
hardwareProfile: selectedUnit.hardwareProfile || null,
|
||||||
ogLive: selectedUnit.ogLive || null,
|
ogLive: selectedUnit.ogLive || null,
|
||||||
menu: selectedUnit.menu || null
|
menu: selectedUnit.menu || null,
|
||||||
|
netiface: selectedUnit.netiface || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
<app-loading [isLoading]="loading"></app-loading>
|
|
||||||
|
|
||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||||
<mat-icon>help</mat-icon>
|
<mat-icon>help</mat-icon>
|
||||||
|
|
|
@ -79,7 +79,6 @@ export class PXEimagesComponent implements OnInit {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.search();
|
this.search();
|
||||||
this.loadAlert();
|
this.loadAlert();
|
||||||
this.syncOgBoot()
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,17 +244,6 @@ export class PXEimagesComponent implements OnInit {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
syncOgBoot(): void {
|
|
||||||
this.http.post(`${this.apiUrl}/sync`, {})
|
|
||||||
.subscribe(response => {
|
|
||||||
this.toastService.success('Sincronización con oGBoot exitosa');
|
|
||||||
this.search()
|
|
||||||
}, error => {
|
|
||||||
console.error('Error al sincronizar', error);
|
|
||||||
this.toastService.error('Error al sincronizar');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
iniciarTour(): void {
|
iniciarTour(): void {
|
||||||
this.joyrideService.startTour({
|
this.joyrideService.startTour({
|
||||||
steps: [
|
steps: [
|
||||||
|
|
|
@ -35,9 +35,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-loading [isLoading]="loading"></app-loading>
|
<app-loading [isLoading]="loading"></app-loading>
|
||||||
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||||
text="{{ 'tableDescription' | translate }}">
|
text="{{ 'tableDescription' | translate }}">
|
||||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ column.header }}</th>
|
<th mat-header-cell *matHeaderCellDef>{{ column.header }}</th>
|
||||||
<td mat-cell *matCellDef="let image">
|
<td mat-cell *matCellDef="let image">
|
||||||
<ng-container *ngIf="column.columnDef === 'synchronized'">
|
<ng-container *ngIf="column.columnDef === 'synchronized'">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Component } from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import { CreatePxeTemplateComponent } from './create-pxeTemplate/create-pxe-template.component';
|
import { CreatePxeTemplateComponent } from './create-pxeTemplate/create-pxe-template.component';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
|
@ -19,7 +19,7 @@ import { ConfigService } from '@services/config.service';
|
||||||
templateUrl: './pxe.component.html',
|
templateUrl: './pxe.component.html',
|
||||||
styleUrls: ['./pxe.component.css']
|
styleUrls: ['./pxe.component.css']
|
||||||
})
|
})
|
||||||
export class PxeComponent {
|
export class PxeComponent implements OnInit{
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
private apiUrl: string;
|
private apiUrl: string;
|
||||||
pxeTemplates: any[] = [];
|
pxeTemplates: any[] = [];
|
||||||
|
@ -76,7 +76,6 @@ export class PxeComponent {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.search();
|
this.search();
|
||||||
this.loadAlert()
|
this.loadAlert()
|
||||||
this.syncTemplates()
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,17 +146,6 @@ export class PxeComponent {
|
||||||
const dialogRef = this.dialog.open(ShowTemplateContentComponent, { data: { data }, width: '700px' });
|
const dialogRef = this.dialog.open(ShowTemplateContentComponent, { data: { data }, width: '700px' });
|
||||||
}
|
}
|
||||||
|
|
||||||
syncTemplates() {
|
|
||||||
this.http.post(`${this.apiUrl}/sync`, {})
|
|
||||||
.subscribe(response => {
|
|
||||||
this.toastService.success('Sincronización completada');
|
|
||||||
this.search()
|
|
||||||
}, error => {
|
|
||||||
console.error('Error al sincronizar', error);
|
|
||||||
this.toastService.error('Error al sincronizar');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
applyFilter() {
|
applyFilter() {
|
||||||
this.http.get<any>(`${this.apiUrl}?page=${this.page}&itemsPerPage=${this.itemsPerPage}`).subscribe({
|
this.http.get<any>(`${this.apiUrl}?page=${this.page}&itemsPerPage=${this.itemsPerPage}`).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
|
|
|
@ -72,10 +72,4 @@ describe('OgDhcpSubnetsComponent', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call syncSubnets and handle success', () => {
|
|
||||||
component.syncSubnets();
|
|
||||||
expect(mockHttpClient.post).toHaveBeenCalledWith(`${component.baseUrl}/subnets/sync`, {});
|
|
||||||
expect(mockToastrService.success).toHaveBeenCalledWith('Sincronización con componente DHCP exitosa');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -69,19 +69,21 @@ export class OgDhcpSubnetsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.loading = true;
|
|
||||||
this.loadAlert()
|
this.loadAlert()
|
||||||
this.syncSubnets()
|
this.loadSubnets()
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSubnets() {
|
loadSubnets() {
|
||||||
|
this.loading = true;
|
||||||
this.http.get<any>(`${this.baseUrl}/subnets?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`).subscribe({
|
this.http.get<any>(`${this.baseUrl}/subnets?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
this.dataSource.data = response['hydra:member'];
|
this.dataSource.data = response['hydra:member'];
|
||||||
this.length = response['hydra:totalItems'];
|
this.length = response['hydra:totalItems'];
|
||||||
|
this.loading = false;
|
||||||
},
|
},
|
||||||
error: error => {
|
error: error => {
|
||||||
this.toastService.error(error.error['hydra:description']);
|
this.toastService.error(error.error['hydra:description']);
|
||||||
|
this.loading = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -90,28 +92,6 @@ export class OgDhcpSubnetsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syncSubnets() {
|
|
||||||
this.loading = true;
|
|
||||||
const timeoutId = setTimeout(() => {
|
|
||||||
this.loading = false;
|
|
||||||
this.toastService.error('Error al sincronizar: tiempo de espera agotado');
|
|
||||||
}, 3500);
|
|
||||||
|
|
||||||
this.http.post(`${this.apiUrl}/sync`, {}).subscribe({
|
|
||||||
next: (response) => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
this.toastService.success('Sincronización con componente DHCP exitosa');
|
|
||||||
this.loadSubnets();
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
error: (error) => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
this.loading = false;
|
|
||||||
this.toastService.error('Error al sincronizar');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleAction(subnet: any, action: string): void {
|
toggleAction(subnet: any, action: string): void {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'get':
|
case 'get':
|
||||||
|
|
|
@ -60,7 +60,7 @@ export class RepositoriesComponent implements OnInit {
|
||||||
cell: (repository: any) => `${this.datePipe.transform(repository.createdAt, 'dd/MM/yyyy hh:mm:ss')}`
|
cell: (repository: any) => `${this.datePipe.transform(repository.createdAt, 'dd/MM/yyyy hh:mm:ss')}`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
isGitModuleInstalled: boolean = true;
|
isGitModuleInstalled: boolean = false;
|
||||||
displayedColumns: string[] = ['id', 'name', 'ip', 'user', 'images', 'createdAt', 'actions'];
|
displayedColumns: string[] = ['id', 'name', 'ip', 'user', 'images', 'createdAt', 'actions'];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="images-button-row">
|
<div class="images-button-row">
|
||||||
<button class="action-button" (click)="openImageInfoDialog()">Ver Información</button>
|
<button class="action-button" (click)="openImageInfoDialog()">Ver Información</button>
|
||||||
<button class="action-button" (click)="syncRepository()">Sincronizar base de datos</button>
|
<button disabled class="action-button" (click)="syncRepository()">Sincronizar base de datos</button>
|
||||||
<button class="action-button" (click)="importImage()">
|
<button class="action-button" (click)="importImage()">
|
||||||
{{ 'importImageButton' | translate }}
|
{{ 'importImageButton' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -12,7 +12,7 @@ export class ConfigService {
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
loadConfig() {
|
loadConfig() {
|
||||||
return this.http.get('/assets/config.json').pipe(
|
return this.http.get('assets/config.json').pipe(
|
||||||
catchError((error) => {
|
catchError((error) => {
|
||||||
console.error('Error loading config.json', error);
|
console.error('Error loading config.json', error);
|
||||||
return of({});
|
return of({});
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"labelOrganizationalUnit": "Organizational Unit",
|
"labelOrganizationalUnit": "Organizational Unit",
|
||||||
"buttonCancel": "Cancel",
|
"buttonCancel": "Cancel",
|
||||||
"buttonAdd": "Add",
|
"buttonAdd": "Add",
|
||||||
|
"firmwareType": "Firmware",
|
||||||
"addRule": "Add rule",
|
"addRule": "Add rule",
|
||||||
"rulesHeader": "Rules",
|
"rulesHeader": "Rules",
|
||||||
"statusUnavailable": "Unavailable",
|
"statusUnavailable": "Unavailable",
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"labelOrganizationalUnit": "Unidad organizativa",
|
"labelOrganizationalUnit": "Unidad organizativa",
|
||||||
"buttonCancel": "Cancelar",
|
"buttonCancel": "Cancelar",
|
||||||
"buttonAdd": "Añadir",
|
"buttonAdd": "Añadir",
|
||||||
|
"firmwareType": "Firmware",
|
||||||
"back": "Atrás",
|
"back": "Atrás",
|
||||||
"addClientDialogTitle": "Añadir Cliente",
|
"addClientDialogTitle": "Añadir Cliente",
|
||||||
"dialogTitleEditUser": "Editar usuario",
|
"dialogTitleEditUser": "Editar usuario",
|
||||||
|
|
Loading…
Reference in New Issue