refs #1138 Fix stash pop from add translate and es-en files
commit
bb41d9c956
|
@ -1 +1,2 @@
|
|||
ogWebconsole/.env
|
||||
ogWebconsole/test-results/ogGui-junit-report.xml
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"@ngx-translate/http-loader": "^16.0.0",
|
||||
"@swimlane/ngx-charts": "^20.5.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"ngx-joyride": "^2.5.0",
|
||||
"ngx-toastr": "^19.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
|
@ -11266,6 +11267,18 @@
|
|||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ngx-joyride": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/ngx-joyride/-/ngx-joyride-2.5.0.tgz",
|
||||
"integrity": "sha512-C/J8C4uWZjKl9aMmRBt9egVjuIpwWFplJgBZDl1EfqNVTJkdEC51nt9DpAOuDwOgkbArhJ9sZIk3bZT4vkud/w==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=8.2.14",
|
||||
"@angular/core": ">=8.2.14"
|
||||
}
|
||||
},
|
||||
"node_modules/ngx-toastr": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-19.0.0.tgz",
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"@ngx-translate/http-loader": "^16.0.0",
|
||||
"@swimlane/ngx-charts": "^20.5.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"ngx-joyride": "^2.5.0",
|
||||
"ngx-toastr": "^19.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
|
|
|
@ -27,6 +27,10 @@ import { RestoreImageComponent } from './components/groups/components/client-mai
|
|||
import {SoftwareComponent} from "./components/software/software.component";
|
||||
import {SoftwareProfileComponent} from "./components/software-profile/software-profile.component";
|
||||
import {OperativeSystemComponent} from "./components/operative-system/operative-system.component";
|
||||
import {
|
||||
PartitionAssistantComponent
|
||||
} from "./components/groups/components/client-main-view/partition-assistant/partition-assistant.component";
|
||||
import {RepositoriesComponent} from "./components/repositories/repositories.component";
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
|
||||
{
|
||||
|
@ -51,7 +55,9 @@ const routes: Routes = [
|
|||
{ path: 'commands-logs', component: TaskLogsComponent },
|
||||
{ path: 'calendars', component: CalendarComponent },
|
||||
{ path: 'client/:id', component: ClientMainViewComponent },
|
||||
{ path: 'client/:id/partition-assistant', component: PartitionAssistantComponent },
|
||||
{ path: 'images', component: ImagesComponent },
|
||||
{ path: 'repositories', component: RepositoriesComponent },
|
||||
{ path: 'restore-image', component: RestoreImageComponent},
|
||||
{ path: 'software', component: SoftwareComponent },
|
||||
{ path: 'software-profiles', component: SoftwareProfileComponent },
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
|
|
|
@ -8,6 +8,7 @@ import { ToastrService } from "ngx-toastr";
|
|||
import { PageEvent } from "@angular/material/paginator";
|
||||
import { CreateCalendarComponent } from "./create-calendar/create-calendar.component";
|
||||
import { DeleteModalComponent } from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-calendar',
|
||||
|
@ -51,14 +52,14 @@ export class CalendarComponent implements OnInit {
|
|||
}
|
||||
];
|
||||
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
|
||||
|
||||
private apiUrl = `${this.baseUrl}/remote-calendars`;
|
||||
|
||||
constructor(
|
||||
public dialog: MatDialog,
|
||||
private http: HttpClient,
|
||||
private dataService: DataService,
|
||||
private toastService: ToastrService
|
||||
private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -71,7 +72,6 @@ export class CalendarComponent implements OnInit {
|
|||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
console.log('The dialog was closed');
|
||||
this.search();
|
||||
});
|
||||
}
|
||||
|
@ -84,24 +84,21 @@ export class CalendarComponent implements OnInit {
|
|||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching og lives', error);
|
||||
console.error('Error fetching calendars', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sync(calendar: any): void {
|
||||
console.log('Syncing calendars');
|
||||
this.syncUds = true;
|
||||
this.http.post(`${this.apiUrl}/${calendar.uuid}/sync-uds`, {}).subscribe({
|
||||
next: () => {
|
||||
console.log('Calendars synced successfully');
|
||||
this.toastService.success('Calendarios sincronizados correctamente');
|
||||
this.search();
|
||||
this.syncUds = false;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al sincronizar los calendarios:', error);
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
this.syncUds = false;
|
||||
}
|
||||
|
@ -133,16 +130,13 @@ export class CalendarComponent implements OnInit {
|
|||
|
||||
this.http.delete(apiUrl).subscribe({
|
||||
next: () => {
|
||||
console.log('Calendar deleted successfully');
|
||||
this.search();
|
||||
this.toastService.success('Calendar deleted successfully');
|
||||
},
|
||||
error: (error) => {
|
||||
error: () => {
|
||||
this.toastService.error('Error deleting calendar');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('calendar deletion cancelled');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -155,8 +149,7 @@ export class CalendarComponent implements OnInit {
|
|||
this.length = response['hydra:totalItems'];
|
||||
this.loading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al cargar las imágenes:', error);
|
||||
error: () => {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
|
@ -167,4 +160,12 @@ export class CalendarComponent implements OnInit {
|
|||
this.itemsPerPage = event.pageSize;
|
||||
this.applyFilter();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: ['titleStep', 'addButtonStep', 'searchStep', 'tableStep', 'actionsStep'],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
<<<<<<< Updated upstream
|
||||
<div class="search-container">
|
||||
|
@ -66,10 +67,11 @@
|
|||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<<<<<<< Updated upstream
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let client" style="text-align: center;">
|
||||
<td mat-cell *matCellDef="let client" style="text-align: center;" joyrideStep="actionsStep" text="Usa estas opciones para ver, editar o eliminar un grupo de comandos.">
|
||||
<button mat-icon-button color="info" (click)="viewGroupDetails(client)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editCommandGroup(client)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
=======
|
||||
|
|
|
@ -7,6 +7,7 @@ import { DetailCommandGroupComponent } from './detail-command-group/detail-comma
|
|||
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
|
||||
import { MatTableDataSource } from "@angular/material/table";
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-commands-groups',
|
||||
|
@ -48,7 +49,8 @@ export class CommandsGroupsComponent implements OnInit {
|
|||
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
|
||||
private apiUrl = `${this.baseUrl}/command-groups`;
|
||||
|
||||
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService) {}
|
||||
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.search();
|
||||
|
@ -114,4 +116,21 @@ export class CommandsGroupsComponent implements OnInit {
|
|||
this.length = event.length;
|
||||
this.search();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'addCommandGroupStep',
|
||||
'searchStep',
|
||||
'tableStep',
|
||||
'viewCommandsStep',
|
||||
'actionsStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
.create-command-group-container {
|
||||
padding: 20px;
|
||||
max-width: 800px; /* Ancho máximo del contenedor */
|
||||
margin: auto; /* Centra el contenedor en la pantalla */
|
||||
background-color: #fff; /* Fondo blanco para el contenedor */
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
|
@ -24,14 +24,14 @@
|
|||
width: 48%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 200px; /* Limita la altura máxima para evitar desbordamiento */
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
overflow-y: auto; /* Scroll para la tabla si hay demasiados comandos */
|
||||
border: 1px solid #ccc; /* Borde para la tabla */
|
||||
border-radius: 4px; /* Bordes redondeados */
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.selected-commands-list {
|
||||
|
@ -40,7 +40,7 @@
|
|||
padding: 10px;
|
||||
background-color: #f9f9f9;
|
||||
flex: 1;
|
||||
overflow-y: auto; /* Scroll para los comandos seleccionados */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.commands-container {
|
||||
|
@ -60,7 +60,7 @@
|
|||
|
||||
.remove-icon {
|
||||
cursor: pointer;
|
||||
color: #f44336; /* Rojo para eliminar */
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.chevron-icon {
|
||||
|
@ -74,29 +74,27 @@
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
.available-commands, .selected-commands {
|
||||
width: 48%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 500px; /* Limita la altura máxima para evitar desbordamiento */
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
overflow-y: auto; /* Scroll para la tabla si hay demasiados comandos */
|
||||
border: 1px solid #ccc; /* Borde para la tabla */
|
||||
border-radius: 4px; /* Bordes redondeados */
|
||||
max-height: 400px; /* Establece la altura máxima */
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
/* Para asegurar que el componente sea responsivo en pantallas pequeñas */
|
||||
@media (max-width: 600px) {
|
||||
.command-selection {
|
||||
flex-direction: column; /* Cambia a columna en pantallas pequeñas */
|
||||
flex-direction: column;
|
||||
}
|
||||
.available-commands, .selected-commands {
|
||||
width: 100%; /* Ocupa el ancho completo */
|
||||
margin-bottom: 20px; /* Espacio entre elementos */
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,15 +46,13 @@
|
|||
<div class="selected-commands-list" cdkDropList (cdkDropListDropped)="drop($event)">
|
||||
>>>>>>> Stashed changes
|
||||
<div class="commands-container">
|
||||
<ng-container *ngFor="let command of selectedCommands; let last = last">
|
||||
<div *ngFor="let command of selectedCommands" cdkDrag>
|
||||
<div class="command-item">
|
||||
<mat-icon class="drag-handle" cdkDragHandle>drag_indicator</mat-icon>
|
||||
{{ command.name }}
|
||||
<mat-icon class="remove-icon" (click)="removeCommand(command)">close</mat-icon>
|
||||
</div>
|
||||
<ng-container *ngIf="!last">
|
||||
<mat-icon class="chevron-icon">chevron_right</mat-icon>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component, OnInit, Inject } from '@angular/core';
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-command-group',
|
||||
|
@ -55,7 +56,9 @@ export class CreateCommandGroupComponent implements OnInit {
|
|||
}
|
||||
|
||||
addCommand(command: any): void {
|
||||
this.selectedCommands.push(command);
|
||||
if (!this.selectedCommands.includes(command)) {
|
||||
this.selectedCommands.push(command);
|
||||
}
|
||||
}
|
||||
|
||||
removeCommand(command: any): void {
|
||||
|
@ -65,6 +68,10 @@ export class CreateCommandGroupComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
drop(event: CdkDragDrop<any[]>): void {
|
||||
moveItemInArray(this.selectedCommands, event.previousIndex, event.currentIndex);
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
const payload = {
|
||||
name: this.groupName,
|
||||
|
@ -81,6 +88,7 @@ export class CreateCommandGroupComponent implements OnInit {
|
|||
},
|
||||
error: (error) => {
|
||||
console.error('Error actualizando el grupo de comandos', error);
|
||||
this.toastService.error('Error al actualizar el grupo de comandos');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -91,6 +99,7 @@ export class CreateCommandGroupComponent implements OnInit {
|
|||
},
|
||||
error: (error) => {
|
||||
console.error('Error creando el grupo de comandos', error);
|
||||
this.toastService.error('Error al crear el grupo de comandos');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -31,10 +31,6 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!loading">
|
||||
<<<<<<< Updated upstream
|
||||
<table mat-table [dataSource]="tasks" class="mat-elevation-z8">
|
||||
|
@ -69,7 +65,7 @@
|
|||
<ng-container matColumnDef="actions">
|
||||
<<<<<<< Updated upstream
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let task" style="text-align: center;">
|
||||
<td mat-cell *matCellDef="let task" style="text-align: center;" joyrideStep="actionsStep" text="Usa estas opciones para ver, editar o eliminar una tarea.">
|
||||
<button mat-icon-button color="info" (click)="viewTaskDetails(task)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editTask(task)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
=======
|
||||
|
|
|
@ -5,6 +5,7 @@ import { ToastrService } from 'ngx-toastr';
|
|||
import { CreateTaskComponent } from './create-task/create-task.component';
|
||||
import { DetailTaskComponent } from './detail-task/detail-task.component';
|
||||
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-commands-task',
|
||||
|
@ -23,7 +24,8 @@ export class CommandsTaskComponent implements OnInit {
|
|||
loading: boolean = false;
|
||||
private apiUrl = `${this.baseUrl}/command-tasks`;
|
||||
|
||||
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService) {}
|
||||
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadTasks();
|
||||
|
@ -93,4 +95,20 @@ export class CommandsTaskComponent implements OnInit {
|
|||
this.itemsPerPage = event.pageSize;
|
||||
this.loadTasks();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'addTaskStep',
|
||||
'searchStep',
|
||||
'tableStep',
|
||||
'actionsStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,10 +12,16 @@
|
|||
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
margin-bottom: 16px; /* Espaciado entre campos */
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
<form [formGroup]="taskForm" class="task-form">
|
||||
<mat-dialog-content>
|
||||
<mat-horizontal-stepper linear>
|
||||
<!-- Paso 1: Información y Selecciona Comandos -->
|
||||
<mat-step label="Información y Selecciona Comandos">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Información</mat-label>
|
||||
<textarea matInput formControlName="notes" placeholder="Ingresa tus notas aquí"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<h3 class="section-title">Información</h3>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Información</mat-label>
|
||||
<textarea matInput formControlName="notes" placeholder="Ingresa tus notas aquí"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
|
@ -21,61 +21,42 @@
|
|||
<mat-error *ngIf="taskForm.get('commandGroup')?.invalid">Este campo es obligatorio</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="button-container">
|
||||
<button mat-raised-button color="primary" matStepperNext [disabled]="taskForm.get('commandGroup')?.invalid">Continuar</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona Comandos Individuales (Opcional)</mat-label>
|
||||
<mat-select formControlName="extraCommands" multiple>
|
||||
<mat-option *ngFor="let command of availableIndividualCommands" [value]="command.uuid">
|
||||
{{ command.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Paso 2: Selecciona Comandos Individuales -->
|
||||
<mat-step label="Selecciona Comandos Individuales">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona Comandos Individuales (Opcional)</mat-label>
|
||||
<mat-select formControlName="extraCommands" multiple>
|
||||
<mat-option *ngFor="let command of availableIndividualCommands" [value]="command.uuid">
|
||||
{{ command.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<h3 class="section-title">Fecha y hora de ejecución</h3>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Fecha de Ejecución</mat-label>
|
||||
<input matInput [matDatepicker]="picker" formControlName="date" placeholder="Selecciona una fecha">
|
||||
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
<mat-error *ngIf="taskForm.get('date')?.invalid">Este campo es obligatorio</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="button-container">
|
||||
<button mat-button matStepperPrevious>Atrás</button>
|
||||
<button mat-raised-button color="primary" matStepperNext>Continuar</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Hora de Ejecución</mat-label>
|
||||
<input matInput type="time" formControlName="time" placeholder="Selecciona una hora">
|
||||
<mat-error *ngIf="taskForm.get('time')?.invalid">Este campo es obligatorio</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Paso 3: Fecha de Ejecución y Hora -->
|
||||
<mat-step label="Fecha de Ejecución y Hora">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Fecha de Ejecución</mat-label>
|
||||
<input matInput [matDatepicker]="picker" formControlName="date" placeholder="Selecciona una fecha">
|
||||
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
<mat-error *ngIf="taskForm.get('date')?.invalid">Este campo es obligatorio</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Hora de Ejecución</mat-label>
|
||||
<input matInput type="time" formControlName="time" placeholder="Selecciona una hora">
|
||||
<mat-error *ngIf="taskForm.get('time')?.invalid">Este campo es obligatorio</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="button-container">
|
||||
<button mat-button matStepperPrevious>Atrás</button>
|
||||
<button mat-raised-button color="primary" matStepperNext>Continuar</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<!-- Paso 4: Selecciona Unidad Organizacional, Aula y Clientes -->
|
||||
<mat-step label="Selecciona Unidad Organizacional, Aula y Clientes">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona Unidad Organizacional</mat-label>
|
||||
<mat-select formControlName="organizationalUnit" (selectionChange)="onOrganizationalUnitChange()">
|
||||
<mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
|
||||
{{ unit.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="taskForm.get('organizationalUnit')?.invalid">Este campo es obligatorio</mat-error>
|
||||
</mat-form-field>
|
||||
<h3 class="section-title">Selecciona destino</h3>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona Unidad Organizacional</mat-label>
|
||||
<mat-select formControlName="organizationalUnit" (selectionChange)="onOrganizationalUnitChange()">
|
||||
<mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
|
||||
{{ unit.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="taskForm.get('organizationalUnit')?.invalid">Este campo es obligatorio</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona aula</mat-label>
|
||||
|
@ -168,24 +149,21 @@
|
|||
</div>
|
||||
>>>>>>> Stashed changes
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona Clientes</mat-label>
|
||||
<mat-select formControlName="selectedClients" multiple>
|
||||
<mat-option (click)="toggleSelectAll()" [selected]="areAllSelected()">
|
||||
Seleccionar todos
|
||||
</mat-option>
|
||||
<mat-option *ngFor="let client of selectedClients" [value]="client.uuid">
|
||||
{{ client.name }} ({{ client.ip }})
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona Clientes</mat-label>
|
||||
<mat-select formControlName="selectedClients" multiple>
|
||||
<mat-option (click)="toggleSelectAll()" [selected]="areAllSelected()">
|
||||
Seleccionar todos
|
||||
</mat-option>
|
||||
<mat-option *ngFor="let client of selectedClients" [value]="client.uuid">
|
||||
{{ client.name }} ({{ client.ip }})
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="button-container">
|
||||
<button mat-button matStepperPrevious>Atrás</button>
|
||||
<button mat-raised-button color="primary" (click)="saveTask()">Guardar</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
<div class="button-container">
|
||||
<button mat-raised-button color="primary" (click)="saveTask()">Guardar</button>
|
||||
</div>
|
||||
|
||||
</mat-horizontal-stepper>
|
||||
</mat-dialog-content>
|
||||
</form>
|
||||
|
|
|
@ -49,8 +49,6 @@ export class CreateTaskComponent implements OnInit {
|
|||
this.editing = true;
|
||||
this.loadTaskData(this.data.task);
|
||||
}
|
||||
|
||||
console.log(this.data);
|
||||
}
|
||||
|
||||
loadCommandGroups(): void {
|
||||
|
@ -101,6 +99,76 @@ export class CreateTaskComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
private collectClassrooms(unit: any): any[] {
|
||||
let classrooms = [];
|
||||
if (unit.type === 'classroom') {
|
||||
classrooms.push(unit);
|
||||
}
|
||||
if (unit.children && unit.children.length > 0) {
|
||||
for (let child of unit.children) {
|
||||
classrooms = classrooms.concat(this.collectClassrooms(child));
|
||||
}
|
||||
}
|
||||
return classrooms;
|
||||
}
|
||||
|
||||
onOrganizationalUnitChange(): void {
|
||||
const selectedUnitId = this.taskForm.get('organizationalUnit')?.value;
|
||||
const selectedUnit = this.availableOrganizationalUnits.find(unit => unit['@id'] === selectedUnitId);
|
||||
|
||||
if (selectedUnit) {
|
||||
this.selectedUnitChildren = this.collectClassrooms(selectedUnit);
|
||||
} else {
|
||||
this.selectedUnitChildren = [];
|
||||
}
|
||||
|
||||
this.taskForm.patchValue({ selectedChild: '', selectedClients: [] });
|
||||
this.selectedClients = [];
|
||||
this.selectedClientIds.clear();
|
||||
}
|
||||
|
||||
onChildChange(): void {
|
||||
const selectedChildId = this.taskForm.get('selectedChild')?.value;
|
||||
|
||||
if (!selectedChildId) {
|
||||
this.selectedClients = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `${this.baseUrl}${selectedChildId}`.replace(/([^:]\/)\/+/g, '$1');
|
||||
|
||||
this.http.get<any>(url).subscribe(
|
||||
(data) => {
|
||||
if (Array.isArray(data.clients) && data.clients.length > 0) {
|
||||
this.selectedClients = data.clients;
|
||||
} else {
|
||||
this.selectedClients = [];
|
||||
this.toastr.warning('El aula seleccionada no tiene clientes.');
|
||||
}
|
||||
|
||||
this.taskForm.patchValue({ selectedClients: [] });
|
||||
this.selectedClientIds.clear();
|
||||
},
|
||||
(error) => {
|
||||
this.toastr.error('Error al cargar los detalles del aula seleccionada');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
toggleSelectAll() {
|
||||
const allSelected = this.areAllSelected();
|
||||
if (allSelected) {
|
||||
this.selectedClientIds.clear();
|
||||
} else {
|
||||
this.selectedClients.forEach(client => this.selectedClientIds.add(client.uuid));
|
||||
}
|
||||
this.taskForm.get('selectedClients')!.setValue(Array.from(this.selectedClientIds));
|
||||
}
|
||||
|
||||
areAllSelected(): boolean {
|
||||
return this.selectedClients.length > 0 && this.selectedClients.every(client => this.selectedClientIds.has(client.uuid));
|
||||
}
|
||||
|
||||
onCommandGroupChange(): void {
|
||||
const selectedGroupId = this.taskForm.get('commandGroup')?.value;
|
||||
this.http.get<any>(`${this.baseUrl}/command-groups/${selectedGroupId}`).subscribe(
|
||||
|
@ -113,40 +181,6 @@ export class CreateTaskComponent implements OnInit {
|
|||
);
|
||||
}
|
||||
|
||||
onOrganizationalUnitChange(): void {
|
||||
const selectedUnitId = this.taskForm.get('organizationalUnit')?.value;
|
||||
const selectedUnit = this.availableOrganizationalUnits.find(unit => unit['@id'] === selectedUnitId);
|
||||
this.selectedUnitChildren = selectedUnit ? selectedUnit.children : [];
|
||||
}
|
||||
|
||||
onChildChange(): void {
|
||||
const selectedChildId = this.taskForm.get('selectedChild')?.value;
|
||||
this.http.get<any>(`${this.baseUrl}${selectedChildId}`).subscribe(
|
||||
(data) => {
|
||||
this.selectedClients = data.clients;
|
||||
this.taskForm.patchValue({ selectedClients: [] });
|
||||
this.selectedClientIds.clear();
|
||||
},
|
||||
(error) => {
|
||||
this.toastr.error('Error al cargar los detalles del aula seleccionada');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
toggleSelectAll() {
|
||||
const allSelected = this.areAllSelected();
|
||||
if (allSelected) {
|
||||
this.selectedClientIds.clear();
|
||||
} else {
|
||||
this.selectedClients.forEach(client => this.selectedClientIds.add(client.uuid));
|
||||
}
|
||||
this.taskForm.get('selectedClients')!.setValue(Array.from(this.selectedClientIds));
|
||||
}
|
||||
|
||||
areAllSelected(): boolean {
|
||||
return this.selectedClients.length > 0 && this.selectedClients.every(client => this.selectedClientIds.has(client.uuid));
|
||||
}
|
||||
|
||||
saveTask(): void {
|
||||
if (this.taskForm.invalid) {
|
||||
this.toastr.error('Por favor, rellene todos los campos obligatorios');
|
||||
|
@ -156,14 +190,14 @@ export class CreateTaskComponent implements OnInit {
|
|||
const formData = this.taskForm.value;
|
||||
const dateTime = this.combineDateAndTime(formData.date, formData.time);
|
||||
const selectedCommands = formData.extraCommands && formData.extraCommands.length > 0
|
||||
? formData.extraCommands.map((id: any) => `/commands/${id}`)
|
||||
: null;
|
||||
? formData.extraCommands.map((id: any) => `/commands/${id}`)
|
||||
: null;
|
||||
|
||||
const payload: any = {
|
||||
commandGroups: formData.commandGroup ? [`/command-groups/${formData.commandGroup}`] : null,
|
||||
dateTime: dateTime,
|
||||
notes: formData.notes || '',
|
||||
clients: Array.from(this.selectedClientIds).map((uuid: string) => `/clients/${uuid}`),
|
||||
clients: Array.from(this.selectedClientIds).map((uuid: string) => `/clients/${uuid}`),
|
||||
};
|
||||
|
||||
if (selectedCommands) {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
|
@ -47,12 +48,10 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Indicador de carga -->
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
|
||||
<!-- Tabla de trazas -->
|
||||
<div *ngIf="!loading">
|
||||
<<<<<<< Updated upstream
|
||||
<table mat-table [dataSource]="traces" class="mat-elevation-z8">
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Observable } from 'rxjs';
|
|||
import { FormControl } from '@angular/forms';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-task-logs',
|
||||
|
@ -63,7 +64,8 @@ export class TaskLogsComponent implements OnInit {
|
|||
filteredCommands!: Observable<any[]>;
|
||||
commandControl = new FormControl();
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
constructor(private http: HttpClient,
|
||||
private joyrideService: JoyrideService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadTraces();
|
||||
|
@ -183,4 +185,20 @@ export class TaskLogsComponent implements OnInit {
|
|||
this.length = event.length;
|
||||
this.loadTraces();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'resetFiltersStep',
|
||||
'clientSelectStep',
|
||||
'commandSelectStep',
|
||||
'tableStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
|
|
|
@ -7,6 +7,8 @@ import { CreateCommandComponent } from './create-command/create-command.componen
|
|||
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { ExecuteCommandComponent } from './execute-command/execute-command.component';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-commands',
|
||||
|
@ -48,7 +50,8 @@ export class CommandsComponent implements OnInit {
|
|||
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
|
||||
private apiUrl = `${this.baseUrl}/commands`;
|
||||
|
||||
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService) {}
|
||||
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.search();
|
||||
|
@ -74,7 +77,7 @@ export class CommandsComponent implements OnInit {
|
|||
this.dialog.open(CommandDetailComponent, {
|
||||
width: '800px',
|
||||
data: command,
|
||||
}).afterClosed().subscribe(() => this.search());
|
||||
});
|
||||
}
|
||||
|
||||
openCreateCommandModal(): void {
|
||||
|
@ -111,10 +114,38 @@ export class CommandsComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
executeCommand(event: MouseEvent, command: any): void {
|
||||
this.dialog.open(ExecuteCommandComponent, {
|
||||
width: '50%',
|
||||
data: { commandData: command }
|
||||
}).afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
console.log('Comando ejecutado con éxito');
|
||||
} else {
|
||||
console.log('Ejecución de comando cancelada');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onPageChange(event: any): void {
|
||||
this.page = event.pageIndex;
|
||||
this.itemsPerPage = event.pageSize;
|
||||
this.length = event.length;
|
||||
this.search();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'addCommandStep',
|
||||
'searchStep',
|
||||
'tableStep',
|
||||
'actionsStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,11 +28,4 @@
|
|||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<button mat-button color="primary" class="primary-button" [disabled]="false" (click)="execute()" >
|
||||
{{ showClientSelect ? 'Ejecutar' : 'Configurar ejecución' }}
|
||||
</button>
|
||||
<button mat-button (click)="cancel()">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -38,54 +38,12 @@ export class CommandDetailComponent implements OnInit {
|
|||
this.http.get<any>(`${this.baseUrl}/clients?page=1&itemsPerPage=30`).subscribe(response => {
|
||||
this.clients = response['hydra:member'];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
if (!this.showClientSelect) {
|
||||
this.showClientSelect = true;
|
||||
} else {
|
||||
if (this.form.get('selectedClients')?.value.length > 0) {
|
||||
|
||||
const payload = {
|
||||
clients: this.form.value.selectedClients.map((uuid: any) => `/clients/${uuid}`)
|
||||
};
|
||||
|
||||
const apiUrl = `${this.baseUrl}/commands/${this.data.uuid}/execute`;
|
||||
this.http.post(apiUrl, payload).subscribe({
|
||||
next: () => {
|
||||
this.dialogRef.close();
|
||||
this.toastService.success('Command executed successfully');
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error executing command:', error);
|
||||
}
|
||||
});
|
||||
|
||||
this.dialogRef.close();
|
||||
} else {
|
||||
this.form.get('selectedClients')?.markAsTouched();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClientSelectionChange(event: any): void {
|
||||
this.canExecute = this.form.get('selectedClients')?.value.length > 0;
|
||||
}
|
||||
|
||||
onScheduleChange(event: any): void {
|
||||
this.showDatePicker = event.checked;
|
||||
if (event.checked) {
|
||||
this.form.get('scheduleDate')?.setValidators(Validators.required);
|
||||
this.form.get('scheduleTime')?.setValidators(Validators.required);
|
||||
} else {
|
||||
this.form.get('scheduleDate')?.clearValidators();
|
||||
this.form.get('scheduleTime')?.clearValidators();
|
||||
}
|
||||
this.form.get('scheduleDate')?.updateValueAndValidity();
|
||||
this.form.get('scheduleTime')?.updateValueAndValidity();
|
||||
}
|
||||
|
||||
edit(): void {
|
||||
const dialogRef = this.dialog.open(CreateCommandComponent, {
|
||||
width: '600px',
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
.form-container {
|
||||
padding: 20px 24px;
|
||||
width: 100%; /* Asegura que el formulario ocupe el ancho completo */
|
||||
}
|
||||
|
||||
.command-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 15px 0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
padding: 24px;
|
||||
width: 100%; /* Ocupar el ancho completo del modal */
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ExecuteCommandComponent } from './execute-command.component';
|
||||
|
||||
describe('ExecuteCommandComponent', () => {
|
||||
let component: ExecuteCommandComponent;
|
||||
let fixture: ComponentFixture<ExecuteCommandComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ExecuteCommandComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ExecuteCommandComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,100 @@
|
|||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-execute-command',
|
||||
templateUrl: './execute-command.component.html',
|
||||
styleUrls: ['./execute-command.component.css']
|
||||
})
|
||||
export class ExecuteCommandComponent implements OnInit {
|
||||
form: FormGroup;
|
||||
units: any[] = [];
|
||||
childUnits: any[] = [];
|
||||
clients: any[] = [];
|
||||
selectedClients: any[] = [];
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
|
||||
constructor(
|
||||
private dialogRef: MatDialogRef<ExecuteCommandComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private http: HttpClient,
|
||||
private fb: FormBuilder
|
||||
) {
|
||||
this.form = this.fb.group({
|
||||
unit: [null],
|
||||
childUnit: [null],
|
||||
clientSelection: [[]]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadUnits();
|
||||
this.form.get('unit')?.valueChanges.subscribe(value => this.onUnitChange(value));
|
||||
this.form.get('childUnit')?.valueChanges.subscribe(value => this.onChildUnitChange(value));
|
||||
}
|
||||
|
||||
loadUnits(): void {
|
||||
this.http.get<any>(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=30`).subscribe(
|
||||
response => {
|
||||
this.units = response['hydra:member'].filter((unit: { type: string; }) => unit.type === 'organizational-unit');
|
||||
},
|
||||
error => console.error('Error fetching organizational units:', error)
|
||||
);
|
||||
}
|
||||
|
||||
onUnitChange(unitId: string): void {
|
||||
const unit = this.units.find(unit => unit.uuid === unitId);
|
||||
this.childUnits = unit ? this.getAllChildren(unit) : [];
|
||||
this.clients = [];
|
||||
this.form.patchValue({ childUnit: null, clientSelection: [] });
|
||||
}
|
||||
|
||||
getAllChildren(unit: any): any[] {
|
||||
let allChildren = [];
|
||||
if (unit.children && unit.children.length > 0) {
|
||||
for (const child of unit.children) {
|
||||
allChildren.push(child);
|
||||
allChildren = allChildren.concat(this.getAllChildren(child));
|
||||
}
|
||||
}
|
||||
return allChildren;
|
||||
}
|
||||
|
||||
onChildUnitChange(childUnitId: string): void {
|
||||
const childUnit = this.childUnits.find(unit => unit.uuid === childUnitId);
|
||||
this.clients = childUnit && childUnit.clients ? childUnit.clients : [];
|
||||
this.form.patchValue({ clientSelection: [] });
|
||||
}
|
||||
|
||||
executeCommand(): void {
|
||||
const payload = {
|
||||
clients: ['/clients/'+this.form.get('clientSelection')?.value]
|
||||
};
|
||||
|
||||
this.http.post(`${this.baseUrl}/commands/${this.data.commandData.uuid}/execute`, payload)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
console.log('Comando ejecutado con éxito');
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al ejecutar el comando:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
closeModal(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
toggleClientSelection(clientId: string): void {
|
||||
const selectedClients = this.form.get('clientSelection')?.value;
|
||||
if (selectedClients.includes(clientId)) {
|
||||
this.form.get('clientSelection')?.setValue(selectedClients.filter((id: string) => id !== clientId));
|
||||
} else {
|
||||
this.form.get('clientSelection')?.setValue([...selectedClients, clientId]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,7 +21,11 @@
|
|||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.unidad-card, .elements-card {
|
||||
|
@ -124,11 +128,6 @@ mat-spinner {
|
|||
align-self: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.classroomBtn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
@ -174,62 +173,61 @@ mat-spinner {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.results-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
height: 250px;
|
||||
height: auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
|
||||
.result-card.small-card {
|
||||
width: 100%;
|
||||
max-width: 180px;
|
||||
height: auto;
|
||||
min-height: 130px;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
padding-top: 5px;
|
||||
color: #555;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.result-type, .result-ip, .result-mac, .result-status, .result-internal-units, .result-clients {
|
||||
font-size: 0.8rem;
|
||||
margin: 2px 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.result-checkbox {
|
||||
align-self: flex-start;
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.result-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.result-checkbox {
|
||||
float: right;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
padding-top: 10px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.result-type {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.result-ip, .result-mac, .result-status {
|
||||
font-size: 0.9rem;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.result-internal-units,
|
||||
.result-clients {
|
||||
font-size: 0.9rem;
|
||||
color: #007bff;
|
||||
margin: 5px 0;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
|
@ -251,13 +249,13 @@ mat-card {
|
|||
}
|
||||
|
||||
.red-card {
|
||||
background-color: #f35f53;
|
||||
background-color: #f35f53;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.green-card {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.view-mode-buttons button.active {
|
||||
|
@ -265,16 +263,12 @@ mat-card {
|
|||
color: #3f51b5;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.result-card-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 2px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
|
@ -283,7 +277,7 @@ mat-card {
|
|||
}
|
||||
|
||||
.result-card-list .result-title {
|
||||
font-size: 14px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
@ -292,11 +286,11 @@ mat-card {
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.result-card-list p {
|
||||
margin: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.result-list {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
>>>>>>> Stashed changes
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="header" joyrideStep="filterSelectionStep" text="Selecciona entre los filtros guardados para ajustar los resultados de la búsqueda.">
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'selectFilterLabel' | translate }}</mat-label>
|
||||
<mat-select (selectionChange)="loadSelectedFilter($event.value)">
|
||||
|
@ -23,7 +23,7 @@
|
|||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="view-mode-buttons">
|
||||
<div class="view-mode-buttons" joyrideStep="viewModeStep" text="Elige cómo quieres ver los resultados: en cuadrícula o en lista.">
|
||||
<button mat-button (click)="changeViewMode('grid')" [class.active]="viewMode === 'grid'">
|
||||
<mat-icon>grid_view</mat-icon> {{ 'gridViewButton' | translate }}
|
||||
</button>
|
||||
|
@ -33,7 +33,7 @@
|
|||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="filters">
|
||||
<div class="filters" joyrideStep="filtersStep" text="Aplica filtros específicos para encontrar los resultados exactos que necesitas.">
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'selectOptionLabel' | translate }}</mat-label>
|
||||
<mat-select [(value)]="selectedFilter1" (selectionChange)="applyFilter()">
|
||||
|
@ -70,12 +70,12 @@
|
|||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
|
||||
<div class="results">
|
||||
<div class="results" joyrideStep="resultsStep" text="Aquí verás los resultados de tu búsqueda filtrada.">
|
||||
<ng-container *ngIf="filteredResults && filteredResults.length > 0; else noResults">
|
||||
<ng-container *ngIf="viewMode === 'grid'">
|
||||
<mat-grid-list cols="4" rowHeight="1:1">
|
||||
<mat-grid-list cols="8" rowHeight="1:1">
|
||||
<mat-grid-tile *ngFor="let result of filteredResults">
|
||||
<mat-card class="result-card">
|
||||
<mat-card class="result-card small-card">
|
||||
<mat-checkbox [checked]="isSelected(result.name)" (change)="onCheckboxChange($event, result.name, result['@id'])" class="result-checkbox"></mat-checkbox>
|
||||
<mat-card-title class="result-title">{{ result.name }}</mat-card-title>
|
||||
<mat-card-content class="result-content">
|
||||
|
@ -106,7 +106,7 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="paginator-container">
|
||||
<div class="paginator-container" joyrideStep="paginationStep" text="Usa el paginador para navegar entre los resultados.">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions" (page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
|
|
@ -23,6 +23,7 @@ import { Router } from '@angular/router';
|
|||
import {
|
||||
CreatePxeBootFileComponent
|
||||
} from "../../../ogboot/pxe-boot-files/create-pxeBootFile/create-pxe-boot-file/create-pxe-boot-file.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
|
||||
@Component({
|
||||
|
@ -69,7 +70,8 @@ export class AdvancedSearchComponent {
|
|||
private toastService: ToastrService,
|
||||
private _bottomSheet: MatBottomSheet,
|
||||
private http: HttpClient,
|
||||
private router: Router
|
||||
private router: Router,
|
||||
private joyrideService: JoyrideService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -436,4 +438,23 @@ export class AdvancedSearchComponent {
|
|||
}
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'title2Step',
|
||||
'filterSelectionStep',
|
||||
'viewModeStep',
|
||||
'filtersStep',
|
||||
'selectAllStep',
|
||||
'saveFiltersStep',
|
||||
'sendActionStep',
|
||||
'addPxeStep',
|
||||
'resultsStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -205,3 +205,37 @@
|
|||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.circular-chart {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
.circle-bg {
|
||||
fill: none;
|
||||
stroke: #f0f0f0;
|
||||
stroke-width: 3.8;
|
||||
}
|
||||
|
||||
.circle {
|
||||
fill: none;
|
||||
stroke-width: 3.8;
|
||||
stroke-linecap: round;
|
||||
animation: progress 1s ease-out forwards;
|
||||
}
|
||||
|
||||
/* Define colores distintos para cada partición */
|
||||
.partition-0 { stroke: #00bfa5; } /* Ejemplo: verde */
|
||||
.partition-1 { stroke: #ff6f61; } /* Ejemplo: rojo */
|
||||
.partition-2 { stroke: #ffb400; } /* Ejemplo: amarillo */
|
||||
.partition-3 { stroke: #3498db; } /* Ejemplo: azul */
|
||||
|
||||
/* Texto en el centro del gráfico */
|
||||
.percentage {
|
||||
fill: #333;
|
||||
font-size: 0.7rem;
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
|
|
|
@ -67,17 +67,20 @@
|
|||
<div *ngFor="let disk of diskUsageData" class="disk-usage">
|
||||
<h3>{{ 'diskTitle' | translate }} {{ disk.diskNumber }}</h3>
|
||||
<div class="chart">
|
||||
<svg viewBox="0 0 36 36" class="circular-chart green">
|
||||
<path class="circle-bg"
|
||||
d="M18 2.0845
|
||||
a 15.9155 15.9155 0 0 1 0 31.831
|
||||
a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||
<path class="circle"
|
||||
[attr.stroke-dasharray]="disk.percentage + ', 100'"
|
||||
d="M18 2.0845
|
||||
a 15.9155 15.9155 0 0 1 0 31.831
|
||||
a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||
<text x="18" y="20.35" class="percentage">{{ disk.percentage }}%</text>
|
||||
<svg viewBox="0 0 36 36" class="circular-chart">
|
||||
<path
|
||||
class="circle-bg"
|
||||
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||
|
||||
<ng-container *ngFor="let partition of disk.partitions; let i = index">
|
||||
<path
|
||||
class="circle partition-{{ i }}"
|
||||
[attr.stroke-dasharray]="(partition.size / 1024).toFixed(2) + ', 100'"
|
||||
[attr.stroke-dashoffset]="getStrokeOffset(disk.partitions, i)"
|
||||
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||
</ng-container>
|
||||
|
||||
<text x="18" y="20.35" class="percentage">{{ (disk.used / disk.total * 100).toFixed(0) }}%</text>
|
||||
</svg>
|
||||
</div>
|
||||
<p>{{ 'diskUsedLabel' | translate }}: {{ disk.used }} GB ({{ disk.percentage }}%)</p>
|
||||
|
@ -86,4 +89,5 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@ import {DatePipe} from "@angular/common";
|
|||
import {MatTableDataSource} from "@angular/material/table";
|
||||
import {PartitionAssistantComponent} from "./partition-assistant/partition-assistant.component";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {Router} from "@angular/router";
|
||||
|
||||
interface ClientInfo {
|
||||
property: string;
|
||||
|
@ -62,7 +63,11 @@ export class ClientMainViewComponent implements OnInit {
|
|||
isDiskUsageEmpty: boolean = true;
|
||||
loading: boolean = true;
|
||||
|
||||
constructor(private http: HttpClient, private dialog: MatDialog) {
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private dialog: MatDialog,
|
||||
private router: Router
|
||||
) {
|
||||
const url = window.location.href;
|
||||
const segments = url.split('/');
|
||||
this.clientUuid = segments[segments.length - 1];
|
||||
|
@ -105,12 +110,13 @@ export class ClientMainViewComponent implements OnInit {
|
|||
}
|
||||
|
||||
calculateDiskUsage() {
|
||||
const diskUsageMap = new Map<number, { total: number, used: number }>();
|
||||
const diskUsageMap = new Map<number, { total: number, used: number, partitions: any[] }>();
|
||||
|
||||
this.partitions.forEach((partition: any) => {
|
||||
const diskNumber = partition.diskNumber;
|
||||
|
||||
if (!diskUsageMap.has(diskNumber)) {
|
||||
diskUsageMap.set(diskNumber, { total: 0, used: 0 });
|
||||
diskUsageMap.set(diskNumber, { total: 0, used: 0, partitions: [] });
|
||||
}
|
||||
|
||||
const diskData = diskUsageMap.get(diskNumber);
|
||||
|
@ -118,17 +124,28 @@ export class ClientMainViewComponent implements OnInit {
|
|||
if (partition.partitionNumber === 0) {
|
||||
diskData!.total = Number((partition.size / 1024).toFixed(2));
|
||||
} else {
|
||||
diskData!.used += Number(((partition.size * (partition.memoryUsage / 100))/ 1024).toFixed(2));
|
||||
diskData!.used += Number(((partition.size * (partition.memoryUsage / 100)) / 1024).toFixed(2));
|
||||
diskData!.partitions.push(partition);
|
||||
}
|
||||
});
|
||||
|
||||
this.diskUsageData = Array.from(diskUsageMap.entries()).map(([diskNumber, { total, used }]) => {
|
||||
this.diskUsageData = Array.from(diskUsageMap.entries()).map(([diskNumber, { total, used, partitions }]) => {
|
||||
const percentage = total > 0 ? Math.round((used / total) * 100) : 0;
|
||||
return { diskNumber, total, used, percentage };
|
||||
return { diskNumber, total, used, percentage, partitions };
|
||||
});
|
||||
|
||||
this.isDiskUsageEmpty = this.diskUsageData.length === 0;
|
||||
}
|
||||
|
||||
getStrokeOffset(partitions: any[], index: number): number {
|
||||
const totalSize = partitions.reduce((acc, part) => acc + (part.size / 1024), 0);
|
||||
|
||||
const offset = partitions.slice(0, index).reduce((acc, part) => acc + (part.size / 1024), 0);
|
||||
console.log(offset, totalSize)
|
||||
|
||||
return totalSize > 0 ? (offset / totalSize) : 0;
|
||||
}
|
||||
|
||||
loadPartitions(): void {
|
||||
this.http.get<any>(`${this.baseUrl}/partitions?client.id=${this.clientData?.id}`).subscribe({
|
||||
next: data => {
|
||||
|
@ -155,18 +172,14 @@ export class ClientMainViewComponent implements OnInit {
|
|||
|
||||
onCommandSelect(command: any): void {
|
||||
if (command.name === 'Particionar y Formatear') {
|
||||
this.openPartitionDialog();
|
||||
this.openPartitionAssistant();
|
||||
}
|
||||
}
|
||||
|
||||
openPartitionDialog(): void {
|
||||
const dialogRef = this.dialog.open(PartitionAssistantComponent, {
|
||||
width: '1000px',
|
||||
data: this.clientData['@id']
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result: any) => {
|
||||
console.log('El diálogo se cerró', result);
|
||||
openPartitionAssistant(): void {
|
||||
console.log(this.clientData)
|
||||
this.router.navigate([`/client/${this.clientData.uuid}/partition-assistant`]).then(r => {
|
||||
console.log('navigated', r);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,20 +5,12 @@
|
|||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
.header-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 10px;
|
||||
background-color: #cecbcb;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header label {
|
||||
font-weight: 500;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.disk-size {
|
||||
|
@ -44,6 +36,11 @@
|
|||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
border-right: 2px solid white; /* Borde de separación */
|
||||
}
|
||||
|
||||
.partition-segment:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.partition-table {
|
||||
|
|
|
@ -24,17 +24,17 @@
|
|||
<span class="disk-size">Tamaño: {{ (disk.totalDiskSize / 1024).toFixed(2) }} GB</span>
|
||||
</div>
|
||||
|
||||
<div class="partition-bar">
|
||||
<div
|
||||
*ngFor="let partition of disk.partitions"
|
||||
[ngStyle]="{'width': partition.percentage + '%', 'background-color': partition.color}"
|
||||
class="partition-segment"
|
||||
>
|
||||
{{ partition.type }} ({{ (partition.size / 1024).toFixed(2) }} GB)
|
||||
</div>
|
||||
<div class="partition-bar">
|
||||
<div
|
||||
*ngFor="let partition of disk.partitions"
|
||||
[ngStyle]="{'width': partition.percentage + '%', 'background-color': partition.color}"
|
||||
class="partition-segment"
|
||||
>
|
||||
{{ partition.type }} ({{ (partition.size / 1024).toFixed(2) }} GB)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button mat-flat-button color="primary" (click)="addPartition(disk.diskNumber)"> + </button>
|
||||
<button mat-flat-button color="primary" (click)="addPartition(disk.diskNumber)"> + </button>
|
||||
|
||||
<table class="partition-table">
|
||||
<thead>
|
||||
|
@ -133,7 +133,3 @@
|
|||
>>>>>>> Stashed changes
|
||||
|
||||
<div *ngIf="errorMessage" class="error-message">{{ errorMessage }}</div>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button (click)="save()">Guardar</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
@ -2,6 +2,7 @@ import {Component, EventEmitter, Inject, Input, OnInit, Output} from '@angular/c
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import {MAT_DIALOG_DATA} from "@angular/material/dialog";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
|
||||
interface Partition {
|
||||
uuid?: string;
|
||||
|
@ -26,6 +27,8 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
|
||||
errorMessage = '';
|
||||
originalPartitions: any[] = [];
|
||||
clientId: string | null = null;
|
||||
data: any = {};
|
||||
disks: { diskNumber: number; totalDiskSize: number; partitions: Partition[] }[] = [];
|
||||
|
||||
private apiUrl: string = this.baseUrl + '/partitions';
|
||||
|
@ -33,15 +36,17 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
constructor(
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.clientId = this.route.snapshot.paramMap.get('id');
|
||||
|
||||
this.loadPartitions();
|
||||
}
|
||||
|
||||
loadPartitions() {
|
||||
const url = `${this.baseUrl}${this.data}`;
|
||||
const url = `${this.baseUrl}/clients/${this.clientId}`;
|
||||
this.http.get(url).subscribe(
|
||||
(response) => {
|
||||
this.data = response;
|
||||
|
@ -76,7 +81,7 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
type: partition.type || partition.filesystem || 'NTFS',
|
||||
sizeBytes: partition.size,
|
||||
format: false,
|
||||
color: '#' + Math.floor(Math.random() * 16777215).toString(16),
|
||||
color: '#1f1b91',
|
||||
percentage: 0
|
||||
});
|
||||
}
|
||||
|
@ -216,14 +221,13 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
memoryUsage: partition.memoryUsage,
|
||||
size: partition.size,
|
||||
filesystem: partition.type,
|
||||
client: this.data['@id']
|
||||
client: `/clients/${this.clientId}`
|
||||
};
|
||||
|
||||
if (isNew) {
|
||||
this.http.post(this.apiUrl, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Partición creada exitosamente');
|
||||
this.loadPartitions();
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al crear la partición:', error);
|
||||
|
@ -235,7 +239,6 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
this.http.patch(patchUrl, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Partición actualizada exitosamente');
|
||||
this.loadPartitions();
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al actualizar la partición:', error);
|
||||
|
@ -261,7 +264,6 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
this.http.delete(deleteUrl).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Partición eliminada exitosamente');
|
||||
this.loadPartitions();
|
||||
},
|
||||
(error) => {}
|
||||
);
|
||||
|
|
|
@ -140,55 +140,54 @@
|
|||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
|
||||
<div *ngIf="!loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
<div *ngIf="!loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
|
||||
<div *ngIf="loading">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="Lista de clientes filtrados por tus criterios de búsqueda.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let client" >
|
||||
<ng-container *ngIf="column.columnDef === 'name'">
|
||||
<div class="client-info">
|
||||
<div class="client-name">{{ client.name }}</div>
|
||||
<div class="client-ip">{{ client.ip }}</div>
|
||||
<div class="client-mac">{{ client.mac }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'status'">
|
||||
<mat-chip>{{ client.status }}</mat-chip>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef !== 'status' && column.columnDef !== 'name'">
|
||||
{{ column.cell(client) }}
|
||||
</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 client" style="text-align: center;" joyrideStep="actionsStep" text="Acciones disponibles para cada cliente, como ver, editar o eliminar.">
|
||||
<button *ngIf="!syncStatus" mat-icon-button color="primary" (click)="getStatus(client)"><mat-icon>sync</mat-icon></button>
|
||||
<button *ngIf="syncStatus" mat-icon-button color="primary"><mat-spinner diameter="24"></mat-spinner></button>
|
||||
<button mat-icon-button color="info" (click)="handleClientClick($event, client)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="onEditClick($event, client.uuid)" i18n="@@editImage"><mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="onDeleteClick($event, client)">
|
||||
<mat-icon i18n="@@deleteElementTooltip">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="Navega entre las páginas de resultados utilizando el paginador.">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
||||
<div *ngIf="loading">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let client" >
|
||||
<ng-container *ngIf="column.columnDef === 'name'">
|
||||
<div class="client-info">
|
||||
<div class="client-name">{{ client.name }}</div>
|
||||
<div class="client-ip">{{ client.ip }}</div>
|
||||
<div class="client-mac">{{ client.mac }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'status'">
|
||||
<mat-chip>
|
||||
{{ client.status }}
|
||||
</mat-chip>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef !== 'status' && column.columnDef !== 'name'" >
|
||||
{{ column.cell(client) }}
|
||||
</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 client" style="text-align: center;">
|
||||
<button *ngIf="!syncStatus" mat-icon-button color="primary" (click)="getStatus(client)"><mat-icon>sync</mat-icon></button>
|
||||
<button *ngIf="syncStatus" mat-icon-button color="primary"><mat-spinner diameter="24"></mat-spinner></button>
|
||||
<button mat-icon-button color="info" (click)="handleClientClick($event, client)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="onEditClick($event, client.uuid)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="onDeleteClick($event, client)">
|
||||
<mat-icon i18n="@@deleteElementTooltip">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">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,6 +13,7 @@ import { throwError } from 'rxjs';
|
|||
import { catchError } from 'rxjs/operators';
|
||||
import {ClientViewComponent} from "../../shared/client-view/client-view.component";
|
||||
import { Router } from '@angular/router';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-client-tab-view',
|
||||
|
@ -78,7 +79,8 @@ export class ClientTabViewComponent {
|
|||
public dialog: MatDialog,
|
||||
private toastService: ToastrService,
|
||||
private http: HttpClient,
|
||||
private router: Router
|
||||
private router: Router,
|
||||
private joyrideService: JoyrideService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -190,4 +192,20 @@ export class ClientTabViewComponent {
|
|||
this.length = event.length;
|
||||
this.getClients();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'title3Step',
|
||||
'resetFiltersStep',
|
||||
'addClientStep',
|
||||
'searchContainerStep',
|
||||
'tableStep',
|
||||
'actionsStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,15 +39,17 @@
|
|||
.mat-chip-success {
|
||||
background-color: #4CAF50 !important;
|
||||
color: white !important;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mat-chip-error {
|
||||
background-color: #F44336 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.button-row{
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.calendar-ico{
|
||||
margin-top: 5px;
|
||||
color: gray;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
|
@ -27,10 +28,11 @@
|
|||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="search-boolean">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-label i18n="@@searchLabel">Tipo</mat-label>
|
||||
<mat-select [(ngModel)]="filters['type']" (selectionChange)="search()" placeholder="Seleccionar opción" >
|
||||
<mat-select [(ngModel)]="filters['type']" (selectionChange)="search()" placeholder="Seleccionar opción">
|
||||
<mat-option [value]="''">Todos</mat-option>
|
||||
<mat-option [value]="'organizational-unit'">Centro</mat-option>
|
||||
<mat-option [value]="'classrooms-group'">Grupos de aulas</mat-option>
|
||||
|
@ -39,16 +41,17 @@
|
|||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="Aquí se muestran las unidades organizativas 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 ou" >
|
||||
<td mat-cell *matCellDef="let ou">
|
||||
<ng-container *ngIf="column.columnDef !== 'available' && column.columnDef !== 'type'">
|
||||
{{ column.cell(ou) }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'available'" >
|
||||
<mat-chip *ngIf="ou.available" class="mat-chip-success"><mat-icon style="color:white;">check</mat-icon></mat-chip>
|
||||
<mat-chip *ngIf="!ou.available" class="mat-chip-error"> <mat-icon style="color:white;">close</mat-icon></mat-chip>
|
||||
<ng-container *ngIf="column.columnDef === 'available'">
|
||||
<mat-chip *ngIf="ou.available" class="mat-chip-success"><mat-icon class="calendar-ico">event_available</mat-icon></mat-chip>
|
||||
<mat-chip *ngIf="!ou.available" class="mat-chip-error"><mat-icon class="calendar-ico">event_busy</mat-icon></mat-chip>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'type'" >
|
||||
<mat-chip> {{ ou.type }} </mat-chip>
|
||||
|
@ -89,9 +92,9 @@
|
|||
<ng-container matColumnDef="actions">
|
||||
<<<<<<< Updated upstream
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let ou" style="text-align: center;">
|
||||
<td mat-cell *matCellDef="let ou" style="text-align: center;" joyrideStep="actionsStep" text="Usa estas opciones para ver, editar o eliminar una unidad organizativa.">
|
||||
<button mat-icon-button color="info" (click)="onShowClick($event, ou)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="onEditClick($event, ou.uuid)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="onEditClick($event, ou.uuid)" i18n="@@editImage"><mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="onDeleteClick($event, ou)"><mat-icon>delete</mat-icon></button>
|
||||
=======
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'columnActions' | translate }}</th>
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
} from "../../shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component";
|
||||
import {ClassroomViewDialogComponent} from "../../shared/classroom-view/classroom-view-modal";
|
||||
import {TreeViewComponent} from "../../shared/tree-view/tree-view.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-organizational-unit-tab-view',
|
||||
|
@ -79,7 +80,8 @@ export class OrganizationalUnitTabViewComponent {
|
|||
private dataService: DataService,
|
||||
public dialog: MatDialog,
|
||||
private toastService: ToastrService,
|
||||
private http: HttpClient
|
||||
private http: HttpClient,
|
||||
private joyrideService: JoyrideService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -169,4 +171,21 @@ export class OrganizationalUnitTabViewComponent {
|
|||
this.length = event.length;
|
||||
this.getOrganizationalUnits();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleS4tep',
|
||||
'resetFiltersStep',
|
||||
'addOUStep',
|
||||
'searchContainerStep',
|
||||
'tableStep',
|
||||
'actionsStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
.card {
|
||||
flex-grow: 1;
|
||||
margin: 10px;
|
||||
|
||||
border: 2px solid rgba(102, 102, 102, 0.103)
|
||||
}
|
||||
|
||||
.header-container {
|
||||
|
@ -28,8 +30,17 @@
|
|||
padding: 10px;
|
||||
}
|
||||
|
||||
.unidad-card, .elements-card {
|
||||
flex: 1 1 45%;
|
||||
.unidad-card {
|
||||
flex: 1 1 20%;
|
||||
background-color: #fafafa;
|
||||
height: 600px;
|
||||
overflow-y: auto;
|
||||
box-shadow: none !important;
|
||||
|
||||
}
|
||||
|
||||
.elements-card {
|
||||
flex: 1 1 75%;
|
||||
background-color: #fafafa;
|
||||
height: 600px;
|
||||
overflow-y: auto;
|
||||
|
@ -183,18 +194,13 @@ mat-spinner {
|
|||
.result-card {
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
<<<<<<< Updated upstream
|
||||
height: 250px; /* Fijo para mantener la forma cuadrada */
|
||||
}
|
||||
|
||||
=======
|
||||
height: 250px;
|
||||
}
|
||||
>>>>>>> Stashed changes
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 30px;
|
||||
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
@ -204,3 +210,7 @@ mat-spinner {
|
|||
mat-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mat-tooltip {
|
||||
white-space: pre-line;
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="groupLists-container">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-card class="card unidad-card">
|
||||
|
@ -58,6 +59,7 @@
|
|||
<span>{{ 'viewTreeMenu' | translate }}</span>
|
||||
>>>>>>> Stashed changes
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="onEditClick($event, unidad.type, unidad.uuid)">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-icon
|
||||
|
@ -72,6 +74,7 @@
|
|||
<span>{{ 'editUnitMenu' | translate }}</span>
|
||||
>>>>>>> Stashed changes
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="onShowClick($event, unidad)">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-icon
|
||||
|
@ -86,6 +89,7 @@
|
|||
<span>{{ 'viewUnitMenu' | translate }}</span>
|
||||
>>>>>>> Stashed changes
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="addOU($event, unidad)">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-icon
|
||||
|
@ -100,6 +104,7 @@
|
|||
<span>{{ 'addInternalUnitMenu' | translate }}</span>
|
||||
>>>>>>> Stashed changes
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="addClient($event, unidad)">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-icon
|
||||
|
@ -145,7 +150,9 @@
|
|||
<mat-icon>info</mat-icon>
|
||||
<span>{{ 'noInternalElementsMessage' | translate }}</span>
|
||||
</div>
|
||||
<mat-list-item *ngFor="let child of children" [ngClass]="{'selected-item': child === selectedUnidad, 'clickable-item': true}" (click)="onSelectChild(child)">
|
||||
<mat-list-item *ngFor="let child of children"
|
||||
[ngClass]="{'selected-item': child === selectedUnidad, 'clickable-item': true}"
|
||||
(click)="onSelectChild(child)">
|
||||
<div class="item-content">
|
||||
<mat-icon [ngSwitch]="child.type">
|
||||
<ng-container *ngSwitchCase="'organizational-unit'">apartment</ng-container>
|
||||
|
@ -168,6 +175,7 @@
|
|||
<span>{{ 'editElementMenu' | translate }}</span>
|
||||
>>>>>>> Stashed changes
|
||||
</button>
|
||||
|
||||
<button *ngIf="child.type !== 'client'" mat-menu-item (click)="onShowClick($event, child)">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-icon class="edit-icon" #tooltip="matTooltip" matTooltip="Visualizar unidad organizativa" matTooltipHideDelay="0" i18n="@@viewUnitTooltip">visibility</mat-icon>
|
||||
|
@ -177,6 +185,7 @@
|
|||
<span>{{ 'viewUnitMenu' | translate }}</span>
|
||||
>>>>>>> Stashed changes
|
||||
</button>
|
||||
|
||||
<button *ngIf="child.type !== 'client'" mat-menu-item (click)="addOU($event, child)">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-icon class="edit-icon" #tooltip="matTooltip" matTooltip="Crear unidad organizativa interna" matTooltipHideDelay="0" i18n="@@addInternalUnitTooltip">add_home_work</mat-icon>
|
||||
|
@ -186,6 +195,7 @@
|
|||
<span>{{ 'addInternalUnitMenu' | translate }}</span>
|
||||
>>>>>>> Stashed changes
|
||||
</button>
|
||||
|
||||
<button *ngIf="child.type !== 'client'" mat-menu-item (click)="addClient($event, child)">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-icon class="edit-icon" #tooltip="matTooltip" matTooltip="Crear cliente en esta unidad organizativa" matTooltipHideDelay="0" i18n="@@addClientTooltip">devices</mat-icon>
|
||||
|
@ -195,6 +205,7 @@
|
|||
<span>{{ 'addClientMenu' | translate }}</span>
|
||||
>>>>>>> Stashed changes
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="onDeleteClick($event, child.uuid, child.name, child.type)">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-icon class="delete-icon" #tooltip="matTooltip" matTooltip="Borrar elemento" matTooltipHideDelay="0" i18n="@@deleteElementTooltip">delete</mat-icon>
|
||||
|
@ -222,9 +233,11 @@
|
|||
<mat-tab i18n-label label="Búsqueda avanzada">
|
||||
<app-advanced-search></app-advanced-search>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab i18n-label label="Clientes">
|
||||
<app-client-tab-view #clientTab></app-client-tab-view>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab i18n-label label="Unidades organizativas">
|
||||
=======
|
||||
|
||||
|
|
|
@ -25,6 +25,9 @@ import {ClientTabViewComponent} from "./components/client-tab-view/client-tab-vi
|
|||
import {
|
||||
OrganizationalUnitTabViewComponent
|
||||
} from "./components/organizational-unit-tab-view/organizational-unit-tab-view.component";
|
||||
import { ExecuteCommandComponent } from '../commands/main-commands/execute-command/execute-command.component';
|
||||
import { ExecuteCommandOuComponent } from './shared/execute-command-ou/execute-command-ou.component';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-groups',
|
||||
|
@ -67,7 +70,8 @@ export class GroupsComponent implements OnInit {
|
|||
public dialog: MatDialog,
|
||||
private toastService: ToastrService,
|
||||
private _bottomSheet: MatBottomSheet,
|
||||
private http: HttpClient
|
||||
private http: HttpClient,
|
||||
private joyrideService: JoyrideService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -298,6 +302,21 @@ export class GroupsComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
onExecuteCommand(event: MouseEvent, child: any, name: string, type:string): void {
|
||||
console.log('Executing command on:', child);
|
||||
|
||||
this.dialog.open(ExecuteCommandOuComponent, {
|
||||
width: '50%',
|
||||
data: { childUnitUuid: child }
|
||||
}).afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
console.log('Comando ejecutado con éxito');
|
||||
} else {
|
||||
console.log('Ejecución de comando cancelada');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openSnackBar(isError: boolean, message: string) {
|
||||
if (isError) {
|
||||
this.toastService.error(' Error al eliminar la entidad: ' + message, 'Error');
|
||||
|
@ -432,4 +451,11 @@ export class GroupsComponent implements OnInit {
|
|||
const dialogRef = this.dialog.open(AcctionsModalComponent, { data: { selectedElements: this.selectedElements }, width: '700px'});
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: ['titleStep', 'addStep', 'keyStep', 'unitStep', 'elementsStep', 'tabsStep'],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
.form-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.command-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.checkbox-group label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mat-checkbox {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.mat-dialog-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
button[mat-button] {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
button[mat-button]:disabled {
|
||||
color: rgba(0, 0, 0, 0.38);
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
|
||||
@Component({
|
||||
selector: 'app-execute-command-ou',
|
||||
templateUrl: './execute-command-ou.component.html',
|
||||
styleUrls: ['./execute-command-ou.component.css']
|
||||
})
|
||||
export class ExecuteCommandOuComponent implements OnInit {
|
||||
form: FormGroup;
|
||||
clients: any[] = [];
|
||||
commands: any[] = [];
|
||||
commandGroups: any[] = [];
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
|
||||
constructor(
|
||||
private dialogRef: MatDialogRef<ExecuteCommandOuComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private http: HttpClient,
|
||||
private fb: FormBuilder,
|
||||
private toastService: ToastrService,
|
||||
) {
|
||||
this.form = this.fb.group({
|
||||
selectedCommand: [null],
|
||||
selectedCommandGroup: [null],
|
||||
clientSelection: [[]]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadClients();
|
||||
this.loadCommands();
|
||||
this.loadCommandGroups();
|
||||
}
|
||||
|
||||
loadClients(): void {
|
||||
this.http.get<any>(`${this.baseUrl}/organizational-units/${this.data.childUnitUuid}`).subscribe(
|
||||
response => {
|
||||
this.clients = this.getAllClients(response);
|
||||
const clientIds = this.clients.map(client => client.uuid);
|
||||
this.form.get('clientSelection')?.setValue(clientIds);
|
||||
},
|
||||
error => console.error('Error al cargar los clientes:', error)
|
||||
);
|
||||
}
|
||||
|
||||
getAllClients(unit: any): any[] {
|
||||
let allClients = unit.clients || [];
|
||||
if (unit.children && unit.children.length > 0) {
|
||||
unit.children.forEach((child: any) => {
|
||||
allClients = allClients.concat(this.getAllClients(child));
|
||||
});
|
||||
}
|
||||
return allClients;
|
||||
}
|
||||
|
||||
loadCommands(): void {
|
||||
this.http.get<any>(`${this.baseUrl}/commands?page=1&itemsPerPage=30`).subscribe(
|
||||
response => {
|
||||
this.commands = response['hydra:member'] || [];
|
||||
},
|
||||
error => this.toastService.error('Error al cargar comandos:', error)
|
||||
);
|
||||
}
|
||||
|
||||
loadCommandGroups(): void {
|
||||
this.http.get<any>(`${this.baseUrl}/command-groups?page=1&itemsPerPage=30`).subscribe(
|
||||
response => {
|
||||
this.commandGroups = response['hydra:member'] || [];
|
||||
},
|
||||
error => this.toastService.error('Error al cargar grupos de comandos:', error)
|
||||
);
|
||||
}
|
||||
|
||||
toggleClientSelection(clientId: string): void {
|
||||
const selectedClients = this.form.get('clientSelection')?.value || [];
|
||||
if (selectedClients.includes(clientId)) {
|
||||
this.form.get('clientSelection')?.setValue(selectedClients.filter((id: string) => id !== clientId));
|
||||
} else {
|
||||
this.form.get('clientSelection')?.setValue([...selectedClients, clientId]);
|
||||
}
|
||||
}
|
||||
|
||||
executeCommand(): void {
|
||||
const selectedCommandUuid = this.form.get('selectedCommand')?.value || this.form.get('selectedCommandGroup')?.value;
|
||||
const isCommandGroup = !!this.form.get('selectedCommandGroup')?.value;
|
||||
|
||||
if (!selectedCommandUuid) {
|
||||
console.warn('No se ha seleccionado ningún comando o grupo de comandos');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
clients: (this.form.get('clientSelection')?.value || []).map((clientId: string) => `/clients/${clientId}`)
|
||||
};
|
||||
|
||||
const url = isCommandGroup
|
||||
? `${this.baseUrl}/command-groups/${selectedCommandUuid}/execute`
|
||||
: `${this.baseUrl}/commands/${selectedCommandUuid}/execute`;
|
||||
|
||||
this.http.post(url, payload)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Comando ejecutado con éxito');
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error('Error al ejecutar el comando:', error);
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
closeModal(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ export class CreateImageComponent implements OnInit {
|
|||
imageForm: FormGroup<any>;
|
||||
imageId: string | null = null;
|
||||
softwareProfiles: any[] = [];
|
||||
repositories: any[] = [];
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
|
@ -29,6 +30,7 @@ export class CreateImageComponent implements OnInit {
|
|||
comments: [''],
|
||||
remotePc: [false],
|
||||
softwareProfile: ['', Validators.required],
|
||||
imageRepository: ['', Validators.required],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -37,6 +39,7 @@ export class CreateImageComponent implements OnInit {
|
|||
this.load()
|
||||
}
|
||||
this.fetchSoftwareProfiles();
|
||||
this.fetchRepositories();
|
||||
}
|
||||
|
||||
load(): void {
|
||||
|
@ -47,7 +50,8 @@ export class CreateImageComponent implements OnInit {
|
|||
description: [response.description],
|
||||
comments: [response.comments],
|
||||
remotePc: [response.remotePc],
|
||||
softwareProfile: [response.softwareProfile, Validators.required],
|
||||
softwareProfile: [response.softwareProfile['@id'], Validators.required],
|
||||
imageRepository: [response.repository['@id'], Validators.required],
|
||||
});
|
||||
this.imageId = response['@id'];
|
||||
},
|
||||
|
@ -70,13 +74,32 @@ export class CreateImageComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
fetchRepositories() {
|
||||
const url = `${this.baseUrl}/image-repositories`;
|
||||
this.http.get(url).subscribe({
|
||||
next: (response: any) => {
|
||||
this.repositories = response['hydra:member'];
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al obtener los repositorios de imágenes:', error);
|
||||
this.toastService.error('Error al obtener los repositorios de imágenes');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
saveImage(): void {
|
||||
if (this.imageForm.invalid) {
|
||||
this.toastService.error('Por favor, rellena los campos obligatorios');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
name: this.imageForm.value.name,
|
||||
description: this.imageForm.value.description,
|
||||
comments: this.imageForm.value.comments,
|
||||
remotePc: this.imageForm.value.remotePc,
|
||||
softwareProfile: this.imageForm.value.softwareProfile
|
||||
softwareProfile: this.imageForm.value.softwareProfile,
|
||||
imageRepository: this.imageForm.value.imageRepository,
|
||||
};
|
||||
|
||||
if (this.imageId) {
|
||||
|
|
|
@ -15,16 +15,18 @@
|
|||
</button>
|
||||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
</div>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>Buscar nombre de imagen</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchImageField" text="Busca una imagen por nombre. Pulsa 'enter' para iniciar la búsqueda.">
|
||||
<mat-label>Buscar nombre de imagen</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
|
@ -42,18 +44,20 @@
|
|||
</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 client" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="editImage($event, client)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="deleteImage($event, client)">
|
||||
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;" joyrideStep="actionsHeader" text="Acciones disponibles para cada imagen.">Acciones</th>
|
||||
<td mat-cell *matCellDef="let client" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="editImage($event, client)" joyrideStep="editImageButton" text="Editar la imagen seleccionada.">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="deleteImage($event, client)" joyrideStep="deleteImageButton" text="Eliminar la imagen seleccionada.">
|
||||
<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">
|
||||
<mat-paginator [length]="length"
|
||||
|
|
|
@ -7,6 +7,7 @@ import { DatePipe } from '@angular/common';
|
|||
import { CreateImageComponent } from './create-image/create-image.component';
|
||||
import {CreateCommandComponent} from "../commands/main-commands/create-command/create-command.component";
|
||||
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-images',
|
||||
|
@ -38,9 +39,14 @@ export class ImagesComponent implements OnInit {
|
|||
header: 'Perfil de software',
|
||||
cell: (image: any) => `${image.softwareProfile?.description}`
|
||||
},
|
||||
{
|
||||
columnDef: 'imageRepository',
|
||||
header: 'Repositorio',
|
||||
cell: (image: any) => `${image.imageRepository?.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'remotePc',
|
||||
header: 'Acceso remoto',
|
||||
header: 'Remote Pc',
|
||||
cell: (image: any) => `${image.remotePc}`
|
||||
},
|
||||
{
|
||||
|
@ -56,7 +62,8 @@ export class ImagesComponent implements OnInit {
|
|||
constructor(
|
||||
public dialog: MatDialog,
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService
|
||||
private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -122,4 +129,22 @@ export class ImagesComponent implements OnInit {
|
|||
this.length = event.length;
|
||||
this.search();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'imagesTitleStep',
|
||||
'addImageButton',
|
||||
'searchImageField',
|
||||
'imagesTable',
|
||||
'actionsHeader',
|
||||
'editImageButton',
|
||||
'deleteImageButton',
|
||||
'imagesPagination'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,17 +11,15 @@
|
|||
<mat-label>{{ 'loginlabelPassword' | translate }}</mat-label>
|
||||
<input matInput (keydown.enter)="$event.preventDefault()" [type]="hide() ? 'password' : 'text'" required
|
||||
[(ngModel)]="loginObj.password" name="password" />
|
||||
<button mat-icon-button matSuffix (click)="clickEvent($event)" [attr.aria-label]="'Ocultar contraseña'">
|
||||
<button mat-icon-button matSuffix type="button" (click)="clickEvent($event)" [attr.aria-label]="'Ocultar contraseña'">
|
||||
<mat-icon>{{hide() ? 'visibility_off' : 'visibility'}}</mat-icon>
|
||||
</button>
|
||||
|
||||
</mat-form-field>
|
||||
<div class="button-row">
|
||||
<button mat-flat-button color="primary" type="submit" [disabled]="!loginObj.username || !loginObj.password">
|
||||
{{ 'buttonLogin' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<<<<<<< Updated upstream
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.disk-usage-info{
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
|
|
|
@ -22,19 +22,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="services-status">
|
||||
<h3>Servicios</h3>
|
||||
<ul>
|
||||
<li *ngFor="let service of getServices()">
|
||||
<span
|
||||
class="status-led"
|
||||
[ngClass]="{ 'active': service.status === 'active', 'inactive': service.status !== 'active' }"
|
||||
></span>
|
||||
{{ service.name }}: {{ service.status }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div 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">
|
||||
<h3>OGLives instalados</h3>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-ogboot-status',
|
||||
|
@ -15,7 +16,6 @@ export class OgbootStatusComponent implements OnInit {
|
|||
|
||||
view: [number, number] = [1100, 500];
|
||||
|
||||
// Opciones de la gráfica
|
||||
gradient: boolean = true;
|
||||
showLegend: boolean = true;
|
||||
showLabels: boolean = true;
|
||||
|
@ -24,7 +24,7 @@ export class OgbootStatusComponent implements OnInit {
|
|||
domain: ['#FF6384', '#3f51b5']
|
||||
};
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
constructor(private http: HttpClient, private joyrideService: JoyrideService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadStatus();
|
||||
|
@ -57,4 +57,30 @@ export class OgbootStatusComponent implements OnInit {
|
|||
status: this.servicesStatus[key]
|
||||
}));
|
||||
}
|
||||
|
||||
formatBytes(bytes: number): string {
|
||||
if (bytes >= 1e9) {
|
||||
return (bytes / 1e9).toFixed(2) + ' GB';
|
||||
} else if (bytes >= 1e6) {
|
||||
return (bytes / 1e6).toFixed(2) + ' MB';
|
||||
} else if (bytes >= 1e3) {
|
||||
return (bytes / 1e3).toFixed(2) + ' KB';
|
||||
} else {
|
||||
return bytes + ' B';
|
||||
}
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'diskUsageStep',
|
||||
'servicesStatusStep',
|
||||
'oglivesStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
|
||||
<div class="header-container">
|
||||
<<<<<<< Updated upstream
|
||||
<h2 class="title">Netboot avanzado</h2>
|
||||
</div>
|
||||
|
||||
<div [formGroup]="taskForm" class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-boolean">
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="selectUnitStep" text="Selecciona la Unidad Organizacional para listar las aulas disponibles.">
|
||||
<mat-label>Selecciona Unidad Organizacional</mat-label>
|
||||
=======
|
||||
<h2 class="title" joyrideStep="titleStep" text="{{ 'advancedNetbootDescription' | translate }}">
|
||||
|
@ -55,12 +54,12 @@
|
|||
<<<<<<< Updated upstream
|
||||
<mat-form-field appearance="fill" class="selected-global">
|
||||
<mat-label>Seleccione plantilla para aplicar a todos los clientes</mat-label>
|
||||
<mat-select [(value)]="globalOgLive" (selectionChange)="applyToAll()" >
|
||||
<mat-select [(value)]="globalOgLive" (selectionChange)="applyToAll()">
|
||||
<mat-option *ngFor="let option of ogLiveOptions" [value]="option['@id']">{{ option.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-flat-button color="primary" [disabled]="selectedUnitChildren.length === 0" (click)="saveOgLiveTemplates()">Guardar</button>
|
||||
<button mat-flat-button color="primary" [disabled]="selectedUnitChildren.length === 0" (click)="saveOgLiveTemplates()" joyrideStep="saveButtonStep" text="Haz clic para guardar la configuración actual de plantillas.">Guardar</button>
|
||||
</div>
|
||||
|
||||
<mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
|
@ -117,7 +116,6 @@
|
|||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
</mat-table>
|
||||
|
|
|
@ -5,6 +5,7 @@ import { ToastrService } from 'ngx-toastr';
|
|||
import { PageEvent } from '@angular/material/paginator';
|
||||
import {Observable} from "rxjs";
|
||||
import {ServerInfoDialogComponent} from "../../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pxe-boot-files',
|
||||
|
@ -32,7 +33,8 @@ export class PxeBootFilesComponent implements OnInit {
|
|||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService
|
||||
private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService
|
||||
) {
|
||||
this.taskForm = this.fb.group({
|
||||
organizationalUnit: ['', Validators.required],
|
||||
|
@ -155,4 +157,21 @@ export class PxeBootFilesComponent implements OnInit {
|
|||
this.itemsPerPage = event.pageSize;
|
||||
this.fetchPxeTemplates();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'selectUnitStep',
|
||||
'selectClassStep',
|
||||
'applyToAllStep',
|
||||
'saveButtonStep',
|
||||
'tableStep',
|
||||
'selectTemplateStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<mat-panel-title>{{ 'serverInfoTitle' | translate }}</mat-panel-title>
|
||||
>>>>>>> Stashed changes
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div class="button-row">
|
||||
<<<<<<< Updated upstream
|
||||
<button mat-flat-button color="primary" (click)="syncOgBoot()"> Sincronizar base de datos</button>
|
||||
|
@ -44,7 +43,9 @@
|
|||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
|
@ -61,13 +62,14 @@
|
|||
<<<<<<< Updated upstream
|
||||
<mat-form-field appearance="fill" class="search-boolean">
|
||||
<mat-label i18n="@@searchLabel">Imagen por defecto</mat-label>
|
||||
<mat-select [(ngModel)]="filters['isDefault']" (selectionChange)="search()" placeholder="Seleccionar opción" >
|
||||
<mat-select [(ngModel)]="filters['isDefault']" (selectionChange)="search()" placeholder="Seleccionar opción">
|
||||
<mat-option [value]="''">Todos</mat-option>
|
||||
<mat-option [value]="true">Sí</mat-option>
|
||||
<mat-option [value]="false">No</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-boolean">
|
||||
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="searchInstalledStep" text="Filtra las imágenes para mostrar solo las instaladas en el servidor OgBoot.">
|
||||
<mat-label i18n="@@searchLabel">Instalado servidor ogBoot</mat-label>
|
||||
<mat-select [(ngModel)]="filters['installed']" (selectionChange)="search()" placeholder="Seleccionar opción">
|
||||
<mat-option [value]="''">Todos</mat-option>
|
||||
|
@ -76,7 +78,8 @@
|
|||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="Aquí se muestra la lista de imágenes disponibles para administrar.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image" >
|
||||
|
@ -139,16 +142,15 @@
|
|||
<ng-container *ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl' && column.columnDef !== 'status' && column.columnDef !== 'name'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<<<<<<< Updated upstream
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions">Acciones</th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<td mat-cell *matCellDef="let image" joyrideStep="actionsStep" text="Administra cada imagen con opciones para ver, editar, eliminar y más.">
|
||||
<button mat-icon-button color="info" (click)="showOgLive($event, image)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editImage(image)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editImage(image)" i18n="@@editImage"><mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="deleteImage(image)" i18n="@@buttonDelete"><mat-icon>delete</mat-icon></button>
|
||||
=======
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'actionsColumnHeader' | translate }}</th>
|
||||
|
|
|
@ -12,6 +12,7 @@ import {DataService} from "./data.service";
|
|||
import {ServerInfoDialogComponent} from "../../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component";
|
||||
import {ShowTemplateContentComponent} from "../pxe/show-template-content/show-template-content.component";
|
||||
import {Observable} from "rxjs";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pxe-images',
|
||||
|
@ -77,7 +78,8 @@ export class PXEimagesComponent implements OnInit {
|
|||
public dialog: MatDialog,
|
||||
private http: HttpClient,
|
||||
private dataService: DataService,
|
||||
private toastService: ToastrService
|
||||
private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -254,4 +256,23 @@ export class PXEimagesComponent implements OnInit {
|
|||
this.toastService.error('Error al sincronizar');
|
||||
});
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'serverInfoStep',
|
||||
'titleStep',
|
||||
'addImageStep',
|
||||
'searchNameStep',
|
||||
'searchDefaultImageStep',
|
||||
'searchInstalledStep',
|
||||
'tableStep',
|
||||
'actionsStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
|
@ -53,9 +52,9 @@ h3 {
|
|||
|
||||
.list-item-content {
|
||||
display: flex;
|
||||
align-items: flex-start; /* Alinea el contenido al inicio */
|
||||
justify-content: space-between; /* Espacio entre los textos y los íconos */
|
||||
width: 100%; /* Asegúrate de que el contenido ocupe todo el ancho */
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
|
@ -73,3 +72,15 @@ h3 {
|
|||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
<mat-dialog-content>
|
||||
<div class="spacing-container">
|
||||
<form [formGroup]="templateForm" (ngSubmit)="onSave()">
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>{{ 'templateNameLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" [placeholder]="'templateNamePlaceholder' | translate">
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import {MatDialogRef, MAT_DIALOG_DATA, MatDialog} from '@angular/material/dialog';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import {DeleteModalComponent} from "../../../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import { DeleteModalComponent } from "../../../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-pxe-template',
|
||||
|
@ -17,6 +17,39 @@ export class CreatePxeTemplateComponent implements OnInit {
|
|||
isEditMode: boolean = false;
|
||||
clients: any[] = [];
|
||||
|
||||
templateModels = {
|
||||
ogLive: `#!ipxe
|
||||
set timeout 0
|
||||
set timeout-style hidden
|
||||
set ISODIR __OGLIVE__
|
||||
set default 0
|
||||
set kernelargs __INFOHOST__
|
||||
:try_iso
|
||||
kernel http://__SERVERIP__/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs} || goto fallback
|
||||
initrd http://__SERVERIP__/tftpboot/\${ISODIR}/oginitrd.img
|
||||
boot
|
||||
|
||||
:fallback
|
||||
set ISODIR ogLive
|
||||
kernel http://__SERVERIP__/tftpboot/\${ISODIR}/ogvmlinuz \${kernelargs}
|
||||
initrd http://__SERVERIP__/tftpboot/\${ISODIR}/oginitrd.img
|
||||
boot`,
|
||||
|
||||
disco: `#!ipxe
|
||||
|
||||
iseq \${platform} efi && goto uefi_boot || goto bios_boot
|
||||
|
||||
:bios_boot
|
||||
echo "Running in BIOS mode - Booting first disk"
|
||||
chain http://__SERVERIP__/tftpboot/grub.exe --config-file="title FirstHardDisk;chainloader (hd0)+1;rootnoverify (hd0);boot" || echo "Failed to boot in BIOS mode"
|
||||
exit
|
||||
|
||||
:uefi_boot
|
||||
echo "Running in UEFI mode - Booting first disk"
|
||||
sanboot --no-describe --drive 0 --filename \\EFI\\grub\\Boot\\grubx64.efi || echo "Failed to boot in UEFI mode"
|
||||
exit`
|
||||
};
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<CreatePxeTemplateComponent>,
|
||||
public dialog: MatDialog,
|
||||
|
@ -29,8 +62,8 @@ export class CreatePxeTemplateComponent implements OnInit {
|
|||
ngOnInit() {
|
||||
this.isEditMode = !!this.data;
|
||||
|
||||
if (this.isEditMode){
|
||||
this.getPxeClients()
|
||||
if (this.isEditMode) {
|
||||
this.getPxeClients();
|
||||
}
|
||||
|
||||
this.templateForm = this.fb.group({
|
||||
|
@ -50,7 +83,7 @@ export class CreatePxeTemplateComponent implements OnInit {
|
|||
getPxeClients(): void {
|
||||
this.http.get<any>(`${this.baseUrl}/clients?template.id=${this.data.id}`).subscribe({
|
||||
next: data => {
|
||||
this.clients = data['hydra:member']
|
||||
this.clients = data['hydra:member'];
|
||||
},
|
||||
error: error => {
|
||||
console.error('Error al obtener los clientes PXE:', error);
|
||||
|
@ -67,12 +100,10 @@ export class CreatePxeTemplateComponent implements OnInit {
|
|||
|
||||
this.http.post<any>(`${this.baseUrl}/pxe-templates`, payload).subscribe({
|
||||
next: data => {
|
||||
console.log('Plantilla PXE creada:', data);
|
||||
this.toastService.success('Plantilla PXE creada exitosamente');
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
error: error => {
|
||||
console.error('Error al crear la plantilla PXE:', error);
|
||||
this.toastService.error('Error al crear la plantilla PXE');
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
@ -98,13 +129,19 @@ export class CreatePxeTemplateComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
loadTemplateModel(type: 'ogLive' | 'disco'): void {
|
||||
const selectedContent = this.templateModels[type];
|
||||
this.templateForm.get('templateContent')?.setValue(selectedContent);
|
||||
this.toastService.info(`Plantilla ${type} cargada.`);
|
||||
}
|
||||
|
||||
addClientToTemplate(client: any): void {
|
||||
const postData = {
|
||||
client: client['@id']
|
||||
};
|
||||
|
||||
this.http.post(`${this.baseUrl}/pxe-templates/${this.data.uuid}/sync-client`, postData).subscribe(
|
||||
response => {
|
||||
() => {
|
||||
this.toastService.success('Clientes asignados correctamente');
|
||||
},
|
||||
error => {
|
||||
|
@ -126,11 +163,12 @@ export class CreatePxeTemplateComponent implements OnInit {
|
|||
this.toastService.success('Cliente eliminado exitosamente');
|
||||
this.dialogRef.close();
|
||||
},
|
||||
error: (error) => {
|
||||
error: error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
}})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onCancel(): void {
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
|
||||
|
||||
<div class="header-container">
|
||||
<<<<<<< Updated upstream
|
||||
<h2 class="title" i18n="@@adminPXETitle">Administrar plantillas PXE</h2>
|
||||
|
@ -36,7 +35,9 @@
|
|||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
|
@ -53,14 +54,15 @@
|
|||
<<<<<<< Updated upstream
|
||||
<mat-form-field appearance="fill" class="search-boolean">
|
||||
<mat-label i18n="@@searchLabel">Creada en ogBoot</mat-label>
|
||||
<mat-select [(ngModel)]="filters['synchronized']" (selectionChange)="search()" placeholder="Seleccionar opción" >
|
||||
<mat-select [(ngModel)]="filters['synchronized']" (selectionChange)="search()" placeholder="Seleccionar opción">
|
||||
<mat-option [value]="''">Todos</mat-option>
|
||||
<mat-option [value]="true">Sí</mat-option>
|
||||
<mat-option [value]="false">No</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="Aquí se muestra la lista de plantillas PXE disponibles para administrar.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image" >
|
||||
|
@ -94,7 +96,7 @@
|
|||
<<<<<<< Updated upstream
|
||||
<ng-container matColumnDef="actions" >
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let template" style="text-align: center;">
|
||||
<td mat-cell *matCellDef="let template" style="text-align: center;" joyrideStep="actionsStep" text="Gestiona cada plantilla PXE con opciones para ver, editar, eliminar y más.">
|
||||
<button mat-icon-button color="info" (click)="showTemplate($event, template)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
|
||||
<button mat-icon-button color="info" [disabled]="template.clientsLength === 0" (click)="editClients($event, template)"><mat-icon i18n="@@deleteElementTooltip">computer</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editPxeTemplate(template)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
|
@ -118,7 +120,7 @@
|
|||
<mat-menu #menu="matMenu">
|
||||
<<<<<<< Updated upstream
|
||||
<button mat-menu-item (click)="toggleAction(template, 'create')">Crear en servidor ogBoot</button>
|
||||
<button mat-menu-item (click)="addClientsToPxe(template)">Añadir cliente</button>
|
||||
<button mat-menu-item (click)="addClientsToPxe(template)">Añadir cliente</button>
|
||||
<button mat-menu-item (click)="toggleAction(template, 'sync')">Sincronizar base de datos</button>
|
||||
<button mat-menu-item (click)="toggleAction(template, 'delete')">Eliminar</button>
|
||||
=======
|
||||
|
|
|
@ -20,6 +20,7 @@ import {Subnet} from "../../ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component";
|
|||
import {AddClientsToPxeComponent} from "./add-clients-to-pxe/add-clients-to-pxe.component";
|
||||
import {Observable} from "rxjs";
|
||||
import {ClientsComponent} from "./clients/clients.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pxe',
|
||||
|
@ -72,7 +73,8 @@ export class PxeComponent {
|
|||
public dialog: MatDialog,
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
private dataService: DataService
|
||||
private dataService: DataService,
|
||||
private joyrideService: JoyrideService
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -228,4 +230,22 @@ export class PxeComponent {
|
|||
this.itemsPerPage = event.pageSize;
|
||||
this.applyFilter();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'serverInfoStep',
|
||||
'titleStep',
|
||||
'addTemplateStep',
|
||||
'searchNameStep',
|
||||
'searchSyncStep',
|
||||
'tableStep',
|
||||
'actionsStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,25 +1,32 @@
|
|||
<h2 mat-dialog-title>Añade clientes a {{data.subnetName}}</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<mat-form-field appearance="fill" class="search-select">
|
||||
<input type="text" matInput [formControl]="clientControl" [matAutocomplete]="clientAuto" placeholder="Seleccione un cliente">
|
||||
<mat-autocomplete #clientAuto="matAutocomplete" [displayWith]="displayFnClient" (optionSelected)="onOptionClientSelected($event.option.value)">
|
||||
<mat-option *ngFor="let client of filteredClients | async" [value]="client">
|
||||
{{ client.name }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Unidad Organizativa</mat-label>
|
||||
<mat-select [formControl]="unitControl" (selectionChange)="onUnitChange($event.value)">
|
||||
<mat-option *ngFor="let unit of units" [value]="unit.uuid">{{ unit.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div *ngIf="selectedClients.length > 0">
|
||||
<h3>Clientes seleccionados:</h3>
|
||||
<ul>
|
||||
<li *ngFor="let client of selectedClients">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Subunidad Organizativa</mat-label>
|
||||
<mat-select [formControl]="childUnitControl" (selectionChange)="onChildUnitChange($event.value)">
|
||||
<mat-option *ngFor="let child of childUnits" [value]="child.uuid">{{ child.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<label>Clientes</label>
|
||||
<div *ngIf="clients.length > 0">
|
||||
<mat-checkbox *ngFor="let client of clients"
|
||||
(change)="toggleClientSelection(client.uuid)"
|
||||
[checked]="selectedClients.includes(client.uuid)">
|
||||
{{ client.name }}
|
||||
<button mat-icon-button color="warn" (click)="removeClient(client)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div *ngIf="clients.length === 0">
|
||||
<p>No hay clientes disponibles</p>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import {Observable, startWith} from "rxjs";
|
||||
import {map} from "rxjs/operators";
|
||||
import {FormControl} from "@angular/forms";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-clients-to-subnet',
|
||||
|
@ -13,59 +11,93 @@ import {ToastrService} from "ngx-toastr";
|
|||
})
|
||||
export class AddClientsToSubnetComponent implements OnInit {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
units: any[] = [];
|
||||
childUnits: any[] = [];
|
||||
clients: any[] = [];
|
||||
selectedClients: any[] = [];
|
||||
selectedClients: string[] = [];
|
||||
loading: boolean = true;
|
||||
filters: { [key: string]: string } = {};
|
||||
filteredClients!: Observable<any[]>;
|
||||
clientControl = new FormControl();
|
||||
unitControl = new FormControl();
|
||||
childUnitControl = new FormControl();
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
public dialogRef: MatDialogRef<AddClientsToSubnetComponent>,
|
||||
private toastService: ToastrService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { subnetUuid: string, subnetName: string }
|
||||
) {}
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log('Selected subnet UUID:', this.data);
|
||||
this.loading = true;
|
||||
this.loadUnits();
|
||||
}
|
||||
|
||||
this.loadClients();
|
||||
|
||||
this.filteredClients = this.clientControl.valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => (typeof value === 'string' ? value : value?.name)),
|
||||
map(name => (name ? this._filterClients(name) : this.clients.slice()))
|
||||
loadUnits() {
|
||||
this.http.get<any>(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=50`).subscribe(
|
||||
response => {
|
||||
this.units = response['hydra:member'].filter((unit: { type: string; }) => unit.type === 'organizational-unit');
|
||||
this.loading = false;
|
||||
},
|
||||
error => console.error('Error fetching organizational units:', error)
|
||||
);
|
||||
}
|
||||
|
||||
loadClients() {
|
||||
this.http.get<any>( `${this.baseUrl}/clients?&page=1&itemsPerPage=10000&exists[subnet]=false`).subscribe(
|
||||
response => {
|
||||
this.clients = response['hydra:member'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching parent units:', error);
|
||||
this.loading = false;
|
||||
onUnitChange(unitId: string): void {
|
||||
const unit = this.units.find(unit => unit.uuid === unitId);
|
||||
this.childUnits = unit ? this.getAllChildren(unit) : [];
|
||||
this.clients = [];
|
||||
this.childUnitControl.setValue(null);
|
||||
this.selectedClients = [];
|
||||
}
|
||||
|
||||
getAllChildren(unit: any): any[] {
|
||||
let allChildren = [];
|
||||
if (unit.children && unit.children.length > 0) {
|
||||
for (const child of unit.children) {
|
||||
allChildren.push(child);
|
||||
allChildren = allChildren.concat(this.getAllChildren(child));
|
||||
}
|
||||
);
|
||||
}
|
||||
return allChildren;
|
||||
}
|
||||
|
||||
onChildUnitChange(childUnitId: string): void {
|
||||
const childUnit = this.childUnits.find(unit => unit.uuid === childUnitId);
|
||||
this.clients = childUnit && childUnit.clients ? childUnit.clients : [];
|
||||
this.selectedClients = [];
|
||||
}
|
||||
|
||||
toggleClientSelection(clientId: string): void {
|
||||
const index = this.selectedClients.indexOf(clientId);
|
||||
if (index >= 0) {
|
||||
this.selectedClients.splice(index, 1);
|
||||
} else {
|
||||
this.selectedClients.push(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
toggleSelectAll(): void {
|
||||
if (this.areAllClientsSelected()) {
|
||||
this.selectedClients = [];
|
||||
} else {
|
||||
this.selectedClients = this.clients.map(client => client.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
areAllClientsSelected(): boolean {
|
||||
return this.selectedClients.length === this.clients.length;
|
||||
}
|
||||
|
||||
save() {
|
||||
this.selectedClients.forEach(client => {
|
||||
const postData = {
|
||||
client: client['@id']
|
||||
};
|
||||
this.selectedClients.forEach(clientId => {
|
||||
const postData = { client: `/clients/${clientId}` };
|
||||
|
||||
this.http.post(`${this.baseUrl}/og-dhcp/server/${this.data.subnetUuid}/post-host`, postData).subscribe(
|
||||
response => {
|
||||
this.toastService.success(`Cliente ${client.name} asignado correctamente`);
|
||||
this.toastService.success(`Cliente asignado correctamente`);
|
||||
},
|
||||
error => {
|
||||
console.error(`Error al asignar el cliente ${client.name}:`, error);
|
||||
this.toastService.error(`Error al asignar el cliente ${client.name}: ${error.error['hydra:description']}`);
|
||||
console.error(`Error al asignar el cliente:`, error);
|
||||
this.toastService.error(`Error al asignar el cliente: ${error.error['hydra:description']}`);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -76,27 +108,4 @@ export class AddClientsToSubnetComponent implements OnInit {
|
|||
close() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
removeClient(client: any) {
|
||||
const index = this.selectedClients.indexOf(client);
|
||||
if (index >= 0) {
|
||||
this.selectedClients.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private _filterClients(name: string): any[] {
|
||||
const filterValue = name.toLowerCase();
|
||||
return this.clients.filter(client => client.name.toLowerCase().includes(filterValue));
|
||||
}
|
||||
|
||||
displayFnClient(client: any): string {
|
||||
return client && client.name ? client.name : '';
|
||||
}
|
||||
|
||||
onOptionClientSelected(client: any) {
|
||||
if (!this.selectedClients.includes(client)) {
|
||||
this.selectedClients.push(client);
|
||||
}
|
||||
this.clientControl.setValue('');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,9 @@ form{
|
|||
|
||||
.list-item-content {
|
||||
display: flex;
|
||||
align-items: flex-start; /* Alinea el contenido al inicio */
|
||||
justify-content: space-between; /* Espacio entre los textos y los íconos */
|
||||
width: 100%; /* Asegúrate de que el contenido ocupe todo el ancho */
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
|
@ -33,4 +33,3 @@ form{
|
|||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,21 +16,29 @@
|
|||
<mat-label>Dirección IP</mat-label>
|
||||
<input matInput [(ngModel)]="ipAddress" placeholder="Dirección IP" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Next Server</mat-label>
|
||||
<input matInput [(ngModel)]="nextServer" placeholder="Next Server" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Boot File Name</mat-label>
|
||||
<input matInput [(ngModel)]="bootFileName" placeholder="Boot File Name" required>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Parámetros Avanzados -->
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>Parámetros avanzados</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Next Server</mat-label>
|
||||
<input matInput [(ngModel)]="nextServer" placeholder="Next Server">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Boot File Name</mat-label>
|
||||
<input matInput [(ngModel)]="bootFileName" placeholder="Boot File Name">
|
||||
</mat-form-field>
|
||||
</mat-expansion-panel>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab *ngIf="isEditMode" label="Clientes">
|
||||
<mat-list>
|
||||
<ng-container *ngFor="let client of clients">
|
||||
<mat-list-item >
|
||||
<mat-list-item>
|
||||
<div class="list-item-content">
|
||||
<mat-icon matListItemIcon>computer</mat-icon>
|
||||
<div class="text-content">
|
||||
|
|
|
@ -58,8 +58,8 @@ export class CreateSubnetComponent implements OnInit {
|
|||
name: this.name,
|
||||
netmask: this.netmask,
|
||||
ipAddress: this.ipAddress,
|
||||
nextServer: this.nextServer,
|
||||
bootFileName: this.bootFileName
|
||||
nextServer: this.nextServer || null,
|
||||
bootFileName: this.bootFileName || null
|
||||
};
|
||||
|
||||
if (!this.data){
|
||||
|
|
|
@ -1,57 +1,62 @@
|
|||
<mat-accordion class="example-headers-align">
|
||||
<mat-expansion-panel hideToggle>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-expansion-panel-header joyrideStep="serverInfoStep" text="Despliega este contenedor para acceder a la información y opciones de sincronización en el servidor OgDHCP.">
|
||||
<mat-panel-title> Información en servidor ogDHCP </mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<div class="example-button-row">
|
||||
<button mat-flat-button color="primary" (click)="syncSubnets()"> Sincronizar base de datos</button>
|
||||
<button mat-flat-button color="primary" (click)="syncSubnets()" joyrideStep="syncDbStep" text="Sincroniza la base de datos del servidor OgDHCP para actualizar la información de las subredes."> Sincronizar base de datos</button>
|
||||
</div>
|
||||
<div class="example-button-row">
|
||||
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()">Ver Información</button>
|
||||
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()" joyrideStep="viewInfoStep" text="Haz clic para ver información detallada de las subredes en el servidor.">Ver Información</button>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
|
||||
<div class="header-container">
|
||||
<h2 class="title" i18n="@@subnetsTitle">Administrar Subredes</h2>
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" i18n="@@subnetsTitle" joyrideStep="titleStep" text="Desde aquí puedes gestionar las subredes configuradas en el servidor OgDHCP.">Administrar Subredes</h2>
|
||||
<div class="subnets-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addSubnet()">Añadir Subred</button>
|
||||
<button mat-flat-button color="primary" (click)="addSubnet()" joyrideStep="addSubnetStep" text="Haz clic para añadir una nueva subred.">Añadir Subred</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNameStep" text="Busca subredes por nombre para localizar una subred específica rápidamente.">
|
||||
<mat-label i18n="@@searchLabel">Buscar nombre de la subred</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" i18n-placeholder="@@searchPlaceholder">
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNetmaskStep" text="Busca subredes usando una netmask específica.">
|
||||
<mat-label i18n="@@searchLabel">Buscar netmask</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['netmask']" i18n-placeholder="@@searchPlaceholder">
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['netmask']" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchIpStep" text="Busca subredes por dirección IP.">
|
||||
<mat-label i18n="@@searchLabel">Buscar IP</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['ip']" i18n-placeholder="@@searchPlaceholder">
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['ip']" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchBootFileStep" text="Busca subredes según el nombre del archivo de arranque.">
|
||||
<mat-label i18n="@@searchLabel">Buscar Boot file name</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['bootFileName']" i18n-placeholder="@@searchPlaceholder">
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['bootFileName']" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
|
||||
<table 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' }}
|
||||
|
@ -75,9 +80,10 @@
|
|||
|
||||
<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;">
|
||||
<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>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="toggleAction(subnet, 'delete')"><mat-icon>delete</mat-icon></button>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu">
|
||||
<mat-icon>menu</mat-icon>
|
||||
|
@ -93,8 +99,8 @@
|
|||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
|
||||
<div class="paginator-container" joyrideStep="paginationStep" text="Navega entre las páginas de subredes usando el paginador.">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions" (page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
|
|
@ -7,8 +7,9 @@ import { HttpClient } from '@angular/common/http';
|
|||
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { AddClientsToSubnetComponent } from './add-clients-to-subnet/add-clients-to-subnet.component';
|
||||
import {ServerInfoDialogComponent} from "./server-info-dialog/server-info-dialog.component";
|
||||
import {Observable} from "rxjs";
|
||||
import { ServerInfoDialogComponent } from "./server-info-dialog/server-info-dialog.component";
|
||||
import { Observable } from "rxjs";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
export interface Subnet {
|
||||
'@id': string;
|
||||
|
@ -32,7 +33,7 @@ export interface Subnet {
|
|||
templateUrl: './og-dhcp-subnets.component.html',
|
||||
styleUrls: ['./og-dhcp-subnets.component.css']
|
||||
})
|
||||
export class OgDhcpSubnetsComponent {
|
||||
export class OgDhcpSubnetsComponent {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
displayedColumns: string[] = ['id', 'name', 'netmask', 'ipAddress', 'nextServer', 'bootFileName', 'synchronized', 'serverId', 'clients', 'actions'];
|
||||
dataSource = new MatTableDataSource<Subnet>([]);
|
||||
|
@ -52,14 +53,15 @@ export class OgDhcpSubnetsComponent {
|
|||
{ columnDef: 'ipAddress', header: 'IP Address', cell: (subnet: Subnet) => subnet.ipAddress },
|
||||
{ columnDef: 'nextServer', header: 'Next Server', cell: (subnet: Subnet) => subnet.nextServer },
|
||||
{ columnDef: 'bootFileName', header: 'Boot File Name', cell: (subnet: Subnet) => subnet.bootFileName },
|
||||
{ columnDef: 'synchronized', header: 'Sincronizado', cell: (subnet: Subnet) => `${subnet.synchronized}`},
|
||||
{ columnDef: 'synchronized', header: 'Sincronizado', cell: (subnet: Subnet) => `${subnet.synchronized}` },
|
||||
{ columnDef: 'serverId', header: 'Id Servidor DHCP', cell: (subnet: Subnet) => subnet.serverId },
|
||||
{ columnDef: 'clients', header: 'Lista de clientes', cell: (subnet: Subnet) => `${subnet.clients}`},
|
||||
{ columnDef: 'clients', header: 'Lista de clientes', cell: (subnet: Subnet) => `${subnet.clients}` },
|
||||
];
|
||||
|
||||
private apiUrl = `${this.baseUrl}/subnets`;
|
||||
|
||||
constructor(public dialog: MatDialog, private http: HttpClient, private toastService: ToastrService) {}
|
||||
constructor(public dialog: MatDialog, private http: HttpClient, private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.loadSubnets();
|
||||
|
@ -93,7 +95,7 @@ export class OgDhcpSubnetsComponent {
|
|||
});
|
||||
}
|
||||
|
||||
toggleAction(subnet: any, action:string): void {
|
||||
toggleAction(subnet: any, action: string): void {
|
||||
switch (action) {
|
||||
case 'get':
|
||||
this.http.post(`${this.baseUrl}/og-dhcp/server/${subnet.uuid}/get`, {}).subscribe({
|
||||
|
@ -149,7 +151,8 @@ export class OgDhcpSubnetsComponent {
|
|||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
}})
|
||||
}
|
||||
})
|
||||
break;
|
||||
default:
|
||||
console.error('Acción no soportada:', action);
|
||||
|
@ -216,4 +219,26 @@ export class OgDhcpSubnetsComponent {
|
|||
this.itemsPerPage = event.pageSize;
|
||||
this.loadSubnets();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'serverInfoStep',
|
||||
'syncDbStep',
|
||||
'viewInfoStep',
|
||||
'titleStep',
|
||||
'addSubnetStep',
|
||||
'searchNameStep',
|
||||
'searchNetmaskStep',
|
||||
'searchIpStep',
|
||||
'searchBootFileStep',
|
||||
'tableStep',
|
||||
'actionsStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -78,11 +78,17 @@ th {
|
|||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px; /* Espacio entre botones */
|
||||
gap: 10px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.btn:first-child {
|
||||
margin-left: 0;
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
<div class="dashboard">
|
||||
<h2>OgDhcp server Status</h2>
|
||||
<div class="header-container" >
|
||||
|
||||
<h2 joyrideStep="titleStep" text="Esta sección muestra el estado general del servidor OgDhcp.">OgDhcp server Status</h2>
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="disk-usage-container">
|
||||
<div class="disk-usage">
|
||||
<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"
|
||||
|
@ -21,40 +26,40 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="services-status">
|
||||
<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>
|
||||
<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">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Component } from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-status',
|
||||
|
@ -23,7 +24,8 @@ export class StatusComponent {
|
|||
domain: ['#FF6384', '#3f51b5']
|
||||
};
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
constructor(private http: HttpClient,
|
||||
private joyrideService: JoyrideService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadStatus();
|
||||
|
@ -55,4 +57,18 @@ export class StatusComponent {
|
|||
status: this.servicesStatus[key]
|
||||
}));
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'diskUsageStep',
|
||||
'servicesStatusStep',
|
||||
'subnetsStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
<div class="header-container">
|
||||
<h2 class="title" i18n="@@adminImagesTitle">Administrar sistemas operativos</h2>
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" i18n="@@adminImagesTitle" joyrideStep="osTitleStep" text="En esta pantalla, puedes gestionar los sistemas operativos disponibles.">
|
||||
Administrar sistemas operativos
|
||||
</h2>
|
||||
<div class="calendar-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addSoftware()">Añadir sistema operativo</button>
|
||||
<button mat-flat-button color="primary" (click)="addSoftware()" joyrideStep="addOsButton" text="Añade un nuevo sistema operativo a la lista.">Añadir sistema operativo</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchField" text="Busca un sistema operativo por nombre. Pulsa 'enter' para iniciar la búsqueda.">
|
||||
<mat-label i18n="@@searchLabel">Buscar nombre de sistema operativo</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="table" text="Esta tabla muestra los sistemas operativos existentes.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image" >
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container>
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
|
@ -24,17 +32,22 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;" joyrideStep="actionsHeader" text="Acciones disponibles para cada sistema operativo.">Acciones</th>
|
||||
<td mat-cell *matCellDef="let calendar" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="editSoftware(calendar)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="deleteSoftware(calendar)" i18n="@@buttonDelete"><mat-icon>delete</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editSoftware(calendar)" i18n="@@editImage" joyrideStep="editButton" text="Editar el sistema operativo seleccionado.">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="deleteSoftware(calendar)" i18n="@@buttonDelete" joyrideStep="deleteButton" text="Eliminar el sistema operativo seleccionado.">
|
||||
<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">
|
||||
|
||||
<div class="paginator-container" joyrideStep="pagination" text="Navega entre las páginas de sistemas operativos.">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
|
|
|
@ -9,6 +9,7 @@ import {CreateSoftwareComponent} from "../software/create-software/create-softwa
|
|||
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import {PageEvent} from "@angular/material/paginator";
|
||||
import {CreateOperativeSystemComponent} from "./create-operative-system/create-operative-system.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-operative-system',
|
||||
|
@ -53,7 +54,8 @@ export class OperativeSystemComponent {
|
|||
public dialog: MatDialog,
|
||||
private http: HttpClient,
|
||||
private dataService: DataService,
|
||||
private toastService: ToastrService
|
||||
private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -124,4 +126,13 @@ export class OperativeSystemComponent {
|
|||
this.length = event.length;
|
||||
this.search();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: ['osTitleStep', 'addOsButton', 'searchField', 'table', 'actionsHeader', 'editButton', 'deleteButton', 'pagination'],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
.dialog-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.repository-form {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<h2 mat-dialog-title> {{ repositoryId ? 'Editar' : 'Añadir' }} repositorio</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<form [formGroup]="imageForm" (ngSubmit)="save()" class="repository-form">
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>Nombre del repositorio</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>Ip</mat-label>
|
||||
<input matInput formControlName="ip" name="description">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>Comentarios</mat-label>
|
||||
<input matInput formControlName="comments" name="comments">
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="end" class="dialog-actions">
|
||||
<button mat-button (click)="close()">Cancelar</button>
|
||||
<button mat-button color="primary" (click)="save()">Guardar</button>
|
||||
</mat-dialog-actions>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CreateRepositoryComponent } from './create-repository.component';
|
||||
|
||||
describe('CreateRepositoryComponent', () => {
|
||||
let component: CreateRepositoryComponent;
|
||||
let fixture: ComponentFixture<CreateRepositoryComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [CreateRepositoryComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CreateRepositoryComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
import {Component, Inject} 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 {DataService} from "../../images/data.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-repository',
|
||||
templateUrl: './create-repository.component.html',
|
||||
styleUrl: './create-repository.component.css'
|
||||
})
|
||||
export class CreateRepositoryComponent {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
imageForm: FormGroup<any>;
|
||||
repositoryId: string | null = null;
|
||||
softwareProfiles: any[] = [];
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private http: HttpClient,
|
||||
public dialogRef: MatDialogRef<CreateRepositoryComponent>,
|
||||
private toastService: ToastrService,
|
||||
private dataService: DataService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any
|
||||
) {
|
||||
this.imageForm = this.fb.group({
|
||||
name: ['', Validators.required],
|
||||
ip: [''],
|
||||
comments: [''],
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.data) {
|
||||
this.load()
|
||||
}
|
||||
}
|
||||
|
||||
load(): void {
|
||||
this.dataService.getImage(this.data).subscribe({
|
||||
next: (response) => {
|
||||
this.imageForm = this.fb.group({
|
||||
name: [response.name, Validators.required],
|
||||
ip: [response.ip],
|
||||
comments: [response.comments],
|
||||
});
|
||||
this.repositoryId = response['@id'];
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error fetching remote calendar:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const payload = {
|
||||
name: this.imageForm.value.name,
|
||||
ip: this.imageForm.value.ip,
|
||||
comments: this.imageForm.value.comments,
|
||||
};
|
||||
|
||||
if (this.repositoryId) {
|
||||
this.http.put(`${this.baseUrl}${this.repositoryId}`, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Imagen editada correctamente');
|
||||
this.dialogRef.close();
|
||||
},
|
||||
(error) => {
|
||||
this.toastService.error(error['error']['hydra:description']);
|
||||
console.error('Error al editar la imagen', error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.http.post(`${this.baseUrl}/image-repositories`, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Imagen añadida correctamente');
|
||||
this.dialogRef.close();
|
||||
},
|
||||
(error) => {
|
||||
this.toastService.error(error['error']['hydra:description']);
|
||||
console.error('Error al añadir la imagen', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.images-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.lists-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.imagesLists-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card.unidad-card {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid rgba(122, 122, 122, 0.555);
|
||||
}
|
||||
|
||||
.image-container h4 {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.image-name{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-string {
|
||||
flex: 2;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.search-boolean {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.example-headers-align .mat-expansion-panel-header-description {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.example-button-row {
|
||||
display: table-cell;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.example-button-row .mat-mdc-button-base {
|
||||
margin: 8px 8px 8px 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RepositoriesComponent } from './repositories.component';
|
||||
|
||||
describe('RepositoriesComponent', () => {
|
||||
let component: RepositoriesComponent;
|
||||
let fixture: ComponentFixture<RepositoriesComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [RepositoriesComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(RepositoriesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -6,7 +6,7 @@ import {HttpClient} from "@angular/common/http";
|
|||
import {ToastrService} from "ngx-toastr";
|
||||
import {CreateImageComponent} from "../images/create-image/create-image.component";
|
||||
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import {CreateRepositoryComponent} from "./create-repository/create-repository.component";
|
||||
import { CreateRepositoryComponent } from './create-repository/create-repository.component';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
<mat-dialog-content class="form-container">
|
||||
<mat-tab-group>
|
||||
<!-- Primer tab: formulario de comandos -->
|
||||
<mat-tab label="Formulario">
|
||||
<form [formGroup]="formGroup" (ngSubmit)="onSubmit()" class="form-group">
|
||||
<mat-form-field appearance="fill" class="full-width" >
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import {Component, Inject} from '@angular/core';
|
||||
import {FormBuilder, FormControl, 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 {DataService as SoftwareService} from "../../software/data.service";
|
||||
import {DataService} from "../data.service";
|
||||
import {Observable, startWith} from "rxjs";
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormControl, 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 { DataService as SoftwareService } from '../../software/data.service';
|
||||
import { DataService } from '../data.service';
|
||||
import { Observable, startWith } from 'rxjs';
|
||||
import { debounceTime, switchMap, map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-software-profile',
|
||||
templateUrl: './create-software-profile.component.html',
|
||||
styleUrl: './create-software-profile.component.css'
|
||||
styleUrls: ['./create-software-profile.component.css']
|
||||
})
|
||||
export class CreateSoftwareProfileComponent {
|
||||
export class CreateSoftwareProfileComponent implements OnInit {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
formGroup: FormGroup<any>;
|
||||
formGroup: FormGroup;
|
||||
private apiUrl = `${this.baseUrl}/software-profiles`;
|
||||
softwareCollection: any[] = [];
|
||||
organizationalUnits: any[] = [];
|
||||
|
@ -45,11 +45,13 @@ export class CreateSoftwareProfileComponent {
|
|||
|
||||
ngOnInit(): void {
|
||||
if (this.data) {
|
||||
this.load()
|
||||
this.softwareProfileId = this.data['@id'];
|
||||
this.patchFormData();
|
||||
}
|
||||
|
||||
this.loadSoftware();
|
||||
this.loadOrganizationalUnits()
|
||||
this.loadOperativeSystems()
|
||||
this.loadOrganizationalUnits();
|
||||
this.loadOperativeSystems();
|
||||
|
||||
this.filteredSoftware = this.softwareControl.valueChanges.pipe(
|
||||
startWith(''),
|
||||
|
@ -58,42 +60,50 @@ export class CreateSoftwareProfileComponent {
|
|||
);
|
||||
}
|
||||
|
||||
patchFormData(): void {
|
||||
this.formGroup.patchValue({
|
||||
description: this.data.description || '',
|
||||
comments: this.data.comments || '',
|
||||
organizationalUnit: this.data.organizationalUnit ? this.data.organizationalUnit['@id'] : null,
|
||||
operativeSystem: this.data.operativeSystem ? this.data.operativeSystem['@id'] : null,
|
||||
});
|
||||
}
|
||||
|
||||
loadSoftware() {
|
||||
this.http.get<any>( `${this.baseUrl}/software?&page=1&itemsPerPage=10`).subscribe(
|
||||
this.http.get<any>(`${this.baseUrl}/software?page=1&itemsPerPage=10`).subscribe(
|
||||
response => {
|
||||
this.softwareCollection = response['hydra:member'];
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching parent units:', error);
|
||||
console.error('Error fetching software:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadOrganizationalUnits() {
|
||||
this.http.get<any>( `${this.baseUrl}/organizational-units?&page=1&itemsPerPage=10000`).subscribe(
|
||||
this.http.get<any>(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=10000`).subscribe(
|
||||
response => {
|
||||
this.organizationalUnits = response['hydra:member'];
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching parent units:', error);
|
||||
console.error('Error fetching organizational units:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadOperativeSystems() {
|
||||
this.http.get<any>( `${this.baseUrl}/operative-systems?&page=1&itemsPerPage=10000`).subscribe(
|
||||
this.http.get<any>(`${this.baseUrl}/operative-systems?page=1&itemsPerPage=10000`).subscribe(
|
||||
response => {
|
||||
this.operativeSystems = response['hydra:member'];
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching parent units:', error);
|
||||
console.error('Error fetching operative systems:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _filterSoftware(value: string): Observable<any[]> {
|
||||
|
||||
return this.softwareDataService.getSoftwareCollection({ 'name': value}).pipe(
|
||||
return this.softwareDataService.getSoftwareCollection({ name: value }).pipe(
|
||||
map(response => response || [])
|
||||
);
|
||||
}
|
||||
|
@ -109,24 +119,6 @@ export class CreateSoftwareProfileComponent {
|
|||
this.softwareControl.setValue('');
|
||||
}
|
||||
|
||||
load(): void {
|
||||
console.log(this.data);
|
||||
this.dataService.getSoftwareProfile(this.data).subscribe({
|
||||
next: (response) => {
|
||||
this.formGroup = this.fb.group({
|
||||
description: [response.description],
|
||||
comments: [response.comments],
|
||||
organizationalUnit: [response.organizationalUnit ? response.organizationalUnit['@id'] : null],
|
||||
operativeSystem: [response.operativeSystem ? response.operativeSystem['@id'] : null],
|
||||
});
|
||||
this.softwareProfileId = response['@id'];
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error fetching software:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addSoftware() {
|
||||
const software = this.softwareCollection.find(s => s.id === this.selectedSoftware);
|
||||
if (software && !this.selectedSoftwares.includes(software)) {
|
||||
|
@ -145,7 +137,6 @@ export class CreateSoftwareProfileComponent {
|
|||
|
||||
onSubmit(): void {
|
||||
if (this.formGroup.valid) {
|
||||
|
||||
const payload = {
|
||||
description: this.formGroup.value.description,
|
||||
comments: this.formGroup.value.comments,
|
||||
|
@ -156,24 +147,24 @@ export class CreateSoftwareProfileComponent {
|
|||
|
||||
if (this.softwareProfileId) {
|
||||
this.http.put(`${this.baseUrl}${this.softwareProfileId}`, payload).subscribe(
|
||||
(response) => {
|
||||
() => {
|
||||
this.toastService.success('Software editado correctamente');
|
||||
this.dialogRef.close();
|
||||
},
|
||||
(error) => {
|
||||
this.toastService.error(error['error']['hydra:description']);
|
||||
console.error('Error al editar el comando', error);
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
console.error('Error al editar el software', error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.http.post(`${this.baseUrl}/software-profiles`, payload).subscribe(
|
||||
(response) => {
|
||||
this.http.post(`${this.apiUrl}`, payload).subscribe(
|
||||
() => {
|
||||
this.toastService.success('Software añadido correctamente');
|
||||
this.dialogRef.close();
|
||||
},
|
||||
(error) => {
|
||||
this.toastService.error(error['error']['hydra:description']);
|
||||
console.error('Error al añadir comando', error);
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
console.error('Error al añadir software', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<div class="header-container">
|
||||
<h2 class="title" i18n="@@adminImagesTitle">Administrar perfiles software</h2>
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" i18n="@@adminImagesTitle" joyrideStep="titleStep" text="En esta pantalla podrás administrar los diferentes perfiles de software">Administrar perfiles software</h2>
|
||||
<div class="calendar-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addSoftware()">Añadir perfil software</button>
|
||||
<button mat-flat-button color="primary" (click)="addSoftware()" joyrideStep="addStep" text="Crea nuevos perfiles de software.">Añadir perfil software</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
<div class="search-container">
|
||||
<div class="search-container" joyrideStep="filterStep" text="Utiliza los filtros para buscar entre los perfiles de software existentes." >
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label i18n="@@searchLabel">Buscar nombre de perfil</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['description']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
|
@ -13,7 +16,7 @@
|
|||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="Aquí se listarán los perfiles existentes y sus detalles.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image" >
|
||||
|
@ -29,7 +32,7 @@
|
|||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<ng-container matColumnDef="actions" joyrideStep="actionsStep" text="Utiliza los botones dedicados para realizar diferentes acciones sobre los perfiles.">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let calendar" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="editSoftware(calendar)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
|
|
|
@ -5,10 +5,10 @@ import {MatDialog} from "@angular/material/dialog";
|
|||
import {HttpClient} from "@angular/common/http";
|
||||
import {DataService} from "../software/data.service";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {CreateSoftwareComponent} from "../software/create-software/create-software.component";
|
||||
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import {PageEvent} from "@angular/material/paginator";
|
||||
import {CreateSoftwareProfileComponent} from "./create-software-profile/create-software-profile.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-software-profile',
|
||||
|
@ -58,7 +58,8 @@ export class SoftwareProfileComponent {
|
|||
public dialog: MatDialog,
|
||||
private http: HttpClient,
|
||||
private dataService: DataService,
|
||||
private toastService: ToastrService
|
||||
private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -90,7 +91,7 @@ export class SoftwareProfileComponent {
|
|||
editSoftware(softwareProfile: any): void {
|
||||
const dialogRef = this.dialog.open(CreateSoftwareProfileComponent, {
|
||||
width: '600px',
|
||||
data: softwareProfile['@id']
|
||||
data: softwareProfile
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
|
@ -129,4 +130,12 @@ export class SoftwareProfileComponent {
|
|||
this.length = event.length;
|
||||
this.search();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: ['titleStep','addStep', 'filterStep', 'tableStep', 'actionsStep'],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<div class="header-container">
|
||||
<h2 class="title" i18n="@@adminImagesTitle">Administrar Software</h2>
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" i18n="@@adminImagesTitle" joyrideStep="titleStep" text="Administra el software deisponible desde este componente.">Administrar Software</h2>
|
||||
<div class="calendar-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addSoftware()">Añadir software</button>
|
||||
<button mat-flat-button color="primary" (click)="addSoftware()" joyrideStep="addSoftwareStep" text="Utiliza este botón par añadir software nuevo.">Añadir software</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
<div class="search-container">
|
||||
<div class="search-container" joyrideStep="searchStep" text="Utiliza los filtros para buscar entre el software listado.">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label i18n="@@searchLabel">Buscar nombre de software</mat-label>
|
||||
<input matInput name="searchBar" placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
|
@ -21,7 +24,7 @@
|
|||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyride="tableStep" text="Aquí se mostrará todo el software disponible y sus características.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image" >
|
||||
|
|
|
@ -5,10 +5,10 @@ import {MatDialog} from "@angular/material/dialog";
|
|||
import {HttpClient} from "@angular/common/http";
|
||||
import {DataService} from "./data.service";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {CreateCalendarComponent} from "../calendar/create-calendar/create-calendar.component";
|
||||
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import {PageEvent} from "@angular/material/paginator";
|
||||
import {CreateSoftwareComponent} from "./create-software/create-software.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
selector: 'app-software',
|
||||
|
@ -63,7 +63,8 @@ export class SoftwareComponent {
|
|||
public dialog: MatDialog,
|
||||
private http: HttpClient,
|
||||
private dataService: DataService,
|
||||
private toastService: ToastrService
|
||||
private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -134,4 +135,18 @@ export class SoftwareComponent {
|
|||
this.length = event.length;
|
||||
this.search();
|
||||
}
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'addSoftwareStep',
|
||||
'searchStep',
|
||||
'tableStep',
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,6 @@ button[mat-flat-button] {
|
|||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.navbar-tittle{
|
||||
.navbar-title{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,35 @@
|
|||
<mat-toolbar>
|
||||
<span class="navbar-title" routerLink="/dashboard" i18n="@@webConsoleTitle">Opengnsys webconsole</span>
|
||||
<span class="navbar-title" i18n="@@webConsoleTitle" matTooltip="Consola web de administración de Opengnsys" matTooltipShowDelay="1000">
|
||||
Opengnsys webconsole
|
||||
</span>
|
||||
|
||||
<button mat-icon-button (click)="onToggleSidebar()" matTooltip="Abrir o cerrar la barra lateral" matTooltipShowDelay="1000">
|
||||
<mat-icon class="navbar-icon">menu</mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="navbar-button-row">
|
||||
<button class="admin-button" *ngIf="isSuperAdmin" mat-button [matMenuTriggerFor]="menu" i18n="@@admin">Administración</button>
|
||||
<button class="user-button" mat-button *ngIf="!isSuperAdmin" (click)="editUser()" i18n="@@editUser">Editar usuario</button>
|
||||
<button class="admin-button" *ngIf="isSuperAdmin" mat-button [matMenuTriggerFor]="menu" i18n="@@admin"
|
||||
matTooltip="Gestión de usuarios y roles de la aplicación" matTooltipShowDelay="1000">
|
||||
Administración
|
||||
</button>
|
||||
|
||||
<button class="user-button" mat-button *ngIf="!isSuperAdmin" (click)="editUser()" i18n="@@editUser"
|
||||
matTooltip="Editar tu información de usuario" matTooltipShowDelay="1000">
|
||||
Editar usuario
|
||||
</button>
|
||||
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item routerLink="/users" i18n="@@usersMenuItem">Usuarios</button>
|
||||
<button mat-menu-item routerLink="/user-groups" i18n="@@rolesMenuItem">Roles</button>
|
||||
<button mat-menu-item routerLink="/users" i18n="@@usersMenuItem" matTooltip="Ver y gestionar todos los usuarios" matTooltipShowDelay="1000">
|
||||
Usuarios
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/user-groups" i18n="@@rolesMenuItem" matTooltip="Gestionar roles de usuario" matTooltipShowDelay="1000">
|
||||
Roles
|
||||
</button>
|
||||
</mat-menu>
|
||||
<button mat-flat-button color="warn" routerLink="/auth/login" i18n="@@logout">Salir</button>
|
||||
|
||||
<button mat-flat-button color="warn" routerLink="/auth/login" i18n="@@logout"
|
||||
matTooltip="Cerrar sesión y salir de la aplicación" matTooltipShowDelay="1000">
|
||||
Salir
|
||||
</button>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<app-header class="header" (toggleSidebar)="toggleSidebar()"></app-header>
|
||||
|
||||
<mat-drawer-container class="container" autosize>
|
||||
<mat-drawer class="sidebar" mode="side" opened>
|
||||
<app-sidebar [isVisible]="isSidebarVisible"></app-sidebar>
|
||||
<mat-drawer class="sidebar" mode="side" [opened]="isSidebarVisible">
|
||||
<app-sidebar></app-sidebar>
|
||||
</mat-drawer>
|
||||
|
||||
<mat-drawer-content class="content">
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-main-layout',
|
||||
templateUrl: './main-layout.component.html',
|
||||
styleUrl: './main-layout.component.css',
|
||||
styleUrls: ['./main-layout.component.css'],
|
||||
})
|
||||
export class MainLayoutComponent {
|
||||
isSidebarVisible: boolean = false;
|
||||
isSidebarVisible: boolean = true;
|
||||
|
||||
toggleSidebar() {
|
||||
this.isSidebarVisible = !this.isSidebarVisible;
|
||||
|
|
|
@ -1,57 +1,55 @@
|
|||
<mat-nav-list>
|
||||
<mat-list-item disabled>
|
||||
<span class="user-logged">
|
||||
<span class="user-logged" matTooltip="Bienvenido, {{username}}" matTooltipShowDelay="1000">
|
||||
<span i18n="@@welcomeUser">Bienvenido {{username}}</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<mat-list-item routerLink="/groups">
|
||||
<mat-list-item routerLink="/groups" matTooltip="Gestionar grupos de usuarios" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">apartment</mat-icon>
|
||||
<span i18n="@@groups">Grupos</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item>
|
||||
<span class="entry" (click)="toggleCommandSub()">
|
||||
<mat-icon class="icon">playlist_play </mat-icon>
|
||||
<mat-list-item (click)="toggleCommandSub()" matTooltip="Ver y ejecutar acciones predefinidas" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">playlist_play</mat-icon>
|
||||
<span i18n="@@actions">Acciones</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
<!-- Submenu items for commands -->
|
||||
<mat-nav-list *ngIf="showCommandSub" style="padding-left: 20px;">
|
||||
<mat-list-item routerLink="/commands">
|
||||
<mat-list-item routerLink="/commands" matTooltip="Lista de comandos disponibles" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">chevron_right</mat-icon>
|
||||
<span i18n="@@gallery">Comandos</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/commands-groups">
|
||||
<mat-list-item routerLink="/commands-groups" matTooltip="Gestionar grupos de comandos" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">chevron_right</mat-icon>
|
||||
<span i18n="@@gallery">Grupos</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/commands-task">
|
||||
<mat-list-item routerLink="/commands-task" matTooltip="Ver y gestionar tareas programadas" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">chevron_right</mat-icon>
|
||||
<span i18n="@@gallery">Tareas</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/commands-logs">
|
||||
<mat-list-item routerLink="/commands-logs" matTooltip="Revisar trazas de ejecución de comandos" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">notifications</mat-icon>
|
||||
<span i18n="@@gallery">Trazas</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
<!-- End commands sub -->
|
||||
|
||||
<!-- OGDHCP -->
|
||||
<mat-list-item (click)="toggleOgDhcpSub()">
|
||||
<mat-list-item (click)="toggleOgDhcpSub()" matTooltip="Configurar y administrar DHCP" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">settings_ethernet</mat-icon>
|
||||
<span i18n="@@images">DHCP</span>
|
||||
|
@ -60,23 +58,21 @@
|
|||
|
||||
<!-- Submenu items ogdhcp -->
|
||||
<mat-nav-list *ngIf="showOgDhcpSub" style="padding-left: 20px;">
|
||||
<mat-list-item routerLink="/ogdhcp-status">
|
||||
<mat-list-item routerLink="/ogdhcp-status" matTooltip="Estado actual del servicio DHCP" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">analytics</mat-icon>
|
||||
<span i18n="@@gallery">Estado</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/subnets">
|
||||
<mat-list-item routerLink="/subnets" matTooltip="Gestionar y crea subredes" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">lan</mat-icon>
|
||||
<span i18n="@@gallery">Subredes</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
<!-- Submenu items ogdhcp -->
|
||||
|
||||
|
||||
<mat-list-item (click)="toggleOgBootSub()">
|
||||
<mat-list-item (click)="toggleOgBootSub()" matTooltip="Configurar y administrar opciones de arranque" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">desktop_windows</mat-icon>
|
||||
<span i18n="@@images">Boot</span>
|
||||
|
@ -85,41 +81,40 @@
|
|||
|
||||
<!-- Submenu items for ogBoot -->
|
||||
<mat-nav-list *ngIf="showOgBootSub" style="padding-left: 20px;">
|
||||
<mat-list-item routerLink="/ogboot-status">
|
||||
<mat-list-item routerLink="/ogboot-status" matTooltip="Estado del servicio de arranque" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">analytics</mat-icon>
|
||||
<span i18n="@@gallery">Estado</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/pxe-images">
|
||||
<mat-list-item routerLink="/pxe-images" matTooltip="Ver imágenes disponibles para arranque PXE" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">album</mat-icon>
|
||||
<span i18n="@@gallery">ogLive</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/pxe">
|
||||
<mat-list-item routerLink="/pxe" matTooltip="Gestionar plantillas de arranque PXE" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">assignment</mat-icon>
|
||||
<span i18n="@@upload">Plantillas PXE</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/pxe-boot-file">
|
||||
<mat-list-item routerLink="/pxe-boot-file" matTooltip="Configurar archivos de arranque PXE" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">save</mat-icon>
|
||||
<span i18n="@@upload">Arranque PXE</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
<!-- End ogBoot sub -->
|
||||
|
||||
<mat-list-item routerLink="/calendars">
|
||||
<mat-list-item routerLink="/calendars" matTooltip="Gestionar calendarios de remotePC" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">calendar_month</mat-icon>
|
||||
<span i18n="@@calendars">Calendarios</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item (click)="toggleSoftwareSub()">
|
||||
<mat-list-item (click)="toggleSoftwareSub()" matTooltip="Administrar configuraciones de software" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">terminal</mat-icon>
|
||||
<span i18n="@@images">Software</span>
|
||||
|
@ -128,19 +123,19 @@
|
|||
|
||||
<!-- Submenu items ogdhcp -->
|
||||
<mat-nav-list *ngIf="showSoftwareSub" style="padding-left: 20px;">
|
||||
<mat-list-item routerLink="/software">
|
||||
<mat-list-item routerLink="/software" matTooltip="Ver lista de software disponible" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">list</mat-icon>
|
||||
<span i18n="@@gallery">Listado </span>
|
||||
<span i18n="@@gallery">Listado</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/software-profiles">
|
||||
<mat-list-item routerLink="/software-profiles" matTooltip="Gestionar perfiles de software" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">folder_shared</mat-icon>
|
||||
<span i18n="@@gallery">Perfiles</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/operative-systems">
|
||||
<mat-list-item routerLink="/operative-systems" matTooltip="Configurar sistemas operativos" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">terminal</mat-icon>
|
||||
<span i18n="@@gallery">S. Operativos</span>
|
||||
|
@ -148,33 +143,31 @@
|
|||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
|
||||
<mat-list-item routerLink="/images">
|
||||
<mat-list-item routerLink="/images" matTooltip="Gestionar imágenes del sistema" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">photo</mat-icon>
|
||||
<span i18n="@@repositories">imágenes</span>
|
||||
<span i18n="@@images">Imágenes</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item class="disabled">
|
||||
<mat-list-item routerLink="/repositories" matTooltip="Ver y gestionar repositorios de software" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">warehouse</mat-icon>
|
||||
<span i18n="@@repositories">Repositorios</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item class="disabled">
|
||||
<mat-list-item class="disabled" matTooltip="Gestión de menús (opción deshabilitada)" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">list</mat-icon>
|
||||
<span i18n="@@menus">Menús</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item class="disabled">
|
||||
<mat-list-item class="disabled" matTooltip="Función de búsqueda (opción deshabilitada)" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">search</mat-icon>
|
||||
<span i18n="@@search">Buscar</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
|
||||
</mat-nav-list>
|
||||
|
|
|
@ -1,45 +1,45 @@
|
|||
<?xml version="1.0"?>
|
||||
<testsuite name="Chrome 123.0.0.0 (Linux x86_64)" package="" timestamp="2024-10-24T11:02:26" id="0" hostname="Ubnt" tests="31" errors="0" failures="0" time="1.13">
|
||||
<testsuite name="Chrome Headless 130.0.0.0 (Linux x86_64)" package="" timestamp="2024-10-29T10:46:18" id="0" hostname="alvaro-Latitude-3420" tests="31" errors="0" failures="0" time="0.938">
|
||||
<properties>
|
||||
<property name="browser.fullName" value="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"/>
|
||||
<property name="browser.fullName" value="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/130.0.0.0 Safari/537.36"/>
|
||||
</properties>
|
||||
<testcase name="CreateOperativeSystemComponent should create" time="0.106" classname="CreateOperativeSystemComponent"/>
|
||||
<testcase name="PxeBootFilesComponent should create" time="0.083" classname="PxeBootFilesComponent"/>
|
||||
<testcase name="OgDhcpSubnetsComponent should create" time="0.068" classname="OgDhcpSubnetsComponent"/>
|
||||
<testcase name="PXEimagesComponent should create" time="0.092" classname="PXEimagesComponent"/>
|
||||
<testcase name="AddClientsToPxeComponent should create" time="0.024" classname="AddClientsToPxeComponent"/>
|
||||
<testcase name="RolesComponent should create" time="0.024" classname="RolesComponent"/>
|
||||
<testcase name="SoftwareProfileComponent should create" time="0.05" classname="SoftwareProfileComponent"/>
|
||||
<testcase name="CreateCommandComponent should create" time="0.026" classname="CreateCommandComponent"/>
|
||||
<testcase name="DashboardComponent should create the component" time="0.008" classname="DashboardComponent"/>
|
||||
<testcase name="CalendarComponent should create" time="0.049" classname="CalendarComponent"/>
|
||||
<testcase name="UsersComponent should create" time="0.023" classname="UsersComponent"/>
|
||||
<testcase name="SoftwareComponent should create" time="0.053" classname="SoftwareComponent"/>
|
||||
<testcase name="OgdhcpComponent should create" time="0.006" classname="OgdhcpComponent"/>
|
||||
<testcase name="StatusComponent should create" time="0.032" classname="StatusComponent"/>
|
||||
<testcase name="OperativeSystemComponent should create" time="0.04" classname="OperativeSystemComponent"/>
|
||||
<testcase name="LoginComponent should create" time="0.036" classname="LoginComponent"/>
|
||||
<testcase name="CommandsTaskComponent should create" time="0.041" classname="CommandsTaskComponent"/>
|
||||
<testcase name="CommandsComponent should create" time="0.037" classname="CommandsComponent"/>
|
||||
<testcase name="CreateSoftwareProfileComponent should create" time="0.067" classname="CreateSoftwareProfileComponent"/>
|
||||
<testcase name="OgbootStatusComponent should create the component" time="0.017" classname="OgbootStatusComponent"/>
|
||||
<testcase name="ClientsComponent should create" time="0.032" classname="ClientsComponent"/>
|
||||
<testcase name="AdminComponent el primer botón debería tener el texto "Usuarios"" time="0.022" classname="AdminComponent"/>
|
||||
<testcase name="AdminComponent el segundo botón debería tener el routerLink correcto" time="0.017" classname="AdminComponent"/>
|
||||
<testcase name="AdminComponent el primer botón debería tener el routerLink correcto" time="0.016" classname="AdminComponent"/>
|
||||
<testcase name="AdminComponent debería crear el componente" time="0.008" classname="AdminComponent"/>
|
||||
<testcase name="AdminComponent el segundo botón debería tener el texto "Roles"" time="0.011" classname="AdminComponent"/>
|
||||
<testcase name="AdminComponent debería contener dos botones" time="0.009" classname="AdminComponent"/>
|
||||
<testcase name="AppComponent should create the app" time="0.009" classname="AppComponent"/>
|
||||
<testcase name="CreateSoftwareComponent should create" time="0.058" classname="CreateSoftwareComponent"/>
|
||||
<testcase name="PxeComponent should create the component" time="0.05" classname="PxeComponent"/>
|
||||
<testcase name="CreateSoftwareComponent should create" time="0.127" classname="CreateSoftwareComponent"/>
|
||||
<testcase name="UsersComponent should create" time="0.039" classname="UsersComponent"/>
|
||||
<testcase name="CommandsComponent should create" time="0.054" classname="CommandsComponent"/>
|
||||
<testcase name="AppComponent should create the app" time="0.004" classname="AppComponent"/>
|
||||
<testcase name="AddClientsToPxeComponent should create" time="0.031" classname="AddClientsToPxeComponent"/>
|
||||
<testcase name="CreateCommandComponent should create" time="0.029" classname="CreateCommandComponent"/>
|
||||
<testcase name="OgbootStatusComponent should create the component" time="0.03" classname="OgbootStatusComponent"/>
|
||||
<testcase name="ServerInfoDialogComponent should create" time="0.016" classname="ServerInfoDialogComponent"/>
|
||||
<testcase name="AdminComponent debería crear el componente" time="0.015" classname="AdminComponent"/>
|
||||
<testcase name="AdminComponent el segundo botón debería tener el texto "Roles"" time="0.008" classname="AdminComponent"/>
|
||||
<testcase name="AdminComponent el primer botón debería tener el texto "Usuarios"" time="0.006" classname="AdminComponent"/>
|
||||
<testcase name="AdminComponent debería contener dos botones" time="0.005" classname="AdminComponent"/>
|
||||
<testcase name="AdminComponent el segundo botón debería tener el routerLink correcto" time="0.007" classname="AdminComponent"/>
|
||||
<testcase name="AdminComponent el primer botón debería tener el routerLink correcto" time="0.005" classname="AdminComponent"/>
|
||||
<testcase name="DashboardComponent should create the component" time="0.004" classname="DashboardComponent"/>
|
||||
<testcase name="PXEimagesComponent should create" time="0.071" classname="PXEimagesComponent"/>
|
||||
<testcase name="CalendarComponent should create" time="0.033" classname="CalendarComponent"/>
|
||||
<testcase name="OgDhcpSubnetsComponent should create" time="0.054" classname="OgDhcpSubnetsComponent"/>
|
||||
<testcase name="OgdhcpComponent should create" time="0.005" classname="OgdhcpComponent"/>
|
||||
<testcase name="StatusComponent should create" time="0.017" classname="StatusComponent"/>
|
||||
<testcase name="CreateOperativeSystemComponent should create" time="0.015" classname="CreateOperativeSystemComponent"/>
|
||||
<testcase name="LoginComponent should create" time="0.027" classname="LoginComponent"/>
|
||||
<testcase name="SoftwareComponent should create" time="0.04" classname="SoftwareComponent"/>
|
||||
<testcase name="OperativeSystemComponent should create" time="0.032" classname="OperativeSystemComponent"/>
|
||||
<testcase name="CreateSoftwareProfileComponent should create" time="0.105" classname="CreateSoftwareProfileComponent"/>
|
||||
<testcase name="SoftwareProfileComponent should create" time="0.031" classname="SoftwareProfileComponent"/>
|
||||
<testcase name="ClientsComponent should create" time="0.014" classname="ClientsComponent"/>
|
||||
<testcase name="PxeComponent should create the component" time="0.034" classname="PxeComponent"/>
|
||||
<testcase name="PxeBootFilesComponent should create" time="0.036" classname="PxeBootFilesComponent"/>
|
||||
<testcase name="RolesComponent should create" time="0.015" classname="RolesComponent"/>
|
||||
<testcase name="CommandsTaskComponent should create" time="0.029" classname="CommandsTaskComponent"/>
|
||||
<system-out>
|
||||
<![CDATA[Chrome 123.0.0.0 (Linux x86_64) ERROR: 'Error fetching images', HttpErrorResponse{headers: HttpHeaders{normalizedNames: Map{}, lazyUpdate: null, headers: Map{}}, status: 0, statusText: 'Unknown Error', url: 'https://127.0.0.1:8443/og-lives?page=1&itemsPerPage=1000', ok: false, name: 'HttpErrorResponse', message: 'Http failure response for https://127.0.0.1:8443/og-lives?page=1&itemsPerPage=1000: 0 Unknown Error', error: ProgressEvent{isTrusted: true}}
|
||||
,Chrome 123.0.0.0 (Linux x86_64) ERROR: 'Error fetching og lives', HttpErrorResponse{headers: HttpHeaders{normalizedNames: Map{}, lazyUpdate: null, headers: Map{}}, status: 0, statusText: 'Unknown Error', url: 'https://127.0.0.1:8443/og-lives?page=1&itemsPerPage=1000', ok: false, name: 'HttpErrorResponse', message: 'Http failure response for https://127.0.0.1:8443/og-lives?page=1&itemsPerPage=1000: 0 Unknown Error', error: ProgressEvent{isTrusted: true}}
|
||||
,Chrome 123.0.0.0 (Linux x86_64) LOG: 'Selected subnet UUID:', Object{}
|
||||
,Chrome 123.0.0.0 (Linux x86_64) LOG: Object{}
|
||||
,Chrome 123.0.0.0 (Linux x86_64) LOG: Object{}
|
||||
<![CDATA[Chrome Headless 130.0.0.0 (Linux x86_64) LOG: 'Selected subnet UUID:', Object{}
|
||||
,Chrome Headless 130.0.0.0 (Linux x86_64) ERROR: 'Error fetching images', HttpErrorResponse{headers: HttpHeaders{normalizedNames: Map{}, lazyUpdate: null, headers: Map{}}, status: 0, statusText: 'Unknown Error', url: 'https://127.0.0.1:8443/og-lives?page=1&itemsPerPage=1000', ok: false, name: 'HttpErrorResponse', message: 'Http failure response for https://127.0.0.1:8443/og-lives?page=1&itemsPerPage=1000: 0 Unknown Error', error: ProgressEvent{isTrusted: true}}
|
||||
,Chrome Headless 130.0.0.0 (Linux x86_64) ERROR: 'Error fetching og lives', HttpErrorResponse{headers: HttpHeaders{normalizedNames: Map{}, lazyUpdate: null, headers: Map{}}, status: 0, statusText: 'Unknown Error', url: 'https://127.0.0.1:8443/og-lives?page=1&itemsPerPage=1000', ok: false, name: 'HttpErrorResponse', message: 'Http failure response for https://127.0.0.1:8443/og-lives?page=1&itemsPerPage=1000: 0 Unknown Error', error: ProgressEvent{isTrusted: true}}
|
||||
,Chrome Headless 130.0.0.0 (Linux x86_64) LOG: Object{}
|
||||
,Chrome Headless 130.0.0.0 (Linux x86_64) LOG: Object{}
|
||||
|
||||
]]>
|
||||
</system-out>
|
||||
|
|
Loading…
Reference in New Issue