commit
c290c94675
|
@ -1,4 +1,9 @@
|
|||
# Changelog
|
||||
## [0.14.1] - 2025-06-09
|
||||
### Fixed
|
||||
- Se han corregido los errores en produccion que hacia que no salieran mensajes desde la API correctamente.
|
||||
|
||||
---
|
||||
## [0.14.0] - 2025-06-02
|
||||
### Added
|
||||
- Se ha añadido funcionalidad de usuarios/roles para separar las vistas segun los permisos.
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
RunScriptAssistantComponent
|
||||
} from "./components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component";
|
||||
import { roleGuard } from './guards/role.guard';
|
||||
import { LogoutGuard } from './guards/logout.guard';
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
|
||||
{
|
||||
|
@ -76,7 +77,7 @@ const routes: Routes = [
|
|||
path: 'auth',
|
||||
component: AuthLayoutComponent,
|
||||
children: [
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'login', component: LoginComponent, canActivate: [LogoutGuard] },
|
||||
],
|
||||
},
|
||||
{ path: '**', component: PageNotFoundComponent },
|
||||
|
|
|
@ -153,6 +153,7 @@ import { ClientTaskLogsComponent } from './components/task-logs/client-task-logs
|
|||
import { BootSoPartitionComponent } from './components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component';
|
||||
import { RemoveCacheImageComponent } from './components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component';
|
||||
import { ChangeParentComponent } from './components/groups/shared/change-parent/change-parent.component';
|
||||
import { SoftwareProfilePartitionComponent } from './components/commands/main-commands/execute-command/software-profile-partition/software-profile-partition.component';
|
||||
|
||||
export function HttpLoaderFactory(http: HttpClient) {
|
||||
return new TranslateHttpLoader(http, './locale/', '.json');
|
||||
|
@ -263,7 +264,8 @@ registerLocaleData(localeEs, 'es-ES');
|
|||
ClientTaskLogsComponent,
|
||||
BootSoPartitionComponent,
|
||||
RemoveCacheImageComponent,
|
||||
ChangeParentComponent
|
||||
ChangeParentComponent,
|
||||
SoftwareProfilePartitionComponent
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [BrowserModule,
|
||||
|
|
|
@ -7,6 +7,7 @@ import { BootSoPartitionComponent } from "./boot-so-partition/boot-so-partition.
|
|||
import { MatDialog } from "@angular/material/dialog";
|
||||
import { RemoveCacheImageComponent } from "./remove-cache-image/remove-cache-image.component";
|
||||
import { AuthService } from '@services/auth.service';
|
||||
import {SoftwareProfilePartitionComponent} from "./software-profile-partition/software-profile-partition.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-execute-command',
|
||||
|
@ -33,7 +34,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
{ translationKey: 'executeCommands.deployImage', slug: 'deploy-image', disabled: false },
|
||||
{ translationKey: 'executeCommands.deleteImageCache', slug: 'remove-cache-image', disabled: false },
|
||||
{ translationKey: 'executeCommands.partition', slug: 'partition', disabled: false },
|
||||
{ translationKey: 'executeCommands.softwareInventory', slug: 'software-inventory', disabled: true },
|
||||
{ translationKey: 'executeCommands.softwareInventory', slug: 'software-inventory', disabled: false },
|
||||
{ translationKey: 'executeCommands.hardwareInventory', slug: 'hardware-inventory', disabled: true },
|
||||
{ translationKey: 'executeCommands.runScript', slug: 'run-script', disabled: false },
|
||||
];
|
||||
|
@ -111,10 +112,10 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
if (states[0] === 'off' || states[0] === 'disconnected') {
|
||||
command.disabled = command.slug !== 'power-on';
|
||||
} else {
|
||||
command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'remove-cache-image', 'partition', 'run-script'].includes(command.slug);
|
||||
command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'remove-cache-image', 'partition', 'run-script', 'software-inventory'].includes(command.slug);
|
||||
}
|
||||
} else {
|
||||
if (command.slug === 'create-image') {
|
||||
if (command.slug === 'create-image'|| command.slug === 'software-inventory') {
|
||||
command.disabled = multipleClients;
|
||||
} else if (
|
||||
['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'remove-cache-image', 'run-script'].includes(command.slug)
|
||||
|
@ -164,6 +165,14 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
if (action === 'remove-cache-image') {
|
||||
this.removeImageCache();
|
||||
}
|
||||
|
||||
if (action === 'hardware-inventory') {
|
||||
this.hardwareInventory();
|
||||
}
|
||||
|
||||
if (action === 'software-inventory') {
|
||||
this.softwareInventory();
|
||||
}
|
||||
}
|
||||
|
||||
rebootClient(): void {
|
||||
|
@ -174,7 +183,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
this.toastService.success('Cliente actualizado correctamente');
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error de conexión con el cliente');
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error de conexión con el cliente');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -227,6 +236,55 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
hardwareInventory(): void {
|
||||
if (this.clientData.length === 0) {
|
||||
this.toastService.error('No hay clientes seleccionados');
|
||||
return;
|
||||
}
|
||||
|
||||
const clientId = this.clientData[0].uuid;
|
||||
|
||||
this.http.post(`${this.baseUrl}/clients/server/${clientId}/hardware-inventory`, {
|
||||
clients: this.clientData.map((client: any) => client['@id'])
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Inventario de hardware actualizado correctamente');
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error de conexión con el cliente');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
softwareInventory(): void {
|
||||
if (this.clientData.length === 0) {
|
||||
this.toastService.error('No hay clientes seleccionados');
|
||||
return;
|
||||
}
|
||||
|
||||
const clientDataToSend = {
|
||||
clientId: this.clientData[0].uuid,
|
||||
name: this.clientData[0].name,
|
||||
mac: this.clientData[0].mac,
|
||||
status: this.clientData[0].status,
|
||||
partitions: this.clientData[0].partitions,
|
||||
firmwareType: this.clientData[0].firmwareType,
|
||||
ip: this.clientData[0].ip
|
||||
}
|
||||
|
||||
const clientId = this.clientData[0].uuid;
|
||||
|
||||
const dialogRef = this.dialog.open(SoftwareProfilePartitionComponent, {
|
||||
width: '70vw',
|
||||
height: 'auto',
|
||||
data: { client: clientDataToSend }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
powerOnClient(): void {
|
||||
this.http.post(`${this.baseUrl}/image-repositories/wol`, {
|
||||
clients: this.clientData.map((client: any) => client['@id'])
|
||||
|
@ -235,7 +293,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
this.toastService.success('Petición de encendido enviada correctamente');
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error de conexión con el cliente');
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error de conexión con el cliente');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -248,7 +306,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
this.toastService.success('Petición de apagado enviada correctamente');
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error de conexión con el cliente');
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error de conexión con el cliente');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
.dialog-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.action-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1em;
|
||||
padding: 1.5em;
|
||||
}
|
||||
|
||||
.select-container {
|
||||
margin-top: 20px;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.clients-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.client-card {
|
||||
background: #ffffff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, transform 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f0f0;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.client-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-description {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.selected-client {
|
||||
background-color: #a0c2e5 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.client-name {
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 150px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client-ip {
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
@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,55 @@
|
|||
<h2 mat-dialog-title> Seleccionar partición para inventariar</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
|
||||
<mat-divider *ngIf="!loading" style="margin-top: 20px;"></mat-divider>
|
||||
|
||||
<div *ngIf="!loading" class="partition-table-container">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: start">Seleccionar imagen</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-radio-group
|
||||
[(ngModel)]="selectedPartition"
|
||||
>
|
||||
<mat-radio-button [value]="row">
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef !== 'size' && column.columnDef !== 'operativeSystem'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="column.columnDef === 'size'">
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<span> {{ image.size }} MB</span>
|
||||
<span style="font-size: 0.75rem; color: gray;">{{ image.size / 1024 }} GB</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="column.columnDef === 'operativeSystem'">
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<span> {{ image.operativeSystem?.name }} </span>
|
||||
<span style="font-size: 0.75rem; color: gray;">{{ image.image?.name}} </span>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<div mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="close()">Cancelar</button>
|
||||
<button class="submit-button" (click)="execute()" [disabled]="!selectedPartition">Ejecutar</button>
|
||||
</div>
|
|
@ -0,0 +1,71 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SoftwareProfilePartitionComponent } from './software-profile-partition.component';
|
||||
import {FormBuilder, ReactiveFormsModule} from "@angular/forms";
|
||||
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
|
||||
import {MatFormFieldModule} from "@angular/material/form-field";
|
||||
import {MatInputModule} from "@angular/material/input";
|
||||
import {MatCheckboxModule} from "@angular/material/checkbox";
|
||||
import {MatButtonModule} from "@angular/material/button";
|
||||
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
|
||||
import {ToastrModule, ToastrService} from "ngx-toastr";
|
||||
import {TranslateModule} from "@ngx-translate/core";
|
||||
import {DataService} from "../../data.service";
|
||||
import {provideHttpClient} from "@angular/common/http";
|
||||
import {provideHttpClientTesting} from "@angular/common/http/testing";
|
||||
import {ConfigService} from "@services/config.service";
|
||||
import {MatDividerModule} from "@angular/material/divider";
|
||||
import {MatTableModule} from "@angular/material/table";
|
||||
|
||||
describe('SoftwareProfilePartitionComponent', () => {
|
||||
let component: SoftwareProfilePartitionComponent;
|
||||
let fixture: ComponentFixture<SoftwareProfilePartitionComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockConfigService = {
|
||||
apiUrl: 'http://mock-api-url',
|
||||
mercureUrl: 'http://mock-mercure-url'
|
||||
};
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [SoftwareProfilePartitionComponent],
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatCheckboxModule,
|
||||
MatButtonModule,
|
||||
BrowserAnimationsModule,
|
||||
MatDividerModule,
|
||||
MatTableModule,
|
||||
ToastrModule.forRoot(),
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
FormBuilder,
|
||||
ToastrService,
|
||||
DataService,
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
{
|
||||
provide: MatDialogRef,
|
||||
useValue: {}
|
||||
},
|
||||
{
|
||||
provide: MAT_DIALOG_DATA,
|
||||
useValue: {}
|
||||
},
|
||||
{ provide: ConfigService, useValue: mockConfigService }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SoftwareProfilePartitionComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,107 @@
|
|||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import {MatTableDataSource} from "@angular/material/table";
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {ConfigService} from "@services/config.service";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: 'app-software-profile-partition',
|
||||
templateUrl: './software-profile-partition.component.html',
|
||||
styleUrl: './software-profile-partition.component.css'
|
||||
})
|
||||
export class SoftwareProfilePartitionComponent implements OnInit{
|
||||
baseUrl: string;
|
||||
selectedPartition: any = null;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
clientId: string | null = null;
|
||||
selectedClients: any[] = [];
|
||||
selectedModelClient: any = null;
|
||||
filteredPartitions: any[] = [];
|
||||
allSelected: boolean = false;
|
||||
clientData: any[] = [];
|
||||
loading: boolean = false;
|
||||
columns = [
|
||||
{
|
||||
columnDef: 'diskNumber',
|
||||
header: 'Disco',
|
||||
cell: (partition: any) => partition.diskNumber
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionNumber',
|
||||
header: 'Particion',
|
||||
cell: (partition: any) => partition.partitionNumber
|
||||
},
|
||||
{
|
||||
columnDef: 'size',
|
||||
header: 'Tamaño',
|
||||
cell: (partition: any) => `${partition.size} MB`
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionCode',
|
||||
header: 'Tipo de partición',
|
||||
cell: (partition: any) => partition.partitionCode
|
||||
},
|
||||
{
|
||||
columnDef: 'filesystem',
|
||||
header: 'Sistema de ficheros',
|
||||
cell: (partition: any) => partition.filesystem
|
||||
},
|
||||
{
|
||||
columnDef: 'operativeSystem',
|
||||
header: 'SO',
|
||||
cell: (partition: any) => partition.operativeSystem?.name
|
||||
}
|
||||
];
|
||||
|
||||
displayedColumns = ['select', ...this.columns.map(column => column.columnDef)];
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: { client: any },
|
||||
private dialogRef: MatDialogRef<SoftwareProfilePartitionComponent>,
|
||||
private configService: ConfigService,
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
this.clientId = this.data.client?.clientId
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadPartitions();
|
||||
}
|
||||
|
||||
loadPartitions() {
|
||||
const url = `${this.baseUrl}/clients/${this.data.client?.clientId}`;
|
||||
this.http.get(url).subscribe(
|
||||
(response: any) => {
|
||||
if (response.partitions) {
|
||||
this.dataSource.data = response.partitions;
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar los datos del cliente:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
this.loading = true;
|
||||
|
||||
this.http.post(`${this.baseUrl}/clients/server/${this.data.client.clientId}/software-inventory`, {
|
||||
partition: this.selectedPartition['@id'],
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Inventario de software actualizado correctamente');
|
||||
this.dialogRef.close(response);
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al actualizar el inventario de software');
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -8,17 +8,9 @@
|
|||
<mat-tab-group (selectedTabChange)="onTabChange($event)">
|
||||
<mat-tab label="OgBoot">
|
||||
<div *ngIf="!loading && !errorOgBoot" class="content-container">
|
||||
<app-status-tab
|
||||
[loading]="loading"
|
||||
[diskUsage]="ogBootDiskUsage"
|
||||
[servicesStatus]="ogBootServicesStatus"
|
||||
[installedOgLives]="installedOgLives"
|
||||
[diskUsageChartData]="ogBootDiskUsageChartData"
|
||||
[view]="view"
|
||||
[colorScheme]="colorScheme"
|
||||
[isDoughnut]="isDoughnut"
|
||||
[showLabels]="showLabels"
|
||||
[isDhcp]="isDhcp"
|
||||
<app-status-tab [loading]="loading" [diskUsage]="ogBootDiskUsage" [servicesStatus]="ogBootServicesStatus"
|
||||
[installedOgLives]="installedOgLives" [diskUsageChartData]="ogBootDiskUsageChartData" [view]="view"
|
||||
[colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="isDhcp"
|
||||
[isRepository]="false">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
|
@ -31,18 +23,9 @@
|
|||
|
||||
<mat-tab label="Dhcp">
|
||||
<div *ngIf="!loading && !errorDhcp" class="content-container">
|
||||
<app-status-tab
|
||||
[loading]="loading"
|
||||
[diskUsage]="dhcpDiskUsage"
|
||||
[servicesStatus]="dhcpServicesStatus"
|
||||
[subnets]="subnets"
|
||||
[diskUsageChartData]="dhcpDiskUsageChartData"
|
||||
[view]="view"
|
||||
[colorScheme]="colorScheme"
|
||||
[isDoughnut]="isDoughnut"
|
||||
[showLabels]="showLabels"
|
||||
[isDhcp]="isDhcp"
|
||||
[isRepository]="false">
|
||||
<app-status-tab [loading]="loading" [diskUsage]="dhcpDiskUsage" [servicesStatus]="dhcpServicesStatus"
|
||||
[subnets]="subnets" [diskUsageChartData]="dhcpDiskUsageChartData" [view]="view" [colorScheme]="colorScheme"
|
||||
[isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="isDhcp" [isRepository]="false">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
<mat-card *ngIf="!loading && errorDhcp" class="error-card">
|
||||
|
@ -52,31 +35,22 @@
|
|||
</mat-card>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Repositorios">
|
||||
<mat-tab label="{{ 'repositoryLabel' | translate }}">
|
||||
<mat-tab-group>
|
||||
<mat-tab *ngFor="let repository of repositories" [label]="repository.name">
|
||||
<div *ngIf="!loading && !errorRepositories[repository.uuid] && repositoryStatuses[repository.uuid]">
|
||||
<app-status-tab
|
||||
[loading]="loading"
|
||||
[diskUsage]="repositoryStatuses[repository.uuid].disk"
|
||||
<app-status-tab [loading]="loading" [diskUsage]="repositoryStatuses[repository.uuid].disk"
|
||||
[servicesStatus]="repositoryStatuses[repository.uuid].services"
|
||||
[processesStatus]="repositoryStatuses[repository.uuid].processes"
|
||||
[ramUsage]="repositoryStatuses[repository.uuid].ram"
|
||||
[cpuUsage]="repositoryStatuses[repository.uuid].cpu"
|
||||
[ramUsage]="repositoryStatuses[repository.uuid].ram" [cpuUsage]="repositoryStatuses[repository.uuid].cpu"
|
||||
[diskUsageChartData]="[
|
||||
{ name: 'Usado', value: repositoryStatuses[repository.uuid].disk.used },
|
||||
{ name: 'Disponible', value: repositoryStatuses[repository.uuid].disk.available }
|
||||
]"
|
||||
[ramUsageChartData]="[
|
||||
]" [ramUsageChartData]="[
|
||||
{ name: 'Usado', value: repositoryStatuses[repository.uuid].ram.used },
|
||||
{ name: 'Disponible', value: repositoryStatuses[repository.uuid].ram.available }
|
||||
]"
|
||||
[view]="view"
|
||||
[colorScheme]="colorScheme"
|
||||
[isDoughnut]="isDoughnut"
|
||||
[showLabels]="showLabels"
|
||||
[isDhcp]="false"
|
||||
[isRepository]="true">
|
||||
]" [view]="view" [colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels"
|
||||
[isDhcp]="false" [isRepository]="true">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
<mat-card *ngIf="!loading && errorRepositories[repository.uuid]" class="error-card">
|
||||
|
|
|
@ -69,7 +69,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
},
|
||||
error: (error) => {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.error('Error al sincronizar las subredes DHCP');
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al sincronizar las subredes');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
this.toastService.success('Sincronización de las plantillas Pxe completada');
|
||||
}, error => {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.error('Error al sincronizar las plantillas Pxe');
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al sincronizar las plantillas Pxe');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
this.toastService.success('Sincronización con los ogLives completada');
|
||||
}, error => {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.error('Error al sincronizar imágenes ogLive');
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al sincronizar las imagenes ogLive');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
clearTimeout(timeoutId);
|
||||
},
|
||||
error: error => {
|
||||
console.log(error);
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al cargar el estado de ogBoot');
|
||||
this.loading = false;
|
||||
this[errorState] = true;
|
||||
clearTimeout(timeoutId);
|
||||
|
@ -217,7 +217,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
callback(false);
|
||||
},
|
||||
error => {
|
||||
console.error(`Error fetching status for repository ${repositoryUuid}`, error);
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al cargar el estado del repositorio');
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
}
|
||||
|
|
|
@ -196,3 +196,24 @@ mat-option .unit-name {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.instructions-box {
|
||||
margin-top: 15px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ccc;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.instructions-textarea textarea {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.instructions-card {
|
||||
background-color: #f5f5f5;
|
||||
box-shadow: none !important;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -15,6 +15,12 @@
|
|||
(click)="save()">Ejecutar</button>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<button class="action-button" (click)="generateOgInstructions()">
|
||||
Generar instrucciones
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button mat-stroked-button color="accent"
|
||||
[disabled]="!isFormValid()"
|
||||
|
@ -118,6 +124,20 @@
|
|||
</div>
|
||||
|
||||
<div class="partition-table-container">
|
||||
<div *ngIf="showInstructions" class="instructions-box">
|
||||
<mat-card class="instructions-card">
|
||||
<mat-card-title>
|
||||
Instrucciones generadas
|
||||
<button mat-icon-button (click)="showInstructions = false" style="float: right;">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-card-title>
|
||||
<mat-card-content>
|
||||
<pre>{{ ogInstructions }}</pre>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="filteredPartitions" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: start">Seleccionar partición</th>
|
||||
|
|
|
@ -37,6 +37,9 @@ export class DeployImageComponent implements OnInit{
|
|||
loading: boolean = false;
|
||||
allSelected = true;
|
||||
runScriptContext: any = null;
|
||||
ogInstructions: string = '';
|
||||
deployImage: boolean = true;
|
||||
showInstructions: boolean = false;
|
||||
|
||||
protected p2pModeOptions = [
|
||||
{ name: 'Leecher', value: 'leecher' },
|
||||
|
@ -395,4 +398,39 @@ export class DeployImageComponent implements OnInit{
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
generateOgInstructions() {
|
||||
let script = '';
|
||||
const disk = this.selectedPartition?.disk;
|
||||
const partition = this.selectedPartition?.partition;
|
||||
|
||||
let ip = this.selectedImage?.repository?.ip || 'REPO';
|
||||
let imgName = this.selectedImage?.canonicalName || '';
|
||||
let target = ` ${disk} ${partition}`;
|
||||
let log = `ogEcho log session "[0] $MSG_SCRIPTS_TASK_START `;
|
||||
|
||||
if (this.deployImage) {
|
||||
script = 'deployImage ';
|
||||
} else {
|
||||
script = 'updateCache ';
|
||||
imgName += '.img';
|
||||
target = '';
|
||||
}
|
||||
|
||||
script += `${ip} /${imgName}${target} ${this.selectedMethod}`;
|
||||
log += `${script}"\n`;
|
||||
script = log + script;
|
||||
|
||||
let params = '';
|
||||
if (['udpcast', 'uftp', 'udpcast-direct'].includes(<string>this.selectedMethod)) {
|
||||
params = `${this.mcastPort}:${this.mcastMode}:${this.mcastIp}:${this.mcastSpeed}M:${this.mcastMaxClients}:${this.mcastMaxTime}`;
|
||||
} else if (this.selectedMethod === 'p2p') {
|
||||
params = `${this.p2pMode}:${this.p2pTime}`;
|
||||
}
|
||||
|
||||
script += ` ${params}`;
|
||||
|
||||
this.ogInstructions = script;
|
||||
this.showInstructions = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,5 +280,12 @@ button.remove-btn:hover {
|
|||
white-space: pre;
|
||||
}
|
||||
|
||||
.instructions-card {
|
||||
background-color: #f5f5f5;
|
||||
box-shadow: none !important;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -92,11 +92,18 @@
|
|||
</div>
|
||||
|
||||
<div class="partition-assistant" *ngIf="selectedDisk">
|
||||
|
||||
<div *ngIf="generatedInstructions" class="instructions-box">
|
||||
<mat-form-field class="instructions-textarea" appearance="fill" style="width: 100%;">
|
||||
<textarea matInput rows="10" readonly [value]="generatedInstructions"></textarea>
|
||||
</mat-form-field>
|
||||
<div *ngIf="showInstructions" class="instructions-box">
|
||||
<mat-card class="instructions-card">
|
||||
<mat-card-title>
|
||||
Instrucciones generadas
|
||||
<button mat-icon-button (click)="showInstructions = false" style="float: right;">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-card-title>
|
||||
<mat-card-content>
|
||||
<pre>{{ generatedInstructions }}</pre>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
<div class="row-button">
|
||||
|
@ -107,7 +114,6 @@
|
|||
<mat-chip color="info" *ngIf="partitionCode">
|
||||
Tabla de particiones: {{ partitionCode }}
|
||||
</mat-chip>
|
||||
|
||||
</div>
|
||||
|
||||
<mat-divider style="padding: 10px;"></mat-divider>
|
||||
|
|
|
@ -44,6 +44,7 @@ export class PartitionAssistantComponent implements OnInit{
|
|||
clientData: any = [];
|
||||
loading: boolean = false;
|
||||
runScriptContext: any = null;
|
||||
showInstructions = false;
|
||||
|
||||
view: [number, number] = [400, 300];
|
||||
showLegend = true;
|
||||
|
@ -509,5 +510,6 @@ export class PartitionAssistantComponent implements OnInit{
|
|||
instructions += `ogExecAndLog command session ogListPartitions ${diskNumber}\n`;
|
||||
|
||||
this.generatedInstructions = instructions;
|
||||
this.showInstructions = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -265,8 +265,7 @@
|
|||
<div *ngFor="let client of arrayClients" class="client-item">
|
||||
<div class="client-card">
|
||||
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(client)"
|
||||
[checked]="selection.isSelected(client)"
|
||||
[disabled]="client.status === 'busy' || client.status === 'off' || client.status === 'disconnected'">
|
||||
[checked]="selection.isSelected(client)">
|
||||
</mat-checkbox>
|
||||
<img style="margin-top: 0.5em;" [src]="'assets/images/computer_' + client.status + '.svg'"
|
||||
alt="Client Icon" class="client-image" />
|
||||
|
@ -341,8 +340,7 @@
|
|||
</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(row)"
|
||||
[checked]="selection.isSelected(row)"
|
||||
[disabled]="row.status === 'busy' || row.status === 'off' || row.status === 'disconnected'">
|
||||
[checked]="selection.isSelected(row)">
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
|
|
@ -587,11 +587,11 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
onRoomMap(room: TreeNode | null): void {
|
||||
if (!room || !room['@id']) return;
|
||||
this.subscriptions.add(
|
||||
this.http.get<{ clients: Client[] }>(`${this.baseUrl}${room['@id']}`).subscribe(
|
||||
(response) => {
|
||||
this.http.get<{ clients: Client[] }>(`${this.baseUrl}/clients?organizationalUnit.id=${room.id}`).subscribe(
|
||||
(response: any) => {
|
||||
this.dialog.open(ClassroomViewDialogComponent, {
|
||||
width: '90vw',
|
||||
data: { clients: response.clients },
|
||||
data: { clients: response['hydra:member'] },
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
|
@ -750,8 +750,8 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
this.syncingClientId = null;
|
||||
this.refreshData(parentNodeId)
|
||||
},
|
||||
() => {
|
||||
this.toastr.error('Error de conexión con el cliente');
|
||||
(error) => {
|
||||
this.toastr.error(error.error['hydra:description'] || 'Error al actualizar el cliente');
|
||||
this.syncStatus = false;
|
||||
this.syncingClientId = null;
|
||||
this.refreshData(parentNodeId)
|
||||
|
|
|
@ -72,7 +72,6 @@ export class ClassroomViewComponent implements OnInit, OnChanges {
|
|||
}
|
||||
|
||||
handleClientClick(client: any): void {
|
||||
console.log('Client clicked:', client);
|
||||
this.dialog.open(ClientViewComponent, { data: { client }, width: '800px', height: '700px' });
|
||||
}
|
||||
|
||||
|
@ -108,4 +107,4 @@ export class ClassroomViewComponent implements OnInit, OnChanges {
|
|||
} else
|
||||
this.toastService.success('Cliente actualizado!', 'Éxito');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
.create-client-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 80vh;
|
||||
padding: 16px 16px 0px 16px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="create-client-container">
|
||||
<h1 mat-dialog-title i18n="@@add-client-dialog-title">Añadir multiples clientes</h1>
|
||||
<mat-dialog-content class="create-client-container">
|
||||
<h1 mat-dialog-title>{{ 'newMultipleClientButton' | translate }}</h1>
|
||||
<div class="inputs-container">
|
||||
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
||||
|
||||
|
@ -57,10 +57,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||
<button class="submit-button" [disabled]="!organizationalUnit" (click)="onSubmit()">{{ 'saveButton' | translate
|
||||
}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||
<button class="submit-button" [disabled]="!organizationalUnit" (click)="onSubmit()">{{ 'saveButton' | translate
|
||||
}}</button>
|
||||
</mat-dialog-actions>
|
|
@ -11,10 +11,6 @@ h1 {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
padding: 15px 50px 15px 50px;
|
||||
}
|
||||
|
||||
mat-option .unit-name {
|
||||
display: block;
|
||||
}
|
||||
|
@ -33,7 +29,12 @@ mat-option .unit-path {
|
|||
}
|
||||
|
||||
.create-client-container {
|
||||
position: relative;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1em 4em 2em 4em;
|
||||
}
|
||||
|
||||
.grid-form {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="create-client-container">
|
||||
<mat-dialog-content class="create-client-container">
|
||||
<h1 mat-dialog-title>{{ dialogTitle | translate }}</h1>
|
||||
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
||||
<div [ngClass]="{'loading': loading}">
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
<form *ngIf="clientForm && !loading" [formGroup]="clientForm" class="client-form grid-form">
|
||||
<mat-form-field class="form-field">
|
||||
|
@ -102,16 +102,15 @@
|
|||
<mat-error>{{ 'menuError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-checkbox formControlName="maintenance">
|
||||
{{ 'maintenance' | translate }}
|
||||
</mat-checkbox>
|
||||
<mat-checkbox formControlName="maintenance">
|
||||
{{ 'maintenance' | translate }}
|
||||
</mat-checkbox>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||
<button class="submit-button" [disabled]="!clientForm.valid" (click)="onSubmit()">
|
||||
{{ isEditMode ? 'Guardar' : 'Crear' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||
<button class="submit-button" [disabled]="!clientForm.valid" (click)="onSubmit()">
|
||||
{{ isEditMode ? 'Guardar' : 'Crear' }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
|
@ -0,0 +1,25 @@
|
|||
mat-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(100px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.client-types,
|
||||
.other-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: large;
|
||||
font-weight: 550;
|
||||
padding-left: 17px;
|
||||
}
|
||||
|
||||
mat-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
border-radius: 8px;
|
||||
}
|
|
@ -1,32 +1,76 @@
|
|||
<mat-list>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>apartment</mat-icon>
|
||||
<div matListItemTitle>{{ 'orgUnitTitle' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>meeting_room</mat-icon>
|
||||
<div matListItemTitle>{{ 'classroomGroupsTitle' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>school</mat-icon>
|
||||
<div matListItemTitle>{{ 'classroomTitle' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>lan</mat-icon>
|
||||
<div matListItemTitle>{{ 'clientGroupsTitle' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>computer</mat-icon>
|
||||
<div matListItemTitle>{{ 'clientTitle' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<div class="client-types">
|
||||
<p class="section-title">Clientes</p>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item>
|
||||
<img matListItemIcon src="assets/images/computer_off.svg">
|
||||
<div matListItemTitle>{{ 'clientOff' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<img matListItemIcon src="assets/images/computer_busy.svg">
|
||||
<div matListItemTitle>{{ 'clientBusy' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<img matListItemIcon src="assets/images/computer_disconnected.svg">
|
||||
<div matListItemTitle>{{ 'clientDisconnected' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<img matListItemIcon src="assets/images/computer_initializing.svg">
|
||||
<div matListItemTitle>{{ 'clientInitializing' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<img matListItemIcon src="assets/images/computer_linux-session.svg">
|
||||
<div matListItemTitle>{{ 'clientLinuxSession' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<img matListItemIcon src="assets/images/computer_linux.svg">
|
||||
<div matListItemTitle>{{ 'clientLinux' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<img matListItemIcon src="assets/images/computer_macos.svg">
|
||||
<div matListItemTitle>{{ 'clientMacOS' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<img matListItemIcon src="assets/images/computer_og-live.svg">
|
||||
<div matListItemTitle>{{ 'clientOgLive' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<img matListItemIcon src="assets/images/computer_windows-session.svg">
|
||||
<div matListItemTitle>{{ 'clientWindowsSession' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<img matListItemIcon src="assets/images/computer_windows.svg">
|
||||
<div matListItemTitle>{{ 'clientWindows' | translate }}</div>
|
||||
</mat-list-item>
|
||||
</div>
|
||||
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon style="color: green;">school</mat-icon>
|
||||
<div matListItemTitle>{{ 'remoteAccess' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon style="color: rgb(209, 5, 5);">school</mat-icon>
|
||||
<div matListItemTitle>{{ 'noRemoteAccess' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<div class="other-items">
|
||||
<p class="section-title">Entidades</p>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>apartment</mat-icon>
|
||||
<div matListItemTitle>{{ 'orgUnitTitle' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>meeting_room</mat-icon>
|
||||
<div matListItemTitle>{{ 'classroomGroupsTitle' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>school</mat-icon>
|
||||
<div matListItemTitle>{{ 'classroomTitle' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon style="color: green;">school</mat-icon>
|
||||
<div matListItemTitle>{{ 'remoteAccess' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon style="color: rgb(209, 5, 5);">school</mat-icon>
|
||||
<div matListItemTitle>{{ 'noRemoteAccess' | translate }}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>lan</mat-icon>
|
||||
<div matListItemTitle>{{ 'clientGroupsTitle' | translate }}</div>
|
||||
</mat-list-item>
|
||||
</div>
|
||||
|
||||
</mat-list>
|
|
@ -7,7 +7,12 @@ h1 {
|
|||
}
|
||||
|
||||
.create-ou-container {
|
||||
position: relative;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1em 4em 2em 4em;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
|
@ -18,25 +23,18 @@ h1 {
|
|||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
padding: 0px 40px 15px 50px;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
.action-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1em;
|
||||
padding: 1em;
|
||||
margin-right: 1em;
|
||||
padding: 1.5em;
|
||||
}
|
||||
|
||||
.grid-form {
|
||||
|
@ -66,4 +64,4 @@ h1 {
|
|||
align-items: center;
|
||||
grid-column: span 2;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<div class="create-ou-container">
|
||||
<mat-dialog-content class="create-ou-container">
|
||||
<h1 mat-dialog-title>{{ isEditMode ? ('edit' | translate) : ('createButton' | translate) }} {{
|
||||
'labelOrganizationalUnit' | translate }}</h1>
|
||||
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
||||
<div [ngClass]="{'loading': loading}">
|
||||
<!-- Paso 1: General -->
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
<span *ngIf="!loading" class="step-title">{{ 'generalTabLabel' | translate }}</span>
|
||||
|
@ -185,10 +185,10 @@
|
|||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
<div class="mat-dialog-actions">
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSubmit()"
|
||||
[disabled]="!generalFormGroup.valid || !additionalInfoFormGroup.valid || !networkSettingsFormGroup.valid">{{
|
||||
isEditMode ? ('edit' | translate) : ('createButton' | translate) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSubmit()"
|
||||
[disabled]="!generalFormGroup.valid || !additionalInfoFormGroup.valid || !networkSettingsFormGroup.valid">{{
|
||||
isEditMode ? ('edit' | translate) : ('createButton' | translate) }}</button>
|
||||
</mat-dialog-actions>
|
|
@ -1,6 +1,6 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ManageOrganizationalUnitComponent } from './manage-organizational-unit.component';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
|
@ -35,6 +35,7 @@ describe('ManageOrganizationalUnitComponent', () => {
|
|||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatSlideToggleModule,
|
||||
MatDialogModule,
|
||||
MatCheckboxModule,
|
||||
TranslateModule.forRoot(),
|
||||
BrowserAnimationsModule,
|
||||
|
|
|
@ -60,7 +60,8 @@ export class OgbootStatusComponent implements OnInit {
|
|||
this.loading = false;
|
||||
|
||||
}, error => {
|
||||
this.toastService.error('Error al sincronizar con el el servicio de og-boot');
|
||||
console.log(error)
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -43,10 +43,12 @@ export class CreatePXEImageComponent implements OnInit {
|
|||
next: (response: any) => {
|
||||
this.loading = false;
|
||||
this.downloads = response.message;
|
||||
this.loading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error fetching downloads:', error);
|
||||
this.toastService.error('Error fetching iso files');
|
||||
this.loading = false;
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error fetching downloads');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</h2>
|
||||
</div>
|
||||
<div class="images-button-row">
|
||||
<button class="action-button" joyrideStep="viewInfoStep" [text]="'viewInfoStepText' | translate" (click)="openSubnetInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
|
||||
<button class="action-button" joyrideStep="viewInfoStep" [text]="'viewInfoStepText' | translate" (click)="openInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
|
||||
<button class="action-button" (click)="addImage()" joyrideStep="addImageStep"
|
||||
[text]="'addOgLiveButtonDescription' | translate">
|
||||
{{ 'addOgLiveButton' | translate }}
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { PXEimagesComponent } from './pxe-images.component';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { of } from 'rxjs';
|
||||
import { of } from 'rxjs';
|
||||
import { MatAccordion, MatExpansionPanel, MatExpansionPanelHeader, MatExpansionPanelTitle, MatExpansionPanelDescription } from '@angular/material/expansion';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import { LoadingComponent } from '../../../shared/loading/loading.component';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import {MatProgressSpinner, MatProgressSpinnerModule, MatSpinner} from "@angular/material/progress-spinner";
|
||||
|
||||
describe('PXEimagesComponent', () => {
|
||||
let component: PXEimagesComponent;
|
||||
|
@ -42,13 +43,14 @@ describe('PXEimagesComponent', () => {
|
|||
MatExpansionPanelDescription,
|
||||
MatIcon,
|
||||
MatDivider,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule,
|
||||
MatPaginatorModule,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule,
|
||||
MatPaginatorModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
MatInputModule,
|
||||
MatTableModule,
|
||||
MatTableModule,
|
||||
MatProgressSpinnerModule,
|
||||
TranslateModule.forRoot(),
|
||||
JoyrideModule.forRoot(),
|
||||
],
|
||||
|
|
|
@ -76,10 +76,8 @@ export class PXEimagesComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading = true;
|
||||
this.search();
|
||||
this.loadAlert();
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
addImage(): void {
|
||||
|
@ -204,7 +202,8 @@ export class PXEimagesComponent implements OnInit {
|
|||
return this.http.get<any>(`${this.apiUrl}/server/get-collection`);
|
||||
}
|
||||
|
||||
openSubnetInfoDialog() {
|
||||
openInfoDialog() {
|
||||
this.loading = true;
|
||||
this.loadAlert().subscribe(
|
||||
response => {
|
||||
this.alertMessage = response.message;
|
||||
|
@ -215,10 +214,12 @@ export class PXEimagesComponent implements OnInit {
|
|||
message: this.alertMessage
|
||||
}
|
||||
});
|
||||
this.loading = false
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
console.error('Error al cargar la información del alert', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import { LoadingComponent } from '../../../shared/loading/loading.component';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
|
||||
|
||||
describe('PxeComponent', () => {
|
||||
let component: PxeComponent;
|
||||
|
@ -45,7 +46,7 @@ describe('PxeComponent', () => {
|
|||
BrowserAnimationsModule,
|
||||
MatDialogModule,
|
||||
MatTableModule,
|
||||
MatAccordion, MatExpansionPanel, MatExpansionPanelHeader, MatExpansionPanelTitle,
|
||||
MatAccordion, MatExpansionPanel, MatExpansionPanelHeader, MatExpansionPanelTitle,
|
||||
MatExpansionPanelDescription,
|
||||
MatIcon,
|
||||
MatDivider,
|
||||
|
@ -55,6 +56,7 @@ describe('PxeComponent', () => {
|
|||
MatSelect,
|
||||
MatOption,
|
||||
MatPaginator,
|
||||
MatProgressSpinnerModule,
|
||||
ToastrModule.forRoot(),
|
||||
TranslateModule.forRoot(),
|
||||
JoyrideModule.forRoot(),
|
||||
|
@ -63,8 +65,8 @@ describe('PxeComponent', () => {
|
|||
DatePipe,
|
||||
DataService,
|
||||
{ provide: ConfigService, useValue: mockConfigService },
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting()
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting()
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
@ -138,4 +140,4 @@ describe('PxeComponent', () => {
|
|||
it('should have a defined selectedItem', () => {
|
||||
expect(component.selectedItem).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -77,10 +77,8 @@ export class PxeComponent implements OnInit{
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading = true;
|
||||
this.search();
|
||||
this.loadAlert()
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
search(): void {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
|
|
|
@ -183,6 +183,7 @@ export class OgDhcpSubnetsComponent implements OnInit {
|
|||
}
|
||||
|
||||
openSubnetInfoDialog() {
|
||||
this.loading = true;
|
||||
this.loadAlert().subscribe(
|
||||
response => {
|
||||
this.alertMessage = response.message;
|
||||
|
@ -193,10 +194,12 @@ export class OgDhcpSubnetsComponent implements OnInit {
|
|||
message: this.alertMessage
|
||||
}
|
||||
});
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
console.error('Error al cargar la información del alert', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -37,3 +37,8 @@ mat-dialog-actions {
|
|||
.selected-item button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<h2 mat-dialog-title>Convertir imagen en virtual </h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-spinner *ngIf="loading" class="loading-spinner"></mat-spinner>
|
||||
<mat-form-field *ngIf="!loading" appearance="fill" class="full-width">
|
||||
<mat-label>Extension</mat-label>
|
||||
<input matInput [(ngModel)]="extension" placeholder="Introduzca la extensión de la imagen a convertir."
|
||||
/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Component, Inject} from '@angular/core';
|
||||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
@ -10,9 +10,9 @@ import {ConfigService} from "@services/config.service";
|
|||
templateUrl: './convert-image-to-virtual.component.html',
|
||||
styleUrl: './convert-image-to-virtual.component.css'
|
||||
})
|
||||
export class ConvertImageToVirtualComponent {
|
||||
export class ConvertImageToVirtualComponent implements OnInit {
|
||||
baseUrl: string;
|
||||
loading: boolean = true;
|
||||
loading: boolean = false;
|
||||
extension: string = '';
|
||||
|
||||
constructor(
|
||||
|
@ -27,25 +27,27 @@ export class ConvertImageToVirtualComponent {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading = true;
|
||||
}
|
||||
|
||||
save() {
|
||||
this.loading = true;
|
||||
this.http.post<any>(`${this.baseUrl}${this.data.imageImageRepository['@id']}/convert-image-to-virtual`, {
|
||||
extension: this.extension
|
||||
}).subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Peticion de conversion de imagen enviada correctamente');
|
||||
this.dialogRef.close();
|
||||
this.dialogRef.close(true);
|
||||
this.loading = false;
|
||||
this.router.navigate(['/commands-logs']);
|
||||
},
|
||||
error: error => {
|
||||
this.loading = false;
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dialogRef.close();
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,3 +37,9 @@ mat-dialog-actions {
|
|||
.selected-item button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
<h2 mat-dialog-title>Convertir imagen virtual </h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<p >Repositorio destino: {{ data.name }}</p>
|
||||
<mat-spinner *ngIf="loading" class="loading-spinner"></mat-spinner>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<p *ngIf="!loading" >Repositorio destino: {{ data.name }}</p>
|
||||
|
||||
<mat-form-field *ngIf="!loading" appearance="fill" class="full-width">
|
||||
<mat-label>Imagen</mat-label>
|
||||
<input matInput [(ngModel)]="imageName" placeholder="Introduzca el nombre de la imagen a importar."
|
||||
/>
|
||||
<mat-hint>El nombre de la imagen tiene que ir con la extensión. </mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-form-field *ngIf="!loading" appearance="fill" class="full-width">
|
||||
<mat-label>Sistema de archivos</mat-label>
|
||||
<input matInput [(ngModel)]="filesystem" placeholder="Introduzca el sistema de archivos."/>
|
||||
</mat-form-field>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
|
@ -10,9 +10,9 @@ import { ConfigService } from "@services/config.service";
|
|||
templateUrl: './convert-image.component.html',
|
||||
styleUrl: './convert-image.component.css'
|
||||
})
|
||||
export class ConvertImageComponent {
|
||||
export class ConvertImageComponent implements OnInit{
|
||||
baseUrl: string;
|
||||
loading: boolean = true;
|
||||
loading: boolean = false;
|
||||
imageName: string = '';
|
||||
filesystem: string = '';
|
||||
|
||||
|
@ -28,27 +28,28 @@ export class ConvertImageComponent {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading = true;
|
||||
}
|
||||
|
||||
save() {
|
||||
console.log(this.data?.repositoryUuid)
|
||||
this.loading = true;
|
||||
this.http.post<any>(`${this.baseUrl}/image-repositories/${this.data?.repositoryUuid}/convert-image`, {
|
||||
name: this.imageName,
|
||||
filesystem: this.filesystem
|
||||
}).subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Peticion de conversion de imagen enviada correctamente');
|
||||
this.dialogRef.close();
|
||||
this.dialogRef.close(true);
|
||||
this.loading = false;
|
||||
this.router.navigate(['/commands-logs']);
|
||||
},
|
||||
error: error => {
|
||||
this.loading = false;
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dialogRef.close();
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ mat-dialog-actions {
|
|||
|
||||
.selected-item {
|
||||
display: flex;
|
||||
justify-content: space-between; /* Alinea texto a la izquierda y botón a la derecha */
|
||||
align-items: center; /* Centra verticalmente */
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
@ -37,3 +37,8 @@ mat-dialog-actions {
|
|||
.selected-item button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<h2 mat-dialog-title>Importar imagenes a {{ data.name }}</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-spinner *ngIf="loading" class="loading-spinner"></mat-spinner>
|
||||
<mat-form-field *ngIf="!loading" appearance="fill" class="full-width">
|
||||
<mat-label>Imagen</mat-label>
|
||||
<input matInput [(ngModel)]="imageName" placeholder="Introduzca el nombre de la imagen a importar."
|
||||
/>
|
||||
|
|
|
@ -12,7 +12,7 @@ import { ConfigService } from '@services/config.service';
|
|||
})
|
||||
export class ImportImageComponent implements OnInit {
|
||||
baseUrl: string;
|
||||
loading: boolean = true;
|
||||
loading: boolean = false;
|
||||
imageName: string = '';
|
||||
|
||||
constructor(
|
||||
|
@ -27,26 +27,28 @@ export class ImportImageComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading = true;
|
||||
|
||||
}
|
||||
|
||||
save() {
|
||||
console.log(this.data?.repositoryUuid)
|
||||
this.loading = true;
|
||||
this.http.post<any>(`${this.baseUrl}/image-repositories/${this.data?.repositoryUuid}/import-image`, {
|
||||
name: this.imageName
|
||||
}).subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Peticion de importacion de imagen enviada correctamente');
|
||||
this.dialogRef.close();
|
||||
this.dialogRef.close(true);
|
||||
this.loading = false;
|
||||
this.router.navigate(['/commands-logs']);
|
||||
},
|
||||
error: error => {
|
||||
this.loading = false;
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dialogRef.close();
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,3 +98,8 @@ table {
|
|||
gap: 1em;
|
||||
padding: 1.5em;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<mat-dialog-content>
|
||||
<div class="header-container">
|
||||
<div class="header-container-title">
|
||||
|
@ -43,7 +41,9 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="imagesTable"
|
||||
<mat-spinner *ngIf="loading" class="loading-spinner"></mat-spinner>
|
||||
|
||||
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="imagesTable"
|
||||
text="Esta tabla muestra las imágenes disponibles.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
|
@ -62,7 +62,7 @@
|
|||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'isGlobal'">
|
||||
<mat-chip>
|
||||
{{ image.isGlobal ? 'Sí' : 'No' }}
|
||||
{{ image.image?.isGlobal ? 'Sí' : 'No' }}
|
||||
</mat-chip>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -89,7 +89,6 @@ export class ShowMonoliticImagesComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.error()
|
||||
if (this.data) {
|
||||
this.loadData();
|
||||
}
|
||||
|
@ -105,6 +104,8 @@ export class ShowMonoliticImagesComponent implements OnInit {
|
|||
},
|
||||
error => {
|
||||
console.error('Error fetching image repositories', error);
|
||||
this.loading = false;
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al cargar las imágenes del repositorio');
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -192,6 +193,7 @@ export class ShowMonoliticImagesComponent implements OnInit {
|
|||
}
|
||||
|
||||
toggleAction(image: any, action:string): void {
|
||||
this.loading = true;
|
||||
switch (action) {
|
||||
case 'get-aux':
|
||||
this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/create-aux-files`, {}).subscribe({
|
||||
|
@ -353,6 +355,7 @@ export class ShowMonoliticImagesComponent implements OnInit {
|
|||
});
|
||||
break;
|
||||
case 'rename':
|
||||
this.loading = true;
|
||||
this.dialog.open(RenameImageComponent, {
|
||||
width: '600px',
|
||||
data: {
|
||||
|
@ -403,6 +406,7 @@ export class ShowMonoliticImagesComponent implements OnInit {
|
|||
}
|
||||
|
||||
openImageInfoDialog() {
|
||||
this.loading = true;
|
||||
this.loadAlert().subscribe(
|
||||
response => {
|
||||
this.alertMessage = response.output;
|
||||
|
@ -411,9 +415,12 @@ export class ShowMonoliticImagesComponent implements OnInit {
|
|||
width: '800px',
|
||||
data: this.alertMessage
|
||||
});
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
console.error('Error al cargar la información del alert', error);
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
|
||||
<div class="software-list">
|
||||
<mat-list>
|
||||
<mat-list-item *ngFor="let software of selectedSoftwares">
|
||||
<mat-list-item *ngFor="let software of softwareCollection">
|
||||
{{ software.name }}
|
||||
<button mat-icon-button (click)="removeSoftware(software)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
|
@ -64,4 +64,4 @@
|
|||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onCancel($event)">Cancelar</button>
|
||||
<button class="submit-button" (click)="onSubmit()" cdkFocusInitial>Guardar</button>
|
||||
</mat-dialog-actions>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
@ -74,7 +74,7 @@ export class CreateSoftwareProfileComponent implements OnInit {
|
|||
}
|
||||
|
||||
loadSoftware() {
|
||||
this.http.get<any>(`${this.baseUrl}/software?page=1&itemsPerPage=10`).subscribe(
|
||||
this.http.get<any>(`${this.baseUrl}/software?softwareProfileId=${this.data.id}&page=1&itemsPerPage=10`).subscribe(
|
||||
response => {
|
||||
this.softwareCollection = response['hydra:member'];
|
||||
},
|
||||
|
|
|
@ -39,12 +39,12 @@ export class SoftwareProfileComponent {
|
|||
{
|
||||
columnDef: 'description',
|
||||
header: 'Descripción',
|
||||
cell: (software: any) => `${software.description}`
|
||||
cell: (software: any) => software.description
|
||||
},
|
||||
{
|
||||
columnDef: 'operativeSystem',
|
||||
header: 'Sistema Operativo',
|
||||
cell: (software: any) => `${software.operativeSystem?.name}`
|
||||
cell: (software: any) => software.operativeSystem?.name
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
|
@ -61,7 +61,7 @@ export class SoftwareProfileComponent {
|
|||
private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService,
|
||||
private configService: ConfigService
|
||||
) {
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
this.apiUrl = `${this.baseUrl}/software-profiles`;
|
||||
}
|
||||
|
|
|
@ -39,17 +39,12 @@ export class SoftwareComponent {
|
|||
{
|
||||
columnDef: 'name',
|
||||
header: 'Nombre',
|
||||
cell: (software: any) => `${software.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'description',
|
||||
header: 'Descripción',
|
||||
cell: (software: any) => `${software.description}`
|
||||
cell: (software: any) => software.name
|
||||
},
|
||||
{
|
||||
columnDef: 'type',
|
||||
header: 'Tipo',
|
||||
cell: (software: any) => `${software.type}`
|
||||
cell: (software: any) => software.type
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
|
@ -108,15 +103,15 @@ export class SoftwareComponent {
|
|||
});
|
||||
}
|
||||
|
||||
deleteSoftware(calendar: any): void {
|
||||
deleteSoftware(software: any): void {
|
||||
const dialogRef = this.dialog.open(DeleteModalComponent, {
|
||||
width: '400px',
|
||||
data: { name: calendar.name }
|
||||
data: { name: software.name }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
const apiUrl = `${this.baseUrl}${calendar['@id']}`;
|
||||
const apiUrl = `${this.baseUrl}${software['@id']}`;
|
||||
|
||||
this.http.delete(apiUrl).subscribe({
|
||||
next: () => {
|
||||
|
|
|
@ -68,6 +68,23 @@
|
|||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="search-date">
|
||||
<mat-label>Desde</mat-label>
|
||||
<input matInput [matDatepicker]="fromPicker" [(ngModel)]="filters['startDate']"
|
||||
(dateChange)="onDateFilterChange()" [max]="today">
|
||||
<mat-datepicker-toggle matSuffix [for]="fromPicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #fromPicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="search-date">
|
||||
<mat-label>Hasta</mat-label>
|
||||
<input matInput [matDatepicker]="toPicker" [(ngModel)]="filters['endDate']" (dateChange)="onDateFilterChange()"
|
||||
[max]="today">
|
||||
<mat-datepicker-toggle matSuffix [for]="toPicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #toPicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
@ -180,4 +197,4 @@
|
|||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -36,6 +36,7 @@ export class TaskLogsComponent implements OnInit {
|
|||
mode: ProgressBarMode = 'buffer';
|
||||
progress = 0;
|
||||
bufferValue = 0;
|
||||
today = new Date();
|
||||
|
||||
filteredCommands2 = Object.keys(COMMAND_TYPES).map(key => ({
|
||||
name: key,
|
||||
|
@ -215,10 +216,20 @@ export class TaskLogsComponent implements OnInit {
|
|||
loadTraces(): void {
|
||||
this.loading = true;
|
||||
const url = `${this.baseUrl}/traces?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`;
|
||||
const params = { ...this.filters };
|
||||
const params: any = { ...this.filters };
|
||||
if (params['status'] === undefined) {
|
||||
delete params['status'];
|
||||
}
|
||||
|
||||
if (params['startDate']) {
|
||||
params['executedAt[after]'] = this.datePipe.transform(params['startDate'], 'yyyy-MM-dd');
|
||||
delete params['startDate'];
|
||||
}
|
||||
if (params['endDate']) {
|
||||
params['executedAt[before]'] = this.datePipe.transform(params['endDate'], 'yyyy-MM-dd');
|
||||
delete params['endDate'];
|
||||
}
|
||||
|
||||
this.http.get<any>(url, { params }).subscribe(
|
||||
(data) => {
|
||||
this.traces = data['hydra:member'];
|
||||
|
@ -288,6 +299,21 @@ export class TaskLogsComponent implements OnInit {
|
|||
}));
|
||||
}
|
||||
|
||||
onDateFilterChange(): void {
|
||||
const start = this.filters['startDate'];
|
||||
const end = this.filters['endDate'];
|
||||
|
||||
if (!start || !end) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (start && end && start > end) {
|
||||
this.toastService.warning('La fecha de inicio no puede ser mayor que la fecha de fin');
|
||||
return;
|
||||
}
|
||||
this.loadTraces();
|
||||
}
|
||||
|
||||
onPageChange(event: any): void {
|
||||
this.page = event.pageIndex;
|
||||
this.itemsPerPage = event.pageSize;
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate } from '@angular/router';
|
||||
import { AuthService } from '@services/auth.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LogoutGuard implements CanActivate {
|
||||
constructor(private auth: AuthService) {}
|
||||
|
||||
canActivate(): boolean {
|
||||
this.auth.logout({ redirect: false });
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@
|
|||
{{ 'Administration' | translate }}
|
||||
</button>
|
||||
|
||||
<button class="ordinary-button" (click)="editUser()"
|
||||
<button class="ordinary-button" (click)="editUser()" *ngIf="auth.userCategory !== 'super-admin'"
|
||||
matTooltip="Editar tu información de usuario" matTooltipShowDelay="1000">
|
||||
{{ 'changePassword' | translate }}
|
||||
</button>
|
||||
|
|
|
@ -106,7 +106,7 @@ export class AuthService {
|
|||
}
|
||||
|
||||
/** Logout: limpia tokens y redirige al login */
|
||||
logout(): void {
|
||||
logout(options: { redirect?: boolean } = { redirect: true }): void {
|
||||
localStorage.removeItem('loginToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('isSuperAdmin');
|
||||
|
@ -114,6 +114,8 @@ export class AuthService {
|
|||
localStorage.removeItem('groupsView');
|
||||
localStorage.removeItem('language');
|
||||
this.tokenPayload = null;
|
||||
this.router.navigate(['/auth/login']);
|
||||
if (options.redirect && this.router.url !== '/auth/login') {
|
||||
this.router.navigate(['/auth/login']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"apiUrl": "https://127.0.0.1:8443",
|
||||
"apiUrl": "https://192.168.68.62:8443",
|
||||
"mercureUrl": "http://localhost:3000/.well-known/mercure"
|
||||
}
|
||||
|
|
|
@ -533,5 +533,15 @@
|
|||
"tracesTitleStepText": "In this screen, you can see the execution traces of each client, with its id, command, real-time status, date and actions to be performed.",
|
||||
"filtersStepText": "Here you can see the different filters to apply to the table information.",
|
||||
"tracesProgressStepText": "Here you can see the execution status updated in real time.",
|
||||
"tracesInfoStepText": "Here you can consult detailed information about the specific trace."
|
||||
"tracesInfoStepText": "Here you can consult detailed information about the specific trace.",
|
||||
"clientOff": "Client off",
|
||||
"clientBusy": "Busy client",
|
||||
"clientDisconnected": "Disconnected client",
|
||||
"clientInitializing": "Initializing client",
|
||||
"clientLinuxSession": "Linux session client",
|
||||
"clientLinux": "Linux client",
|
||||
"clientMacOS": "MacOS client",
|
||||
"clientOgLive": "OGLive client",
|
||||
"clientWindowsSession": "Windows session client",
|
||||
"clientWindows": "Windows client"
|
||||
}
|
||||
|
|
|
@ -536,5 +536,15 @@
|
|||
"tracesTitleStepText": "En esta pantalla, puedes ver las trazas de ejecución de cada cliente, con su id, comando, estado en tiempo real, fecha y acciones a realizar.",
|
||||
"filtersStepText": "Aquí puedes ver los diferentes filtros que aplicar a la información de la tabla.",
|
||||
"tracesProgressStepText": "Aquí puedes ver el estado de ejecución actualizado en tiempo real.",
|
||||
"tracesInfoStepText": "Aquí puedes consultar información detallada de la traza en concreto."
|
||||
"tracesInfoStepText": "Aquí puedes consultar información detallada de la traza en concreto.",
|
||||
"clientOff": "Cliente apagado",
|
||||
"clientBusy": "Cliente ocupado",
|
||||
"clientDisconnected": "Cliente desconectado",
|
||||
"clientInitializing": "Cliente inicializando",
|
||||
"clientLinuxSession": "Cliente con sesión Linux",
|
||||
"clientLinux": "Cliente Linux",
|
||||
"clientMacOS": "Cliente MacOS",
|
||||
"clientOgLive": "Cliente OGLive",
|
||||
"clientWindowsSession": "Cliente con sesión Windows",
|
||||
"clientWindows": "Cliente Windows"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue