Updated remote PC and bug status classrom
commit
1978cf41e7
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -1,5 +1,28 @@
|
|||
# Changelog
|
||||
|
||||
## [1.5.4] - 2026-04-24
|
||||
### Improved
|
||||
- Modificado script de post instalación debian para añadir a config.json las nuevas variables.
|
||||
|
||||
## [1.5.3] - 2026-04-24
|
||||
### Improved
|
||||
- Corregidas traducciones
|
||||
|
||||
## [1.5.2] - 2026-04-24
|
||||
### Added
|
||||
- Añadido campo resolución en los ajustes de red de aulas y grupos para propagarlo a los equipos.
|
||||
- Rediseño del menú de configuracion de una Unidad Organizativa
|
||||
|
||||
## [1.5.1] - 2026-04-23
|
||||
### Improved
|
||||
- Creadas claves de traducción para idiomas, añadido menu para cambiar de idioma en la barra superior
|
||||
|
||||
## [1.5.0] - 2026-04-21
|
||||
### Added
|
||||
- Añadida gestión de múltiples ogBoot Servers
|
||||
- Si es un ogBoot existente, permite sincronizar su información sobre oglives y plantillas pxe.
|
||||
- En los Ajustes de red de equipos, aulas y grupos, ahora se debe seleccionar servidor ogBoot y luego oglive y plantilla dentro de dicho servidor.
|
||||
|
||||
## [1.4.0] - 2026-04-16
|
||||
### Added
|
||||
- Añadida gestión de múltiples Dhcps
|
||||
|
|
|
|||
|
|
@ -22,11 +22,19 @@ restore_config_if_modified() {
|
|||
|
||||
if [ -f "$backup" ]; then
|
||||
if ! cmp -s "$new" "$backup"; then
|
||||
echo ">>> Archivo modificado por el usuario detectado en $new"
|
||||
echo " - Guardando archivo nuevo como ${new}.new"
|
||||
mv -f "$new" "${new}.new"
|
||||
echo " - Restaurando archivo anterior desde backup"
|
||||
mv -f "$backup" "$new"
|
||||
if [ "$new" = "$CONFIG_FILE" ]; then
|
||||
echo ">>> Fusionando configuración de usuario en $new"
|
||||
# El operador '*' realiza un merge recursivo:
|
||||
# Preserva los valores del usuario (.[1]) y añade solo las claves nuevas del paquete (.[0])
|
||||
jq -s '.[0] * .[1]' "$new" "$backup" > "${new}.tmp" && mv "${new}.tmp" "$new"
|
||||
rm -f "$backup"
|
||||
else
|
||||
echo ">>> Archivo modificado por el usuario detectado en $new"
|
||||
echo " - Guardando archivo nuevo como ${new}.new"
|
||||
mv -f "$new" "${new}.new"
|
||||
echo " - Restaurando archivo anterior desde backup"
|
||||
mv -f "$backup" "$new"
|
||||
fi
|
||||
else
|
||||
echo ">>> El archivo $new no ha cambiado desde la última versión, eliminando backup"
|
||||
rm -f "$backup"
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import { EnvVarsComponent } from "./components/admin/env-vars/env-vars.component
|
|||
import { MenusComponent } from "./components/menus/menus.component";
|
||||
import { OgDhcpSubnetsComponent } from "./components/ogdhcp/og-dhcp-subnets.component";
|
||||
import { DhcpServersComponent } from './components/ogdhcp/dhcp-servers/dhcp-servers.component';
|
||||
import { BootServersComponent } from './components/ogboot/boot-servers/boot-servers.component';
|
||||
import {
|
||||
|
||||
RunScriptAssistantComponent
|
||||
|
|
@ -45,6 +46,7 @@ import { LogoutGuard } from './guards/logout.guard';
|
|||
import { HardwareComponent } from './components/hardware/hardware.component';
|
||||
import { HardwareProfileComponent } from './components/hardware-profile/hardware-profile.component';
|
||||
import { HardwareTypeComponent } from './components/hardware-type/hardware-type.component';
|
||||
import { BootServerDashboardComponent } from './components/ogboot/boot-server-dashboard/boot-server-dashboard.component';
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
|
||||
{
|
||||
|
|
@ -60,6 +62,8 @@ const routes: Routes = [
|
|||
{ path: 'ogboot-status', component: OgbootStatusComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'subnets', component: OgDhcpSubnetsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'dhcp-servers', component: DhcpServersComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'boot-servers', component: BootServersComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'boot-servers/:uuid', component: BootServerDashboardComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'commands', component: CommandsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'commands-groups', component: CommandsGroupsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
{ path: 'commands-task', component: CommandsTaskComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ export class AppComponent {
|
|||
this.translateService.use(savedLanguage);
|
||||
}
|
||||
ngOnInit() {
|
||||
sessionStorage.setItem('language', 'es');
|
||||
// Language is already handled in constructor
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ import { MatProgressSpinner } from "@angular/material/progress-spinner";
|
|||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatMenu, MatMenuItem, MatMenuTrigger } from "@angular/material/menu";
|
||||
import { MatAutocomplete, MatAutocompleteTrigger } from "@angular/material/autocomplete";
|
||||
import { MatChip, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule } from "@angular/material/chips";
|
||||
import { MatChipsModule } from "@angular/material/chips";
|
||||
import { ClientViewComponent } from './components/groups/shared/client-view/client-view.component';
|
||||
import { MatTab, MatTabGroup } from "@angular/material/tabs";
|
||||
import { MatTooltip } from "@angular/material/tooltip";
|
||||
import { MatTabsModule } from "@angular/material/tabs";
|
||||
import { MatTooltipModule } from "@angular/material/tooltip";
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
|
|
@ -193,10 +193,19 @@ import { HardwareCollectionModalComponent } from './components/hardware-profile/
|
|||
import { HardwareTypeComponent } from './components/hardware-type/hardware-type.component';
|
||||
import { ShowDetailsComponent } from './components/hardware-type/show-details/show-details.component';
|
||||
import { DiskPartitionSelectorComponent } from './components/groups/shared/disk-partition-selector/disk-partition-selector.component';
|
||||
import { BootServersComponent } from './components/ogboot/boot-servers/boot-servers.component';
|
||||
import { ManageBootServerComponent } from './components/ogboot/manage-boot-server/manage-boot-server.component';
|
||||
import { BootServerDashboardComponent } from './components/ogboot/boot-server-dashboard/boot-server-dashboard.component';
|
||||
import { OgcoreStatusComponent } from './components/global-status/ogcore-status/ogcore-status.component';
|
||||
import { GlobalOgbootStatusComponent } from './components/global-status/ogboot-status/ogboot-status.component';
|
||||
import { OgrepositoryStatusComponent } from './components/global-status/ogrepository-status/ogrepository-status.component';
|
||||
import { OgdhcpStatusComponent } from './components/global-status/ogdhcp-status/ogdhcp-status.component';
|
||||
export function HttpLoaderFactory(http: HttpClient) {
|
||||
return new TranslateHttpLoader(http, './locale/', '.json');
|
||||
}
|
||||
|
||||
import { PageNotFoundComponent } from './shared/page-not-found/page-not-found.component';
|
||||
|
||||
export function initializeApp(configService: ConfigService) {
|
||||
return () => configService.loadConfig();
|
||||
}
|
||||
|
|
@ -324,7 +333,15 @@ registerLocaleData(localeEs, 'es-ES');
|
|||
HardwareCollectionModalComponent,
|
||||
HardwareTypeComponent,
|
||||
ShowDetailsComponent,
|
||||
AssetManagerModalComponent
|
||||
AssetManagerModalComponent,
|
||||
BootServersComponent,
|
||||
ManageBootServerComponent,
|
||||
BootServerDashboardComponent,
|
||||
OgcoreStatusComponent,
|
||||
GlobalOgbootStatusComponent,
|
||||
OgrepositoryStatusComponent,
|
||||
OgdhcpStatusComponent,
|
||||
PageNotFoundComponent
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [BrowserModule,
|
||||
|
|
@ -349,7 +366,7 @@ registerLocaleData(localeEs, 'es-ES');
|
|||
MatProgressBarModule,
|
||||
MatStepperModule,
|
||||
DragDropModule,
|
||||
MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule, MatChip, MatProgressSpinner, MatTabGroup, MatTab, MatTooltip,
|
||||
MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipsModule, MatProgressSpinner, MatTabsModule, MatTooltipModule,
|
||||
MatExpansionModule,
|
||||
NgxChartsModule,
|
||||
MatDatepickerModule,
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
<div class="env-settings">
|
||||
<div class="header-container">
|
||||
<div class="header-container-title">
|
||||
<h2>Editar Variables de Entorno</h2>
|
||||
<h2>{{ 'editEnvVarsTitle' | translate }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-table [dataSource]="envVars" class="mat-elevation-z8">
|
||||
<!-- Nombre de la variable -->
|
||||
<ng-container matColumnDef="name">
|
||||
<mat-header-cell *matHeaderCellDef> Variable </mat-header-cell>
|
||||
<mat-header-cell *matHeaderCellDef> {{ 'labelVariable' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let variable">{{ variable.name }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Valor de la variable -->
|
||||
<ng-container matColumnDef="value">
|
||||
<mat-header-cell *matHeaderCellDef> Valor </mat-header-cell>
|
||||
<mat-header-cell *matHeaderCellDef> {{ 'labelValue' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let variable">
|
||||
<!-- Si es booleano, usamos checkbox -->
|
||||
<mat-checkbox *ngIf="isBoolean(variable.value)"
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
</mat-table>
|
||||
|
||||
<div class="actions">
|
||||
<button class="action-button" (click)="loadEnvVars()">Recargar</button>
|
||||
<button class="submit-button" (click)="saveEnvVars()">Guardar Cambios</button>
|
||||
<button class="action-button" (click)="loadEnvVars()">{{ 'buttonReload' | translate }}</button>
|
||||
<button class="submit-button" (click)="saveEnvVars()">{{ 'buttonSaveChanges' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component } from '@angular/core';
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
|
|
@ -9,6 +11,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrl: './env-vars.component.css'
|
||||
})
|
||||
export class EnvVarsComponent {
|
||||
public translate = inject(TranslateService);
|
||||
envVars: { name: string; value: string }[] = [];
|
||||
displayedColumns: string[] = ['name', 'value'];
|
||||
private apiUrl: string;
|
||||
|
|
@ -31,7 +34,7 @@ export class EnvVarsComponent {
|
|||
this.envVars = Object.entries(response.vars).map(([name, value]) => ({ name, value }));
|
||||
},
|
||||
error: (err) => {
|
||||
this.toastService.error('No se pudieron cargar las variables de entorno.');
|
||||
this.toastService.error(this.translate.instant('couldNotBeLoaded'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -48,11 +51,11 @@ export class EnvVarsComponent {
|
|||
|
||||
this.http.post(this.apiUrl, { vars }).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Variables de entorno guardadas correctamente.');
|
||||
this.toastService.success(this.translate.instant('environmentVariablesSavedCorrectly'));
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error al guardar las variables de entorno:', err);
|
||||
this.toastService.error('No se pudieron cargar las variables de entorno.');
|
||||
console.error(this.translate.instant('errorSavingVariables'), err);
|
||||
this.toastService.error(this.translate.instant('couldNotBeLoaded'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<p><mat-checkbox formControlName="orgAdmin">{{ 'checkboxOrgAdmin' | translate }}</mat-checkbox></p>
|
||||
<p><mat-checkbox formControlName="orgOperator">{{ 'checkboxOrgOperator' | translate }}</mat-checkbox></p>
|
||||
<p><mat-checkbox formControlName="orgMinimal">{{ 'checkboxOrgMinimal' | translate }}</mat-checkbox></p>
|
||||
<p><mat-checkbox formControlName="userRole">{{ 'checkboxUserRole' | translate }}</mat-checkbox></p>
|
||||
<p><mat-checkbox formControlName="userRole">{{ 'labelUser' | translate }}</mat-checkbox></p>
|
||||
</section>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
|
@ -12,6 +14,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrls: ['./add-role-modal.component.css']
|
||||
})
|
||||
export class AddRoleModalComponent {
|
||||
public translate = inject(TranslateService);
|
||||
roleForm: FormGroup<any>;
|
||||
roleId: string | null = null;
|
||||
baseUrl: string;
|
||||
|
|
@ -56,7 +59,7 @@ export class AddRoleModalComponent {
|
|||
this.roleId = response['@id'];
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error fetching remote calendar:', err);
|
||||
console.error(this.translate.instant('errorFetchingRemoteCalendar'), err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -85,23 +88,23 @@ export class AddRoleModalComponent {
|
|||
if (this.roleId) {
|
||||
this.http.put(`${this.baseUrl}${this.roleId}`, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Rol actualizado correctamente');
|
||||
this.toastService.success(this.translate.instant('roleUpdatedSuccessfully'));
|
||||
this.dialogRef.close();
|
||||
},
|
||||
(error) => {
|
||||
this.toastService.error('Error al editar el rol:', error);
|
||||
console.error('Error al editar el rol', error);
|
||||
this.toastService.error(this.translate.instant('errorEditingRole1'), error);
|
||||
console.error(this.translate.instant('errorEditingRole'), error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.http.post(`${this.baseUrl}/user-groups`, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Rol añadido correctamente');
|
||||
this.toastService.success(this.translate.instant('roleAddedSuccessfully'));
|
||||
this.dialogRef.close();
|
||||
},
|
||||
(error) => {
|
||||
this.toastService.error('Error al añadir rol:', error);
|
||||
console.error('Error al añadir rol', error);
|
||||
this.toastService.error(this.translate.instant('errorAddingRole1'), error);
|
||||
console.error(this.translate.instant('errorAddingRole'), error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import {HttpClient, HttpParams} from '@angular/common/http';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
|
|
@ -8,6 +10,7 @@ import { ConfigService } from '@services/config.service';
|
|||
providedIn: 'root'
|
||||
})
|
||||
export class DataService {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
private apiUrl: string;
|
||||
|
||||
|
|
@ -31,7 +34,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching user groups', error);
|
||||
console.error(this.translate.instant('errorFetchingUserGroups'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -47,7 +50,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching user group', error);
|
||||
console.error(this.translate.instant('errorFetchingUserGroup'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchRoleLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
|
||||
<mat-label>{{ 'labelSearchRole' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'labelSearch' | translate }}" [(ngModel)]="filters['name']"
|
||||
(keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
|
|
@ -24,12 +24,12 @@
|
|||
<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>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header | translate }} </th>
|
||||
<td mat-cell *matCellDef="let role"> {{ column.cell(role) }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let role" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="editRole(role)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
|
@ -15,6 +17,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrls: ['./roles.component.css']
|
||||
})
|
||||
export class RolesComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string = this.configService.apiUrl;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
filters: { [key: string]: string } = {};
|
||||
|
|
@ -27,17 +30,17 @@ export class RolesComponent implements OnInit {
|
|||
columns = [
|
||||
{
|
||||
columnDef: 'id',
|
||||
header: 'ID',
|
||||
header: 'labelId',
|
||||
cell: (role: any) => `${role.id}`
|
||||
},
|
||||
{
|
||||
columnDef: 'name',
|
||||
header: 'Nombre',
|
||||
header: 'labelName',
|
||||
cell: (role: any) => `${role.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'permissions',
|
||||
header: 'Permisos',
|
||||
header: 'permissions',
|
||||
cell: (role: any) => `${role.permissions.join(', ')}`
|
||||
}
|
||||
];
|
||||
|
|
@ -66,7 +69,7 @@ export class RolesComponent implements OnInit {
|
|||
this.loading = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching roles', error);
|
||||
console.error(this.translate.instant('errorFetchingRoles'), error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -108,10 +111,10 @@ export class RolesComponent implements OnInit {
|
|||
this.http.delete(apiUrl).subscribe({
|
||||
next: () => {
|
||||
this.search();
|
||||
this.toastService.success('Role deleted successfully');
|
||||
this.toastService.success(this.translate.instant('roleDeletedSuccessfully'));
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error('Error deleting role:', error);
|
||||
this.toastService.error(this.translate.instant('errorDeletingRole'), error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
<mat-dialog-content class="form-container">
|
||||
<form [formGroup]="userForm" class="user-form">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'addUserlabelUsername' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelAddUserUsername' | translate }}</mat-label>
|
||||
<input matInput formControlName="username" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'addUserlabelPassword' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelAddUserPassword' | translate }}</mat-label>
|
||||
<input matInput formControlName="password" type="password" required>
|
||||
</mat-form-field>
|
||||
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Vista tarjetas</mat-label>
|
||||
<mat-label>{{ 'cardView' | translate }}</mat-label>
|
||||
<mat-select formControlName="groupsView" required>
|
||||
<mat-option *ngFor="let option of views" [value]="option.value">
|
||||
{{ option.name }}
|
||||
|
|
@ -40,9 +40,9 @@
|
|||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Clientes por página</mat-label>
|
||||
<input matInput formControlName="clientsPerPage" type="number" min="1" placeholder="Por defecto (20)">
|
||||
<mat-hint>Número de clientes mostrados por página en el listado</mat-hint>
|
||||
<mat-label>{{ 'clientsPerPage' | translate }}</mat-label>
|
||||
<input matInput formControlName="clientsPerPage" type="number" min="1" [placeholder]="'default20' | translate">
|
||||
<mat-hint>{{ 'clientsPerPageHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
|
|
@ -18,6 +20,7 @@ interface UserGroup {
|
|||
styleUrls: ['./add-user-modal.component.css']
|
||||
})
|
||||
export class AddUserModalComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
@Output() userAdded = new EventEmitter<void>();
|
||||
@Output() userEdited = new EventEmitter<void>();
|
||||
|
|
@ -94,7 +97,7 @@ export class AddUserModalComponent implements OnInit {
|
|||
},
|
||||
error: (err) => {
|
||||
this.loading = false;
|
||||
console.error('Error fetching user:', err);
|
||||
console.error(this.translate.instant('fetchingUserError'), err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -125,7 +128,7 @@ export class AddUserModalComponent implements OnInit {
|
|||
if (this.userId) {
|
||||
this.http.put(`${this.baseUrl}${this.userId}`, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Usuario editado correctamente');
|
||||
this.toastService.success(this.translate.instant('userEditedSuccessfully'));
|
||||
this.userEdited.emit();
|
||||
this.dialogRef.close();
|
||||
this.loading = false;
|
||||
|
|
@ -138,7 +141,7 @@ export class AddUserModalComponent implements OnInit {
|
|||
} else {
|
||||
this.http.post(`${this.baseUrl}/users`, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Usuario añadido correctamente');
|
||||
this.toastService.success(this.translate.instant('userAddedSuccessfully'));
|
||||
this.userAdded.emit();
|
||||
this.dialogRef.close();
|
||||
this.loading = false;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<h1 mat-dialog-title>{{ 'dialogTitleChangePassword' | translate }}</h1>
|
||||
<h1 mat-dialog-title>{{ 'changePassword' | translate }}</h1>
|
||||
<mat-dialog-content class="form-container">
|
||||
<form [formGroup]="userForm" class="user-form">
|
||||
<mat-form-field class="form-field">
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, EventEmitter, Inject, Output } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
|
@ -10,6 +12,7 @@ import {ToastrService} from "ngx-toastr";
|
|||
styleUrl: './change-password-modal.component.css'
|
||||
})
|
||||
export class ChangePasswordModalComponent {
|
||||
public translate = inject(TranslateService);
|
||||
@Output() userEdited = new EventEmitter<void>();
|
||||
userForm: FormGroup;
|
||||
passwordMismatch: boolean = false;
|
||||
|
|
@ -55,10 +58,10 @@ export class ChangePasswordModalComponent {
|
|||
next: () => {
|
||||
this.userEdited.emit();
|
||||
this.dialogRef.close();
|
||||
this.toastService.success('Contraseña cambiada con éxito');
|
||||
this.toastService.success(this.translate.instant('passwordChangedSuccessfully'));
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error changing password:', err);
|
||||
console.error(this.translate.instant('errorChangingPassword'), err);
|
||||
this.toastService.error(err.error['hydra:description']);
|
||||
this.resetPasswordError = err.error.message;
|
||||
this.updateError = true;
|
||||
|
|
@ -66,7 +69,7 @@ export class ChangePasswordModalComponent {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
console.error('Form is invalid');
|
||||
console.error(this.translate.instant('formIsInvalid'));
|
||||
this.passwordMismatch = this.userForm.hasError('mismatch');
|
||||
this.updateError = true;
|
||||
this.loading = false
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import {HttpClient, HttpParams} from '@angular/common/http';
|
||||
|
|
@ -9,6 +11,7 @@ import { ConfigService } from '@services/config.service';
|
|||
providedIn: 'root'
|
||||
})
|
||||
export class DataService {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
private apiUrl: string;
|
||||
|
||||
|
|
@ -32,7 +35,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching users', error);
|
||||
console.error(this.translate.instant('fetchingUsersError'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -48,7 +51,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching user group', error);
|
||||
console.error(this.translate.instant('errorFetchingUserGroup'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -64,7 +67,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching user groups', error);
|
||||
console.error(this.translate.instant('errorFetchingUserGroups'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -80,7 +83,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching organizational units', error);
|
||||
console.error(this.translate.instant('errorFetchingOrganizationalUnits'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
|
||||
<mat-label>{{ 'labelSearch' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'labelSearch' | translate }}" [(ngModel)]="filters['name']"
|
||||
(keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
<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>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header | translate }} </th>
|
||||
<td mat-cell *matCellDef="let user">
|
||||
<ng-container *ngIf="column.columnDef === 'groupsView'">
|
||||
<mat-chip>
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let user" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="editUser(user)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
|
@ -13,6 +15,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrls: ['./users.component.css']
|
||||
})
|
||||
export class UsersComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
private apiUrl: string;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
|
|
@ -25,32 +28,32 @@ export class UsersComponent implements OnInit {
|
|||
columns = [
|
||||
{
|
||||
columnDef: 'id',
|
||||
header: 'ID',
|
||||
header: 'labelId',
|
||||
cell: (user: any) => `${user.id}`
|
||||
},
|
||||
{
|
||||
columnDef: 'username',
|
||||
header: 'Nombre de Usuario',
|
||||
header: 'userName',
|
||||
cell: (user: any) => `${user.username}`
|
||||
},
|
||||
{
|
||||
columnDef: 'groupsView',
|
||||
header: 'Vista de Grupos',
|
||||
header: 'groupView',
|
||||
cell: (user: any) => `${user.groupsView}`
|
||||
},
|
||||
{
|
||||
columnDef: 'clientsPerPage',
|
||||
header: 'Clientes por página',
|
||||
header: 'clientsPerPage',
|
||||
cell: (user: any) => user.clientsPerPage != null ? `${user.clientsPerPage}` : '-'
|
||||
},
|
||||
{
|
||||
columnDef: 'allowedOrganizationalUnits',
|
||||
header: 'Unidades Organizacionales Permitidas',
|
||||
header: 'allowedOrganizationalUnits',
|
||||
cell: (user: any) => `${user.allowedOrganizationalUnits.map((unit: { name: any; }) => unit.name).join(', ')}`
|
||||
},
|
||||
{
|
||||
columnDef: 'roles',
|
||||
header: 'Roles',
|
||||
header: 'labelRoles',
|
||||
cell: (user: any) => `${user.roles.join(', ')}`
|
||||
}
|
||||
];
|
||||
|
|
@ -79,7 +82,7 @@ export class UsersComponent implements OnInit {
|
|||
this.loading = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching users', error);
|
||||
console.error(this.translate.instant('fetchingUsersError'), error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -123,11 +126,11 @@ export class UsersComponent implements OnInit {
|
|||
this.http.delete(apiUrl).subscribe({
|
||||
next: () => {
|
||||
console.log('User deleted successfully');
|
||||
this.toastService.success('User deleted successfully');
|
||||
this.toastService.success(this.translate.instant('userDeletedSuccessfully'));
|
||||
this.search();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error deleting user:', error);
|
||||
console.error(this.translate.instant('errorDeletingUser'), error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
<div class="search-container">
|
||||
<mat-form-field joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}" appearance="fill"
|
||||
class="search-string">
|
||||
<mat-label>{{ 'searchCalendarLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
|
||||
<mat-label>{{ 'labelSearchCalendar' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'labelSearch' | translate }}" [(ngModel)]="filters['name']"
|
||||
(keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
text="{{ 'tableStepText' | translate }}">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header | translate }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef === 'isDefault' || column.columnDef === 'installed'">
|
||||
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions" joyrideStep="actionsStep" text="{{ 'actionsStepText' | translate }}">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let calendar" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="editCalendar(calendar)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit, signal } from '@angular/core';
|
||||
import { MatTableDataSource } from "@angular/material/table";
|
||||
import { DatePipe } from "@angular/common";
|
||||
|
|
@ -17,6 +19,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrl: './calendar.component.css'
|
||||
})
|
||||
export class CalendarComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
private apiUrl: string;
|
||||
images: { downloadUrl: string; name: string; uuid: string }[] = [];
|
||||
|
|
@ -34,22 +37,22 @@ export class CalendarComponent implements OnInit {
|
|||
columns = [
|
||||
{
|
||||
columnDef: 'id',
|
||||
header: 'ID',
|
||||
header: 'labelId',
|
||||
cell: (remoteCalendar: any) => `${remoteCalendar.id}`,
|
||||
},
|
||||
{
|
||||
columnDef: 'name',
|
||||
header: 'Nombre',
|
||||
header: 'labelName',
|
||||
cell: (remoteCalendar: any) => `${remoteCalendar.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'rulesLength',
|
||||
header: 'Nº de reglas',
|
||||
header: 'numberOfRules',
|
||||
cell: (remoteCalendar: any) => `${remoteCalendar.remoteCalendarRules.length}`
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
header: 'Fecha de creación',
|
||||
header: 'creationDate',
|
||||
cell: (remoteCalendar: any) => `${this.datePipe.transform(remoteCalendar.createdAt, 'dd/MM/yyyy hh:mm:ss')}`,
|
||||
}
|
||||
];
|
||||
|
|
@ -89,7 +92,7 @@ export class CalendarComponent implements OnInit {
|
|||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching calendars', error);
|
||||
console.error(this.translate.instant('errorFetchingCalendars'), error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -99,7 +102,7 @@ export class CalendarComponent implements OnInit {
|
|||
this.syncUds = true;
|
||||
this.http.post(`${this.apiUrl}/${calendar.uuid}/sync-uds`, {}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Calendarios sincronizados correctamente');
|
||||
this.toastService.success(this.translate.instant('calendarsSynchronizedCorrectly'));
|
||||
this.search();
|
||||
this.syncUds = false;
|
||||
},
|
||||
|
|
@ -136,10 +139,10 @@ export class CalendarComponent implements OnInit {
|
|||
this.http.delete(apiUrl).subscribe({
|
||||
next: () => {
|
||||
this.search();
|
||||
this.toastService.success('Calendar deleted successfully');
|
||||
this.toastService.success(this.translate.instant('calendarDeletedSuccessfully'));
|
||||
},
|
||||
error: () => {
|
||||
this.toastService.error('Error deleting calendar');
|
||||
this.toastService.error(this.translate.instant('errorDeletingCalendar'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<div *ngIf="!isRemoteAvailable" class="form-group">
|
||||
<mat-label>{{ 'selectWeekDays' | translate }}</mat-label>
|
||||
<p class="custom-text"> (Los dias y horas seleccionados se marcarán como aula no disponible para remote pc.) </p>
|
||||
<p class="custom-text"> {{ 'remotePcCalendarRuleInfo' | translate }}</p>
|
||||
<div class="weekday-toggle-group full-width">
|
||||
<button
|
||||
*ngFor="let day of weekDays"
|
||||
|
|
@ -38,31 +38,29 @@
|
|||
<div class="unavailability-summary" *ngIf="busyFromHour && busyToHour && busyWeekDays && getSelectedDays().length">
|
||||
<mat-icon style="width: 50px;" color="warn">block</mat-icon>
|
||||
<span class="summary-text">
|
||||
El aula estará <strong>no disponible</strong> para Remote PC los días:
|
||||
<strong>{{ getSelectedDays().join(', ') }}</strong> de
|
||||
<strong>{{ busyFromHour }}</strong> a <strong>{{ busyToHour }}</strong>.
|
||||
{{ 'classroomWillBe' | translate }}<strong>{{ 'notAvailableLowercase' | translate }}</strong> {{ 'forRemotePcOnDays' | translate }}<strong>{{ getSelectedDays().join(', ') }}</strong> {{ 'labelOf' | translate }}<strong>{{ busyFromHour }}</strong> a <strong>{{ busyToHour }}</strong>.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isRemoteAvailable" class="form-group">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'reasonLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelReason' | translate }}</mat-label>
|
||||
<input matInput [(ngModel)]="availableReason" placeholder="{{ 'reasonPlaceholder' | translate }}" [required]="isRemoteAvailable">
|
||||
<mat-hint>Razón por la cual el aula SI está disponible para su uso en Remote PC</mat-hint>
|
||||
<mat-hint>{{ 'reasonAvailableRemotePc' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
<div class="time-fields">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'startDate' | translate }}</mat-label>
|
||||
<input matInput [matDatepicker]="picker1" [(ngModel)]="availableFromDate" [required]="isRemoteAvailable">
|
||||
<mat-hint>MM/DD/YYYY</mat-hint>
|
||||
<mat-hint>{{ 'dateFormatPlaceholder' | translate }}</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker1"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker1></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'endDate' | translate }}</mat-label>
|
||||
<input matInput [matDatepicker]="picker2" [(ngModel)]="availableToDate" [required]="isRemoteAvailable">
|
||||
<mat-hint>MM/DD/YYYY</mat-hint>
|
||||
<mat-hint>{{ 'dateFormatPlaceholder' | translate }}</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker2"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker2></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
|
@ -84,11 +82,9 @@
|
|||
<div class="availability-summary" *ngIf="availableFromDate && availableToDate">
|
||||
<mat-icon color="primary" style="width: 50px;">info</mat-icon>
|
||||
<span class="summary-text">
|
||||
El aula estará <strong>disponible</strong> para reserva desde el
|
||||
<strong>{{ availableFromDate | date:'fullDate' }}</strong> hasta el
|
||||
<strong>{{ availableToDate | date:'fullDate' }}</strong>
|
||||
{{ 'classroomWillBe' | translate }}<strong>{{ 'statusAvailableLowercase' | translate }}</strong> {{ 'forReservationFrom' | translate }}<strong>{{ availableFromDate | date:'fullDate' }}</strong> {{ 'untilThe' | translate }}<strong>{{ availableToDate | date:'fullDate' }}</strong>
|
||||
<span *ngIf="busyFromHour && busyToHour">
|
||||
en el horario de <strong>{{ busyFromHour }}</strong> a <strong>{{ busyToHour }}</strong>.
|
||||
{{ 'inScheduleOf' | translate }}<strong>{{ busyFromHour }}</strong> a <strong>{{ busyToHour }}</strong>.
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
|
|
@ -10,6 +12,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrl: './create-calendar-rule.component.css'
|
||||
})
|
||||
export class CreateCalendarRuleComponent {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
name: string = '';
|
||||
remoteCalendarRules: any[] = [];
|
||||
|
|
@ -98,7 +101,7 @@ export class CreateCalendarRuleComponent {
|
|||
this.http.put(`${this.baseUrl}${this.ruleId}`, formData)
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Calendar rule updated successfully');
|
||||
this.toastService.success(this.translate.instant('calendarRuleUpdatedSuccessfully'));
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
error: (error) => {
|
||||
|
|
@ -110,7 +113,7 @@ export class CreateCalendarRuleComponent {
|
|||
this.http.post(`${this.baseUrl}/remote-calendar-rules`, formData)
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Calendar rule created successfully');
|
||||
this.toastService.success(this.translate.instant('calendarRuleCreatedSuccessfully'));
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
error: (error) => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<mat-dialog-content class="form-container">
|
||||
<!-- Campo para el nombre -->
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'labelName' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelRoleName' | translate }}</mat-label>
|
||||
<input matInput [(ngModel)]="name" required>
|
||||
<mat-icon *ngIf="isEditMode" matSuffix (click)="submitForm()">mode_edit</mat-icon>
|
||||
</mat-form-field>
|
||||
|
|
@ -28,9 +28,9 @@
|
|||
<mat-icon matListItemIcon>event_available</mat-icon>
|
||||
<div class="text-content">
|
||||
<div matListItemTitle>{{ rule.isRemoteAvailable ? ('remotePcStatusAvailable' | translate) : ('remotePcStatusUnavailable' | translate) }}</div>
|
||||
<div matListItemLine *ngIf="!rule.isRemoteAvailable">Días: <strong>{{ rule.busyWeekDaysMap }}</strong></div>
|
||||
<div matListItemLine *ngIf="!rule.isRemoteAvailable">{{ 'labelDays' | translate }}<strong>{{ rule.busyWeekDaysMap }}</strong></div>
|
||||
<div matListItemLine *ngIf="rule.isRemoteAvailable">Razón: {{ rule.availableReason }}</div>
|
||||
<div matListItemLine *ngIf="rule.isRemoteAvailable">Días: <strong>{{ rule.availableFromDate | date }} - {{ rule.availableToDate | date }}</strong></div>
|
||||
<div matListItemLine *ngIf="rule.isRemoteAvailable">{{ 'labelDays' | translate }}<strong>{{ rule.availableFromDate | date }} - {{ rule.availableToDate | date }}</strong></div>
|
||||
<div matListItemLine>Horario: {{ rule.busyFromHour }} - {{ rule.busyToHour }}</div>
|
||||
</div>
|
||||
<div class="icon-container">
|
||||
|
|
@ -50,6 +50,6 @@
|
|||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="submitForm()" [disabled]="!name || name === ''" cdkFocusInitial>
|
||||
{{ 'buttonSave' | translate }}
|
||||
{{ 'labelSaveButton' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
|
|
@ -13,6 +15,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrl: './create-calendar.component.css'
|
||||
})
|
||||
export class CreateCalendarComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
name: string = '';
|
||||
remoteCalendarRules: any[] = [];
|
||||
|
|
@ -50,7 +53,7 @@ export class CreateCalendarComponent implements OnInit {
|
|||
});
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error fetching remote calendar:', err);
|
||||
console.error(this.translate.instant('errorFetchingRemoteCalendar'), err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -75,7 +78,7 @@ export class CreateCalendarComponent implements OnInit {
|
|||
this.http.patch(`${this.baseUrl}${this.calendarId}`, payload)
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Calendar updated successfully');
|
||||
this.toastService.success(this.translate.instant('calendarUpdatedSuccessfully'));
|
||||
this.dialogRef.close(true);
|
||||
this.load()
|
||||
},
|
||||
|
|
@ -88,7 +91,7 @@ export class CreateCalendarComponent implements OnInit {
|
|||
this.http.post(`${this.baseUrl}/remote-calendars`, payload)
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Calendar created successfully');
|
||||
this.toastService.success(this.translate.instant('calendarCreatedSuccessfully'));
|
||||
this.dialogRef.close(true);
|
||||
this.load()
|
||||
},
|
||||
|
|
@ -129,10 +132,10 @@ export class CreateCalendarComponent implements OnInit {
|
|||
next: () => {
|
||||
console.log('Calendar deleted successfully');
|
||||
this.load();
|
||||
this.toastService.success('Calendar deleted successfully');
|
||||
this.toastService.success(this.translate.instant('calendarDeletedSuccessfully'));
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error('Error deleting calendar');
|
||||
this.toastService.error(this.translate.instant('errorDeletingCalendar'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
|
|
@ -8,6 +10,7 @@ import { ConfigService } from '@services/config.service';
|
|||
providedIn: 'root'
|
||||
})
|
||||
export class DataService {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
private apiUrl: string;
|
||||
|
||||
|
|
@ -28,7 +31,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching remote calendars', error);
|
||||
console.error(this.translate.instant('errorFetchingRemoteCalendars'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -44,7 +47,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching calendar', error);
|
||||
console.error(this.translate.instant('fetchingCalendarError'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
|
||||
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchGroupNameLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()">
|
||||
<mat-label>{{ 'labelSearchGroupName' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'labelSearch' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
<div *ngIf="!loading">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableStepText' | translate }}">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header | translate }} </th>
|
||||
<td mat-cell *matCellDef="let commandGroup">
|
||||
<ng-container *ngIf="column.columnDef !== 'commands'">
|
||||
{{ column.cell(commandGroup) }}
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let client" style="text-align: center;" joyrideStep="actionsStep" text="{{ 'actionsStepText' | translate }}">
|
||||
<button mat-icon-button color="info" (click)="viewGroupDetails(client)">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
|
@ -16,6 +18,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrls: ['./commands-groups.component.css']
|
||||
})
|
||||
export class CommandsGroupsComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
private apiUrl: string;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
|
|
@ -29,22 +32,22 @@ export class CommandsGroupsComponent implements OnInit {
|
|||
columns = [
|
||||
{
|
||||
columnDef: 'id',
|
||||
header: 'ID',
|
||||
header: 'labelId',
|
||||
cell: (group: any) => `${group.id}`,
|
||||
},
|
||||
{
|
||||
columnDef: 'name',
|
||||
header: 'Nombre',
|
||||
header: 'labelName',
|
||||
cell: (group: any) => `${group.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'commands',
|
||||
header: 'Lista de comandos',
|
||||
header: 'commandList',
|
||||
cell: (group: any) => `${group.commands}`
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
header: 'Fecha de creación',
|
||||
header: 'creationDate',
|
||||
cell: (group: any) => `${this.datePipe.transform(group.createdAt, 'dd/MM/yyyy hh:mm:ss')}`,
|
||||
}
|
||||
];
|
||||
|
|
@ -69,7 +72,7 @@ export class CommandsGroupsComponent implements OnInit {
|
|||
this.loading = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching command groups', error);
|
||||
console.error(this.translate.instant('fetchingCommandGroupsError'), error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -103,11 +106,11 @@ export class CommandsGroupsComponent implements OnInit {
|
|||
if (result) {
|
||||
this.http.delete(`${this.apiUrl}/${group.uuid}`).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Grupo de comandos eliminado con éxito');
|
||||
this.toastService.success(this.translate.instant('commandGroupRemovedWith'));
|
||||
this.search();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al eliminar el grupo de comandos:', error);
|
||||
console.error(this.translate.instant('errorDeletingGroup'), error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
<form *ngIf="!loading" class="command-group-form" (ngSubmit)="onSubmit()">
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'groupNameLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelGroupName' | translate }}</mat-label>
|
||||
<input matInput [(ngModel)]="groupName" name="groupName" required />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-slide-toggle [(ngModel)]="enabled" name="enabled">{{ 'enabledToggle' | translate }}</mat-slide-toggle>
|
||||
<mat-slide-toggle [(ngModel)]="enabled" name="enabled">{{ 'labelEnabled' | translate }}</mat-slide-toggle>
|
||||
|
||||
<div class="command-selection">
|
||||
<div class="available-commands">
|
||||
|
|
@ -16,12 +16,12 @@
|
|||
<div class="table-wrapper">
|
||||
<table mat-table [dataSource]="availableCommands" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'nameColumn' | translate }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'labelRoleName' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let command"> {{ command.name }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'actionsColumn' | translate }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'actions' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let command">
|
||||
<button mat-icon-button type="button" (click)="addCommand(command)">
|
||||
<mat-icon>add</mat-icon>
|
||||
|
|
@ -55,5 +55,5 @@
|
|||
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSubmit()" cdkFocusInitial>{{ 'buttonSave' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSubmit()" cdkFocusInitial>{{ 'labelSaveButton' | translate }}</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
|
@ -11,6 +13,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrls: ['./create-command-group.component.css']
|
||||
})
|
||||
export class CreateCommandGroupComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
availableCommands: any[] = [];
|
||||
selectedCommands: any[] = [];
|
||||
|
|
@ -48,7 +51,7 @@ export class CreateCommandGroupComponent implements OnInit {
|
|||
this.loading = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching available commands', error);
|
||||
console.error(this.translate.instant('errorFetchingAvailableCommands'), error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -88,23 +91,23 @@ export class CreateCommandGroupComponent implements OnInit {
|
|||
const groupId = this.data.group.uuid;
|
||||
this.http.patch(`${this.baseUrl}/command-groups/${groupId}`, payload).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Grupo de comandos actualizado con éxito');
|
||||
this.toastService.success(this.translate.instant('commandGroupUpdatedWith'));
|
||||
this.dialogRef.close();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error actualizando el grupo de comandos', error);
|
||||
this.toastService.error('Error al actualizar el grupo de comandos');
|
||||
console.error(this.translate.instant('errorUpdatingGroup'), error);
|
||||
this.toastService.error(this.translate.instant('errorUpdatingGroup'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.http.post(`${this.baseUrl}/command-groups`, payload).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Grupo de comandos creado con éxito');
|
||||
this.toastService.success(this.translate.instant('commandGroupCreatedWith'));
|
||||
this.dialogRef.close();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error creando el grupo de comandos', error);
|
||||
this.toastService.error('Error al crear el grupo de comandos');
|
||||
console.error(this.translate.instant('errorCreatingGroup'), error);
|
||||
this.toastService.error(this.translate.instant('errorCreatingGroup'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@
|
|||
<h3>{{ 'includedCommands' | translate }}</h3>
|
||||
<table mat-table [dataSource]="data.commands" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'nameColumn' | translate }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'labelRoleName' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let command"> {{ command.name }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="uuid">
|
||||
<th mat-header-cell *matHeaderCellDef> UUID </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'labelUuid' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let command"> {{ command.uuid }} </td>
|
||||
</ng-container>
|
||||
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
<form [formGroup]="form">
|
||||
<h4>{{ 'selectClients' | translate }}</h4>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>{{ 'clientsLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelClients' | translate }}</mat-label>
|
||||
<mat-select formControlName="selectedClients" multiple (selectionChange)="onClientSelectionChange($event)">
|
||||
<mat-option *ngFor="let client of clients" [value]="client.uuid">
|
||||
{{ client.name }}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
|
|
@ -11,6 +13,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrls: ['./detail-command-group.component.css']
|
||||
})
|
||||
export class DetailCommandGroupComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
form!: FormGroup;
|
||||
clients: any[] = [];
|
||||
|
|
@ -45,7 +48,7 @@ export class DetailCommandGroupComponent implements OnInit {
|
|||
this.loading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error fetching clients:', error);
|
||||
console.error(this.translate.instant('fetchingClients1Error'), error);
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
|
|
@ -70,11 +73,11 @@ export class DetailCommandGroupComponent implements OnInit {
|
|||
this.http.post(apiUrl, payload).subscribe({
|
||||
next: () => {
|
||||
this.dialogRef.close();
|
||||
this.toastService.success('Grupo de comandos ejecutado exitosamente');
|
||||
this.toastService.success(this.translate.instant('groupOfCommandsExecutedSuccessfully'));
|
||||
this.loading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error ejecutando grupo de comandos:', error);
|
||||
console.error(this.translate.instant('errorExecutingGroupOfCommands'), error);
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchTaskLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()" />
|
||||
<mat-label>{{ 'labelSearchTask' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'labelSearch' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()" />
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
<div *ngIf="!loading">
|
||||
<table mat-table [dataSource]="tasks" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableStepText' | translate }}">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header | translate }} </th>
|
||||
<td mat-cell *matCellDef="let task">
|
||||
<ng-container *ngIf="column.columnDef !== 'management' && column.columnDef !== 'scope' && column.columnDef !== 'organizationalUnit'">
|
||||
{{ column.cell(task) }}
|
||||
|
|
@ -50,14 +50,14 @@
|
|||
</button>
|
||||
<button class="action-button script-btn" (click)="openShowScriptDialog(task)">
|
||||
<mat-icon>code</mat-icon>
|
||||
<span>{{ 'taskActions' | translate }}</span>
|
||||
<span>{{ 'actions' | translate }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let task" style="text-align: center;" joyrideStep="actionsStep" text="{{ 'actionsStepText' | translate }}">
|
||||
<button mat-icon-button color="primary" (click)="editTask(task)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
|
@ -16,6 +18,7 @@ import {DatePipe} from "@angular/common";
|
|||
styleUrls: ['./commands-task.component.css']
|
||||
})
|
||||
export class CommandsTaskComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
tasks: any[] = [];
|
||||
filters: { [key: string]: string | boolean } = {};
|
||||
|
|
@ -26,13 +29,13 @@ export class CommandsTaskComponent implements OnInit {
|
|||
datePipe: DatePipe = new DatePipe('es-ES');
|
||||
|
||||
columns = [
|
||||
{ columnDef: 'id', header: 'ID', cell: (task: any) => task.id },
|
||||
{ columnDef: 'name', header: 'Nombre de tarea', cell: (task: any) => task.name },
|
||||
{ columnDef: 'scope', header: 'Ámbito', cell: (task: any) => task.scope },
|
||||
{ columnDef: 'organizationalUnit', header: 'Unidad Organizativa', cell: (task: any) => task.organizationalUnit?.name || '' },
|
||||
{ columnDef: 'management', header: 'Gestiones', cell: (task: any) => task.schedules },
|
||||
{ columnDef: 'nextExecution', header: 'Próxima ejecución', cell: (task: any) => this.datePipe.transform(task.nextExecution, 'dd/MM/yyyy HH:mm:ss') },
|
||||
{ columnDef: 'createdBy', header: 'Creado por', cell: (task: any) => task.createdBy },
|
||||
{ columnDef: 'id', header: 'labelId', cell: (task: any) => task.id },
|
||||
{ columnDef: 'name', header: 'taskName', cell: (task: any) => task.name },
|
||||
{ columnDef: 'scope', header: 'labelScope', cell: (task: any) => task.scope },
|
||||
{ columnDef: 'organizationalUnit', header: 'organizationalUnit', cell: (task: any) => task.organizationalUnit?.name || '' },
|
||||
{ columnDef: 'management', header: 'efforts', cell: (task: any) => task.schedules },
|
||||
{ columnDef: 'nextExecution', header: 'nextExecution', cell: (task: any) => this.datePipe.transform(task.nextExecution, 'dd/MM/yyyy HH:mm:ss') },
|
||||
{ columnDef: 'createdBy', header: 'createdBy', cell: (task: any) => task.createdBy },
|
||||
];
|
||||
|
||||
displayedColumns: string[] = ['id', 'name', 'scope', 'management', 'nextExecution', 'createdBy', 'actions'];
|
||||
|
|
@ -65,7 +68,7 @@ export class CommandsTaskComponent implements OnInit {
|
|||
this.loading = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching tasks', error);
|
||||
console.error(this.translate.instant('fetchingTasksError'), error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -133,11 +136,11 @@ export class CommandsTaskComponent implements OnInit {
|
|||
if (result) {
|
||||
this.http.delete(`${this.apiUrl}/${task.uuid}`).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Tarea eliminada con éxito');
|
||||
this.toastService.success(this.translate.instant('taskDeletedSuccessfully'));
|
||||
this.loadTasks();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al eliminar la tarea:', error);
|
||||
console.error(this.translate.instant('errorDeletingTask'), error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
</mat-form-field>
|
||||
|
||||
<div *ngIf="form.get('recurrenceType')?.value !== 'none'" class="mb-4">
|
||||
<label class="section-label">{{ 'weekDaysLabel' | translate }}:</label>
|
||||
<label class="section-label">{{ 'labelWeekDays' | translate }}:</label>
|
||||
<div class="weekday-toggle-group">
|
||||
<button
|
||||
*ngFor="let day of weekDays"
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
</div>
|
||||
|
||||
<div *ngIf="form.get('recurrenceType')?.value !== 'none'" >
|
||||
<label class="section-label">{{ 'monthsLabel' | translate }}:</label>
|
||||
<label class="section-label">{{ 'labelMonths' | translate }}:</label>
|
||||
<div class="month-toggle-row" *ngFor="let row of monthRows">
|
||||
<button
|
||||
*ngFor="let month of row"
|
||||
|
|
@ -106,6 +106,6 @@
|
|||
<button class="ordinary-button" (click)="onCancel()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" [disabled]="!form.valid" (click)="onSubmit()">
|
||||
<mat-icon>save</mat-icon>
|
||||
{{ 'buttonSave' | translate }}
|
||||
{{ 'labelSaveButton' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {Component, Inject, OnInit } from '@angular/core';
|
||||
import {FormBuilder, FormGroup} from "@angular/forms";
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
|
|
@ -11,12 +13,13 @@ import {ConfigService} from "@services/config.service";
|
|||
styleUrl: './create-task-schedule.component.css'
|
||||
})
|
||||
export class CreateTaskScheduleComponent implements OnInit{
|
||||
public translate = inject(TranslateService);
|
||||
form: FormGroup;
|
||||
baseUrl: string;
|
||||
apiUrl: string;
|
||||
recurrenceTypes = [
|
||||
{ value: 'none', label: 'Sin repetición' },
|
||||
{ value: 'custom', label: 'Repetición personalizada' }
|
||||
{ value: 'none', label : this.translate.instant('withoutRepetition') },
|
||||
{ value: 'custom', label : this.translate.instant('customRepeat') }
|
||||
];
|
||||
weekDays: string[] = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
isSingleDateSelected: boolean = true;
|
||||
|
|
@ -105,7 +108,7 @@ export class CreateTaskScheduleComponent implements OnInit{
|
|||
}, {});
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error loading schedule data', error);
|
||||
console.error(this.translate.instant('errorLoadingScheduleData'), error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -173,12 +176,12 @@ export class CreateTaskScheduleComponent implements OnInit{
|
|||
const selectedMonthNames = Object.keys(this.selectedMonths).filter(month => this.selectedMonths[month]);
|
||||
|
||||
if (selectedDayNames.length === 0) {
|
||||
this.toastr.error('Debe seleccionar al menos un día de la semana');
|
||||
this.toastr.error(this.translate.instant('youMustSelectAtLeastOne1'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedMonthNames.length === 0) {
|
||||
this.toastr.error('Debe seleccionar al menos un mes');
|
||||
this.toastr.error(this.translate.instant('youMustSelectAtLeastOne2'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -186,12 +189,12 @@ export class CreateTaskScheduleComponent implements OnInit{
|
|||
const endDate = formData.recurrenceDetails?.endDate;
|
||||
|
||||
if (!initDate || !endDate) {
|
||||
this.toastr.error('Debe especificar fecha de inicio y fecha de fin');
|
||||
this.toastr.error(this.translate.instant('youMustSpecifyStartDate'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (new Date(endDate) <= new Date(initDate)) {
|
||||
this.toastr.error('La fecha de fin debe ser posterior a la fecha de inicio');
|
||||
this.toastr.error(this.translate.instant('theEndDateMust'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -215,21 +218,21 @@ export class CreateTaskScheduleComponent implements OnInit{
|
|||
const taskId = this.data.task.uuid;
|
||||
this.http.patch<any>(`${this.baseUrl}${this.data.schedule['@id']}`, payload).subscribe({
|
||||
next: () => {
|
||||
this.toastr.success('Programacion de tarea actualizada con éxito');
|
||||
this.toastr.success(this.translate.instant('updatedTaskScheduleWith'));
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
error: () => {
|
||||
this.toastr.error('Error al actualizar la tarea');
|
||||
this.toastr.error(this.translate.instant('errorUpdatingTask'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.http.post<any>(this.apiUrl, payload).subscribe({
|
||||
next: () => {
|
||||
this.toastr.success('Programacion de tarea creada con éxito');
|
||||
this.toastr.success(this.translate.instant('taskScheduleCreatedWith'));
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
error: () => {
|
||||
this.toastr.error('Error al crear la tarea');
|
||||
this.toastr.error(this.translate.instant('errorCreatingTask'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
</div>
|
||||
|
||||
<div class="info-supported-ops">
|
||||
<p class="info-supported-label">{{ 'supportedOpsLabel' | translate }}:</p>
|
||||
<p class="info-supported-label">{{ 'labelSupportedOps' | translate }}:</p>
|
||||
<ng-container *ngFor="let cat of categories">
|
||||
<ng-container *ngIf="cat.id === selectedCategory">
|
||||
<div *ngFor="let action of cat.actions" class="info-op-chip">
|
||||
|
|
@ -131,7 +131,7 @@
|
|||
<mat-label>{{ 'enterScript' | translate }}</mat-label>
|
||||
<textarea matInput [(ngModel)]="newScript" rows="6" [placeholder]="'writeScriptHere' | translate"></textarea>
|
||||
</mat-form-field>
|
||||
<button mat-flat-button color="primary" (click)="saveNewScript()">{{ 'saveScript' | translate }}</button>
|
||||
<button mat-flat-button color="primary" (click)="saveNewScript()">{{ 'buttonSaveScript' | translate }}</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="commandType === 'existing'">
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
|
||||
<div *ngIf="selectedScript && commandType === 'existing'" class="script-container">
|
||||
<div class="script-content">
|
||||
<h3>{{ 'scriptLabel' | translate }}:</h3>
|
||||
<h3>{{ 'labelScript' | translate }}:</h3>
|
||||
<div class="script-preview" [innerHTML]="scriptContent"></div>
|
||||
</div>
|
||||
<div class="script-params" *ngIf="parameterNames.length > 0">
|
||||
|
|
@ -165,5 +165,5 @@
|
|||
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onCancel()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSubmit()" *ngIf="selectedAction">{{ 'buttonSave' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSubmit()" *ngIf="selectedAction">{{ 'labelSaveButton' | translate }}</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ export class CreateTaskScriptComponent implements OnInit {
|
|||
|
||||
saveNewScript(): void {
|
||||
if (!this.newScript.trim()) {
|
||||
this.toastService.error('Debe ingresar un script antes de guardar.');
|
||||
this.toastService.error(this.translate.instant('youMustEnterAScriptBefore'));
|
||||
return;
|
||||
}
|
||||
const dialogRef = this.dialog.open(SaveScriptComponent, {
|
||||
|
|
@ -209,7 +209,7 @@ export class CreateTaskScriptComponent implements OnInit {
|
|||
});
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.toastService.success('Script guardado correctamente');
|
||||
this.toastService.success(this.translate.instant('scriptSavedSuccessfully'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -244,7 +244,7 @@ export class CreateTaskScriptComponent implements OnInit {
|
|||
break;
|
||||
|
||||
default:
|
||||
this.toastService.error('Tipo de acción no reconocido');
|
||||
this.toastService.error(this.translate.instant('unrecognizedActionType'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -269,12 +269,12 @@ export class CreateTaskScriptComponent implements OnInit {
|
|||
private validateScript(): boolean {
|
||||
if (this.commandType === 'new') {
|
||||
if (!this.newScript.trim()) {
|
||||
this.toastService.error('Debe ingresar un script');
|
||||
this.toastService.error(this.translate.instant('youMustEnterAScript'));
|
||||
return false;
|
||||
}
|
||||
} else if (this.commandType === 'existing') {
|
||||
if (!this.selectedScript) {
|
||||
this.toastService.error('Debe seleccionar un script');
|
||||
this.toastService.error(this.translate.instant('youMustSelectAScript'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<mat-button-toggle-group *ngIf="data?.source === 'assistant'" [(ngModel)]="taskMode" class="task-mode-selection" name="taskMode" appearance="standard">
|
||||
<mat-button-toggle value="create">
|
||||
<mat-icon>add_task</mat-icon>
|
||||
{{ 'createTaskLabel' | translate }}
|
||||
{{ 'labelCreateTask' | translate }}
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle value="add">
|
||||
<mat-icon>playlist_add</mat-icon>
|
||||
|
|
@ -41,13 +41,13 @@
|
|||
|
||||
<form *ngIf="taskMode === 'create' && taskForm && !loading" [formGroup]="taskForm" class="task-form">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'nameLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelRoleName' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" [placeholder]="'nameLabel' | translate">
|
||||
<mat-error *ngIf="taskForm.get('name')?.invalid">{{ 'requiredFieldError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'notesLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelNotes' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="notes" [placeholder]="'notesPlaceholder' | translate"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
|
|
@ -58,19 +58,19 @@
|
|||
<mat-divider style="margin: 15px 0;"></mat-divider>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'scopeLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelScope' | translate }}</mat-label>
|
||||
<mat-select formControlName="scope" (selectionChange)="onScopeChange($event.value)"
|
||||
[disabled]="data?.clients && data.clients.length >= 1">
|
||||
<mat-option value="organizational-unit">{{ 'scopeOrganizationalUnit' | translate }}</mat-option>
|
||||
<mat-option value="organizational-unit">{{ 'organizationalUnit' | translate }}</mat-option>
|
||||
<mat-option value="classrooms-group">{{ 'scopeClassroomsGroup' | translate }}</mat-option>
|
||||
<mat-option value="classroom">{{ 'scopeClassroom' | translate }}</mat-option>
|
||||
<mat-option value="clients-group">{{ 'scopeClientsGroup' | translate }}</mat-option>
|
||||
<mat-option value="clients">{{ 'scopeClients' | translate }}</mat-option>
|
||||
<mat-option value="classroom">{{ 'classrooms' | translate }}</mat-option>
|
||||
<mat-option value="clients-group">{{ 'clientGroupsTitle' | translate }}</mat-option>
|
||||
<mat-option value="clients">{{ 'labelClients' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field *ngIf="taskForm.get('scope')?.value !== 'clients'" appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'organizationalUnitLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelOrganizationalUnit' | translate }}</mat-label>
|
||||
<mat-select formControlName="organizationalUnit">
|
||||
<mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
|
||||
<div class="unit-name">{{ unit.name }}</div>
|
||||
|
|
@ -121,6 +121,6 @@
|
|||
class="submit-button"
|
||||
(click)="taskMode === 'create' ? saveTask() : addToExistingTask()"
|
||||
>
|
||||
{{ 'buttonSave' | translate }}
|
||||
{{ 'labelSaveButton' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog';
|
||||
|
|
@ -15,6 +17,7 @@ import {ShowTaskScriptComponent} from "../show-task-script/show-task-script.comp
|
|||
styleUrls: ['./create-task.component.css']
|
||||
})
|
||||
export class CreateTaskComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
taskForm: FormGroup;
|
||||
availableCommandGroups: any[] = [];
|
||||
|
|
@ -109,7 +112,7 @@ export class CreateTaskComponent implements OnInit {
|
|||
resolve();
|
||||
},
|
||||
(error) => {
|
||||
this.toastr.error('Error al cargar los datos de la tarea');
|
||||
this.toastr.error(this.translate.instant('errorLoadingData'));
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
|
|
@ -124,7 +127,7 @@ export class CreateTaskComponent implements OnInit {
|
|||
resolve();
|
||||
},
|
||||
(error) => {
|
||||
this.toastr.error('Error al cargar las tareas existentes');
|
||||
this.toastr.error(this.translate.instant('errorLoadingTasks'));
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
|
|
@ -137,7 +140,7 @@ export class CreateTaskComponent implements OnInit {
|
|||
this.selectedClients = [...this.data.selectedClients];
|
||||
this.clients = [...this.data.selectedClients];
|
||||
} else {
|
||||
this.toastr.error('No hay clientes pre-seleccionados para este ámbito');
|
||||
this.toastr.error(this.translate.instant('thereAreNoPreselectedClientsFor'));
|
||||
this.taskForm.get('scope')?.setValue('');
|
||||
return;
|
||||
}
|
||||
|
|
@ -168,7 +171,7 @@ export class CreateTaskComponent implements OnInit {
|
|||
this.availableOrganizationalUnits = filteredUnits;
|
||||
resolve();
|
||||
}, error => {
|
||||
this.toastr.error('Error al filtrar las unidades organizacionales');
|
||||
this.toastr.error(this.translate.instant('errorWhenFilteringUnits'));
|
||||
reject(error);
|
||||
});
|
||||
})
|
||||
|
|
@ -187,7 +190,7 @@ export class CreateTaskComponent implements OnInit {
|
|||
resolve();
|
||||
},
|
||||
(error) => {
|
||||
this.toastr.error('Error al cargar los grupos de comandos');
|
||||
this.toastr.error(this.translate.instant('errorLoadingGroups'));
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
|
@ -201,7 +204,7 @@ export class CreateTaskComponent implements OnInit {
|
|||
resolve();
|
||||
},
|
||||
(error) => {
|
||||
this.toastr.error('Error al cargar los comandos individuales');
|
||||
this.toastr.error(this.translate.instant('errorLoadingCommands'));
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
|
@ -216,7 +219,7 @@ export class CreateTaskComponent implements OnInit {
|
|||
resolve();
|
||||
},
|
||||
(error) => {
|
||||
this.toastr.error('Error al cargar las unidades organizacionales');
|
||||
this.toastr.error(this.translate.instant('errorLoadingUnits'));
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
|
|
@ -225,12 +228,12 @@ export class CreateTaskComponent implements OnInit {
|
|||
|
||||
addToExistingTask() {
|
||||
if (!this.selectedExistingTask) {
|
||||
this.toastr.error('Debes seleccionar una tarea existente.');
|
||||
this.toastr.error(this.translate.instant('youMustSelectAnExistingTask'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.executionOrder == null || this.executionOrder < 1) {
|
||||
this.toastr.error('Debes introducir un orden de ejecución válido (mayor que 0).');
|
||||
this.toastr.error(this.translate.instant('youMustEnterAnOrderOf'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -239,14 +242,14 @@ export class CreateTaskComponent implements OnInit {
|
|||
executionOrder: this.executionOrder
|
||||
};
|
||||
|
||||
this.toastr.success('Tarea actualizada con éxito');
|
||||
this.toastr.success(this.translate.instant('taskUpdatedSuccessfully'));
|
||||
this.dialogRef.close(data);
|
||||
}
|
||||
|
||||
|
||||
saveTask(): void {
|
||||
if (this.taskForm.invalid) {
|
||||
this.toastr.error('Por favor, rellene todos los campos obligatorios');
|
||||
this.toastr.error(this.translate.instant('pleaseFillOutAll'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -254,12 +257,12 @@ export class CreateTaskComponent implements OnInit {
|
|||
const scope = formData.scope;
|
||||
|
||||
if (scope === 'clients' && this.selectedClients.length === 0) {
|
||||
this.toastr.error('Debe seleccionar al menos un cliente');
|
||||
this.toastr.error(this.translate.instant('youMustSelectAtLeastOne'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (scope !== 'clients' && !formData.organizationalUnit) {
|
||||
this.toastr.error('Debe seleccionar una unidad organizativa');
|
||||
this.toastr.error(this.translate.instant('youMustSelectAnOrganizationalUnit'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -280,17 +283,17 @@ export class CreateTaskComponent implements OnInit {
|
|||
const taskId = this.data.task.uuid;
|
||||
this.http.patch<any>(`${this.apiUrl}/${taskId}`, payload).subscribe({
|
||||
next: () => {
|
||||
this.toastr.success('Tarea actualizada con éxito');
|
||||
this.toastr.success(this.translate.instant('taskUpdatedSuccessfully'));
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
error: () => {
|
||||
this.toastr.error('Error al actualizar la tarea');
|
||||
this.toastr.error(this.translate.instant('errorUpdatingTask'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.http.post<any>(this.apiUrl, payload).subscribe({
|
||||
next: response => {
|
||||
this.toastr.success('Tarea creada con éxito');
|
||||
this.toastr.success(this.translate.instant('taskCreatedSuccessfully'));
|
||||
this.dialogRef.close(response);
|
||||
if (this.data?.source === 'assistant') {
|
||||
if (formData.scheduleAfterCreate) {
|
||||
|
|
@ -301,7 +304,7 @@ export class CreateTaskComponent implements OnInit {
|
|||
}
|
||||
},
|
||||
error: () => {
|
||||
this.toastr.error('Error al crear la tarea');
|
||||
this.toastr.error(this.translate.instant('errorCreatingTask'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -313,7 +316,7 @@ export class CreateTaskComponent implements OnInit {
|
|||
data: { task: task }
|
||||
}).afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.toastr.success('Tarea programada correctamente');
|
||||
this.toastr.success(this.translate.instant('successfullyScheduledTask'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -334,7 +337,7 @@ export class CreateTaskComponent implements OnInit {
|
|||
data: { task: task }
|
||||
}).afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.toastr.success('Tarea programada correctamente');
|
||||
this.toastr.success(this.translate.instant('successfullyScheduledTask'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,27 +9,26 @@
|
|||
<mat-card-content>
|
||||
<p><strong>{{ 'taskId' | translate }}:</strong> {{ task.uuid }}</p>
|
||||
<p><strong>{{ 'creationDate' | translate }}:</strong> {{ task.createdAt | date: 'short' }}</p>
|
||||
<p><strong>{{ 'notes' | translate }}:</strong> {{ task.notes }}</p>
|
||||
<p><strong>{{ 'labelNotes' | translate }}:</strong> {{ task.notes }}</p>
|
||||
|
||||
<h3>{{ 'commandsToExecute' | translate }}</h3>
|
||||
<table mat-table [dataSource]="task.commandTaskScripts || []" class="mat-elevation-z8" *ngIf="task.commandTaskScripts?.length > 0">
|
||||
<ng-container matColumnDef="order">
|
||||
<th mat-header-cell *matHeaderCellDef>Orden</th>
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labelOrder' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let script">{{ script.order }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef>Tipo</th>
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labelType' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let script">{{ script.type }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="content">
|
||||
<th mat-header-cell *matHeaderCellDef>Contenido</th>
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labelContent' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let script">
|
||||
<span *ngIf="script.content">{{ script.content }}</span>
|
||||
<span *ngIf="!script.content && script.parameters">
|
||||
(parámetros configurados)
|
||||
</span>
|
||||
{{ 'configuredParameters' | translate }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
|
@ -37,7 +36,7 @@
|
|||
<tr mat-row *matRowDef="let row; columns: ['order', 'type', 'content'];"></tr>
|
||||
</table>
|
||||
|
||||
<p *ngIf="!task.commandTaskScripts?.length">No hay acciones configuradas.</p>
|
||||
<p *ngIf="!task.commandTaskScripts?.length">{{ 'noActionsConfigured' | translate }}</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchScheduleLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelSearchSchedule' | translate }}</mat-label>
|
||||
<input matInput [placeholder]="'searchPlaceholder' | translate" [(ngModel)]="filters['name']"
|
||||
(keyup.enter)="loadData()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
<table *ngIf="!loading" 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>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header | translate }} </th>
|
||||
<td mat-cell *matCellDef="let schedule">
|
||||
|
||||
<ng-container *ngIf="column.columnDef === 'recurrenceType'">
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let schedule" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="editSchedule(schedule)" [matTooltip]="'editScheduleTooltip' | translate">
|
||||
<mat-icon>edit</mat-icon>
|
||||
|
|
@ -120,5 +120,5 @@
|
|||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'close' | translate }}</button>
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonClose' | translate }}</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {Component, Inject, OnInit } from '@angular/core';
|
||||
import {MatTableDataSource} from "@angular/material/table";
|
||||
import {Client} from "../../../groups/model/model";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
|
@ -15,6 +17,7 @@ import {DatePipe} from "@angular/common";
|
|||
styleUrl: './show-task-schedule.component.css'
|
||||
})
|
||||
export class ShowTaskScheduleComponent implements OnInit{
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
dataSource = new MatTableDataSource<any>([]);
|
||||
length = 0;
|
||||
|
|
@ -26,13 +29,13 @@ export class ShowTaskScheduleComponent implements OnInit{
|
|||
datePipe: DatePipe = new DatePipe('es-ES');
|
||||
|
||||
columns = [
|
||||
{ columnDef: 'id', header: 'ID', cell: (schedule: any) => schedule.id },
|
||||
{ columnDef: 'recurrenceType', header: 'Tipo de Programación', cell: (schedule: any) => this.getRecurrenceTypeDisplay(schedule) },
|
||||
{ columnDef: 'executionTime', header: 'Hora de ejecución', cell: (schedule: any) => this.formatExecutionTimeForDisplay(schedule.executionTime) },
|
||||
{ columnDef: 'nextExecution', header: 'Próxima ejecución', cell: (schedule: any) => this.calculateNextExecution(schedule) },
|
||||
{ columnDef: 'daysOfWeek', header: 'Días de la semana', cell: (schedule: any) => this.formatDaysOfWeek(schedule.recurrenceDetails?.daysOfWeek) },
|
||||
{ columnDef: 'months', header: 'Meses', cell: (schedule: any) => this.formatMonths(schedule.recurrenceDetails?.months) },
|
||||
{ columnDef: 'enabled', header: 'Estado', cell: (schedule: any) => schedule.enabled }
|
||||
{ columnDef: 'id', header: 'labelId', cell: (schedule: any) => schedule.id },
|
||||
{ columnDef: 'recurrenceType', header: 'typeOfProgramming', cell: (schedule: any) => this.getRecurrenceTypeDisplay(schedule) },
|
||||
{ columnDef: 'executionTime', header: 'executionTime', cell: (schedule: any) => this.formatExecutionTimeForDisplay(schedule.executionTime) },
|
||||
{ columnDef: 'nextExecution', header: 'nextExecution', cell: (schedule: any) => this.calculateNextExecution(schedule) },
|
||||
{ columnDef: 'daysOfWeek', header: 'daysOfTheWeek', cell: (schedule: any) => this.formatDaysOfWeek(schedule.recurrenceDetails?.daysOfWeek) },
|
||||
{ columnDef: 'months', header: 'labelMonths', cell: (schedule: any) => this.formatMonths(schedule.recurrenceDetails?.months) },
|
||||
{ columnDef: 'enabled', header: 'state', cell: (schedule: any) => schedule.enabled }
|
||||
];
|
||||
|
||||
displayedColumns: string[] = ['id', 'recurrenceType', 'executionTime', 'nextExecution', 'daysOfWeek', 'months', 'enabled', 'actions'];
|
||||
|
|
@ -67,7 +70,7 @@ export class ShowTaskScheduleComponent implements OnInit{
|
|||
},
|
||||
(error) => {
|
||||
this.loading = false;
|
||||
this.toastService.error('Error al cargar las programaciones');
|
||||
this.toastService.error(this.translate.instant('errorLoadingProgramming'));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -79,7 +82,7 @@ export class ShowTaskScheduleComponent implements OnInit{
|
|||
}).afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.loadData();
|
||||
this.toastService.success('Programación creada correctamente');
|
||||
this.toastService.success(this.translate.instant('programmingCreatedCorrectly'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -91,7 +94,7 @@ export class ShowTaskScheduleComponent implements OnInit{
|
|||
}).afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.loadData();
|
||||
this.toastService.success('Programación actualizada correctamente');
|
||||
this.toastService.success(this.translate.instant('programmingUpdatedCorrectly'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -106,7 +109,7 @@ export class ShowTaskScheduleComponent implements OnInit{
|
|||
if (result) {
|
||||
this.http.delete(`${this.baseUrl}${schedule['@id']}`).subscribe(
|
||||
() => {
|
||||
this.toastService.success('Programación eliminada correctamente');
|
||||
this.toastService.success(this.translate.instant('programmingRemovedCorrectly'));
|
||||
this.loadData();
|
||||
},
|
||||
(error) => {
|
||||
|
|
@ -317,7 +320,7 @@ export class ShowTaskScheduleComponent implements OnInit{
|
|||
if (schedule.recurrenceType === 'none') {
|
||||
return {
|
||||
type: 'none',
|
||||
label: 'Ejecución única',
|
||||
label : this.translate.instant('uniqueExecution'),
|
||||
icon: 'event',
|
||||
color: 'primary',
|
||||
details: schedule.executionDate ?
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="content">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'scriptLabel' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labelScript' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let item">
|
||||
<div *ngIf="item.content" class="script-content-cell">
|
||||
<code>{{ item.content }}</code>
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let item" style="text-align: center;">
|
||||
<button mat-icon-button color="warn" (click)="deleteTaskScript(item)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
|
|
@ -92,5 +92,5 @@
|
|||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'close' | translate }}</button>
|
||||
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonClose' | translate }}</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import {MatTableDataSource} from "@angular/material/table";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog";
|
||||
import {ConfigService} from "@services/config.service";
|
||||
import {CreateTaskScheduleComponent} from "../create-task-schedule/create-task-schedule.component";
|
||||
import {CreateTaskScriptComponent} from "../create-task-script/create-task-script.component";
|
||||
import {DeleteModalComponent} from "../../../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import {ViewParametersModalComponent} from "./view-parameters-modal/view-parameters-modal.component";
|
||||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MatTableDataSource } from "@angular/material/table";
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from "@angular/material/dialog";
|
||||
import { ConfigService } from "@services/config.service";
|
||||
import { CreateTaskScheduleComponent } from "../create-task-schedule/create-task-schedule.component";
|
||||
import { CreateTaskScriptComponent } from "../create-task-script/create-task-script.component";
|
||||
import { DeleteModalComponent } from "../../../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import { ViewParametersModalComponent } from "./view-parameters-modal/view-parameters-modal.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-task-script',
|
||||
templateUrl: './show-task-script.component.html',
|
||||
styleUrl: './show-task-script.component.css'
|
||||
})
|
||||
export class ShowTaskScriptComponent implements OnInit{
|
||||
export class ShowTaskScriptComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
dataSource = new MatTableDataSource<any>([]);
|
||||
length = 0;
|
||||
|
|
@ -25,26 +28,26 @@ export class ShowTaskScriptComponent implements OnInit{
|
|||
filters: { [key: string]: string } = {};
|
||||
|
||||
columns = [
|
||||
{ columnDef: 'id', header: 'ID', cell: (item: any) => item.id },
|
||||
{ columnDef: 'order', header: 'Orden', cell: (item: any) => item.order },
|
||||
{ columnDef: 'type', header: 'Tipo', cell: (item: any) => item.type },
|
||||
{ columnDef: 'parameters', header: 'Parámetros', cell: (item: any) => item.parameters },
|
||||
{ columnDef: 'content', header: 'Contenido', cell: (item: any) => item.content },
|
||||
{ columnDef: 'id', header: 'labelId', cell: (item: any) => item.id },
|
||||
{ columnDef: 'order', header: 'labelOrder', cell: (item: any) => item.order },
|
||||
{ columnDef: 'type', header: 'labelType', cell: (item: any) => item.type },
|
||||
{ columnDef: 'parameters', header: 'labelParameters', cell: (item: any) => item.parameters },
|
||||
{ columnDef: 'content', header: 'labelContent', cell: (item: any) => item.content },
|
||||
];
|
||||
|
||||
displayedColumns: string[] = ['order', 'type', 'content', 'parameters', 'actions'];
|
||||
|
||||
typeLabels: { [key: string]: { label: string, icon: string } } = {
|
||||
'partition-and-format': { label: 'Particionar y formatear', icon: 'storage' },
|
||||
'partition-assistant': { label: 'Asistente de particionado', icon: 'storage' },
|
||||
'deploy-image': { label: 'Desplegar imagen', icon: 'cloud_download' },
|
||||
'create-image': { label: 'Crear imagen', icon: 'cloud_upload' },
|
||||
'run-script': { label: 'Ejecutar script', icon: 'code' },
|
||||
'partition-and-format': { label: this.translate.instant('partitionAndFormat'), icon: 'storage' },
|
||||
'partition-assistant': { label: this.translate.instant('partitionTitle'), icon: 'storage' },
|
||||
'deploy-image': { label: this.translate.instant('displayImage'), icon: 'cloud_download' },
|
||||
'create-image': { label: this.translate.instant('createImage'), icon: 'cloud_upload' },
|
||||
'run-script': { label: this.translate.instant('runScript'), icon: 'code' },
|
||||
'reboot': { label: 'Reiniciar', icon: 'restart_alt' },
|
||||
'shutdown': { label: 'Apagar', icon: 'power_off' },
|
||||
'poweron': { label: 'Encender', icon: 'power' },
|
||||
'power-on': { label: 'Encender', icon: 'power' },
|
||||
'login': { label: 'Iniciar sesión', icon: 'login' },
|
||||
'login': { label: this.translate.instant('login'), icon: 'login' },
|
||||
};
|
||||
|
||||
getTypeDisplay(type: string): { label: string, icon: string } {
|
||||
|
|
@ -94,7 +97,7 @@ export class ShowTaskScriptComponent implements OnInit{
|
|||
if (result) {
|
||||
this.http.delete(`${this.baseUrl}${schedule['@id']}`).subscribe(
|
||||
() => {
|
||||
this.toastService.success('Eliminado correctamente');
|
||||
this.toastService.success(this.translate.instant('removedSuccessfully'));
|
||||
this.loadData();
|
||||
},
|
||||
(error) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<h2 mat-dialog-title>Parámetros</h2>
|
||||
<h2 mat-dialog-title>{{ 'labelParameters' | translate }}</h2>
|
||||
<mat-dialog-content>
|
||||
<pre>{{ data | json }}</pre>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button (click)="close()">Cerrar</button>
|
||||
<button mat-button (click)="close()">{{ 'buttonClose' | translate }}</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
|
||||
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchCommandLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()" />
|
||||
<mat-label>{{ 'labelSearchCommand' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'labelSearch' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()" />
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
<div *ngIf="!loading">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableStepText' | translate }}">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header | translate }} </th>
|
||||
<td mat-cell *matCellDef="let command">
|
||||
<ng-container *ngIf="column.columnDef !== 'readOnly'">
|
||||
{{ column.cell(command) }}
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let command" style="text-align: center;" joyrideStep="actionsStep" text="{{ 'actionsStepText' | translate }}">
|
||||
<button mat-icon-button color="info" (click)="viewDetails($event, command)"><mat-icon>visibility</mat-icon></button>
|
||||
<button mat-icon-button color="primary" [disabled]="command.readOnly" (click)="editCommand($event, command)"><mat-icon>edit</mat-icon></button>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
|
@ -16,6 +18,7 @@ import { JoyrideService } from 'ngx-joyride';
|
|||
styleUrls: ['./commands.component.css']
|
||||
})
|
||||
export class CommandsComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
private apiUrl: string;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
|
|
@ -29,22 +32,22 @@ export class CommandsComponent implements OnInit {
|
|||
columns = [
|
||||
{
|
||||
columnDef: 'id',
|
||||
header: 'ID',
|
||||
header: 'labelId',
|
||||
cell: (command: any) => `${command.id}`,
|
||||
},
|
||||
{
|
||||
columnDef: 'name',
|
||||
header: 'Nombre',
|
||||
header: 'labelName',
|
||||
cell: (command: any) => `${command.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'readOnly',
|
||||
header: 'Solo Lectura',
|
||||
header: 'readOnly',
|
||||
cell: (command: any) => `${command.readOnly}`
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
header: 'Fecha de creación',
|
||||
header: 'creationDate',
|
||||
cell: (command: any) => `${this.datePipe.transform(command.createdAt, 'dd/MM/yyyy hh:mm:ss')}`,
|
||||
}
|
||||
];
|
||||
|
|
@ -69,7 +72,7 @@ export class CommandsComponent implements OnInit {
|
|||
this.loading = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching commands', error);
|
||||
console.error(this.translate.instant('fetchingCommandsError'), error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -106,11 +109,11 @@ export class CommandsComponent implements OnInit {
|
|||
if (result) {
|
||||
this.http.delete(`${this.apiUrl}/${command.uuid}`).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Comando eliminado con éxito');
|
||||
this.toastService.success(this.translate.instant('commandRemovedSuccessfully'));
|
||||
this.search();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al eliminar el comando:', error);
|
||||
console.error(this.translate.instant('errorDeletingCommand'), error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,28 +2,28 @@
|
|||
<mat-dialog-content class="form-container">
|
||||
<form [formGroup]="createCommandForm" (ngSubmit)="onSubmit()" class="command-form">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'nameLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelRoleName' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" placeholder="{{ 'commandNamePlaceholder' | translate }}" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'scriptLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelScript' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="script" placeholder="{{ 'commandScriptPlaceholder' | translate }}"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<mat-checkbox formControlName="readOnly">{{ 'readOnlyLabel' | translate }}</mat-checkbox>
|
||||
<mat-checkbox formControlName="readOnly">{{ 'labelReadOnly' | translate }}</mat-checkbox>
|
||||
|
||||
<mat-checkbox formControlName="enabled">{{ 'enabledLabel' | translate }}</mat-checkbox>
|
||||
<mat-checkbox formControlName="enabled">{{ 'labelEnabled' | translate }}</mat-checkbox>
|
||||
|
||||
<div class="checkbox-with-hint">
|
||||
<mat-checkbox formControlName="parameters">{{ 'parameters' | translate }}</mat-checkbox>
|
||||
<span class="hint-text">Si se selecciona esta opción los parámetros deben indicarse en el script con el símbolo @.</span>
|
||||
<mat-checkbox formControlName="parameters">{{ 'labelParameters' | translate }}</mat-checkbox>
|
||||
<span class="hint-text">{{ 'scriptParameterHint' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ 'commentsLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelComments' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="comments" placeholder="{{ 'commentsPlaceholder' | translate }}"></textarea>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
|
@ -31,5 +31,5 @@
|
|||
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="onCancel($event)">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSubmit()" cdkFocusInitial>{{ 'buttonSave' | translate }}</button>
|
||||
<button class="submit-button" (click)="onSubmit()" cdkFocusInitial>{{ 'labelSaveButton' | translate }}</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {Component, Inject, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
|
@ -12,6 +14,7 @@ import { ConfigService } from "@services/config.service";
|
|||
styleUrls: ['./create-command.component.css']
|
||||
})
|
||||
export class CreateCommandComponent implements OnInit{
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
createCommandForm: FormGroup<any>;
|
||||
commandId: string | null = null;
|
||||
|
|
@ -56,7 +59,7 @@ export class CreateCommandComponent implements OnInit{
|
|||
this.commandId = response['@id'];
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error fetching remote calendar:', err);
|
||||
console.error(this.translate.instant('errorFetchingRemoteCalendar'), err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -81,7 +84,7 @@ export class CreateCommandComponent implements OnInit{
|
|||
if (this.commandId) {
|
||||
this.http.put(`${this.baseUrl}${this.commandId}`, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Comando editado correctamente');
|
||||
this.toastService.success(this.translate.instant('commandEditedSuccessfully'));
|
||||
this.dialogRef.close();
|
||||
},
|
||||
(error) => {
|
||||
|
|
@ -91,7 +94,7 @@ export class CreateCommandComponent implements OnInit{
|
|||
} else {
|
||||
this.http.post(`${this.baseUrl}/commands`, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Comando añadido correctamente');
|
||||
this.toastService.success(this.translate.instant('commandAddedSuccessfully'));
|
||||
this.dialogRef.close();
|
||||
},
|
||||
(error) => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
|
|
@ -8,6 +10,7 @@ import { catchError, map } from 'rxjs/operators';
|
|||
providedIn: 'root'
|
||||
})
|
||||
export class DataService {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
private apiUrl: string;
|
||||
|
||||
|
|
@ -34,7 +37,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching commands', error);
|
||||
console.error(this.translate.instant('fetchingCommandsError'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -50,7 +53,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching user group', error);
|
||||
console.error(this.translate.instant('errorFetchingUserGroup'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
<div class="modal-container">
|
||||
<h3 class="modal-title">{{ 'commandDetailsTitle' | translate }}</h3>
|
||||
<div class="modal-content">
|
||||
<p><strong>{{ 'nameLabel' | translate }}:</strong> {{ data.name }}</p>
|
||||
<p><strong>{{ 'commentsLabel' | translate }}:</strong> {{ data.comments }}</p>
|
||||
<p><strong>{{ 'createdByLabel' | translate }}:</strong> {{ data.createdBy }}</p>
|
||||
<p><strong>{{ 'creationDateLabel' | translate }}:</strong> {{ data.createdAt | date:'medium' }}</p>
|
||||
<p><strong>{{ 'labelRoleName' | translate }}:</strong> {{ data.name }}</p>
|
||||
<p><strong>{{ 'labelComments' | translate }}:</strong> {{ data.comments }}</p>
|
||||
<p><strong>{{ 'labelCreatedBy' | translate }}:</strong> {{ data.createdBy }}</p>
|
||||
<p><strong>{{ 'labelCreationDate' | translate }}:</strong> {{ data.createdAt | date:'medium' }}</p>
|
||||
|
||||
<div class="script-display">
|
||||
<h4>{{ 'scriptLabel' | translate }}:</h4>
|
||||
<h4>{{ 'labelScript' | translate }}:</h4>
|
||||
<pre>{{ data.script }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="additional-section" *ngIf="showClientSelect">
|
||||
<form [formGroup]="form">
|
||||
<h4>{{ 'selectClientsTitle' | translate }}</h4>
|
||||
<h4>{{ 'selectClients' | translate }}</h4>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>{{ 'clientsLabel' | translate }}</mat-label>
|
||||
<mat-label>{{ 'labelClients' | translate }}</mat-label>
|
||||
<mat-select formControlName="selectedClients" multiple (selectionChange)="onClientSelectionChange($event)">
|
||||
<mat-option *ngFor="let client of clients" [value]="client.uuid">
|
||||
{{ client.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="form.get('selectedClients')?.invalid && form.get('selectedClients')?.touched">
|
||||
{{ 'selectAtLeastOneClientError' | translate }}
|
||||
{{ 'selectAtLeastOneClient' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
|
|
@ -12,6 +14,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrls: ['./command-detail.component.css']
|
||||
})
|
||||
export class CommandDetailComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
form!: FormGroup;
|
||||
clients: any[] = [];
|
||||
|
|
@ -56,7 +59,7 @@ export class CommandDetailComponent implements OnInit {
|
|||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.toastService.success('Comando editado');
|
||||
this.toastService.success(this.translate.instant('editedCommand'));
|
||||
this.data.command = result;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<h2 mat-dialog-title> Seleccionar particion para arrancar SO</h2>
|
||||
<h2 mat-dialog-title> {{ 'selectPartitionToBootOs' | translate }}</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
|
|
@ -6,10 +6,9 @@
|
|||
<div *ngIf="!loading" class="select-container">
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> Clientes </mat-panel-title>
|
||||
<mat-panel-title> {{ 'labelClients' | translate }}</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
Listado de clientes para arrancar un SO
|
||||
<mat-icon>desktop_windows</mat-icon>
|
||||
{{ 'clientListBootOs' | translate }}<mat-icon>desktop_windows</mat-icon>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
|
|
@ -42,8 +41,7 @@
|
|||
color="primary"
|
||||
[disabled]="!client.selected"
|
||||
(click)="$event.stopPropagation()">
|
||||
Modelo
|
||||
</mat-radio-button>
|
||||
{{ 'labelModel' | translate }}</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -57,7 +55,7 @@
|
|||
<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 partición</th>
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: start">{{ 'buttonSelectPartition' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-radio-group
|
||||
[(ngModel)]="selectedPartition"
|
||||
|
|
@ -70,7 +68,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header | translate }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef !== 'size'">
|
||||
{{ column.cell(image) }}
|
||||
|
|
@ -95,6 +93,6 @@
|
|||
</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>
|
||||
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="execute()" [disabled]="!selectedPartition">{{ 'buttonExecute' | translate }}</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
|
||||
import { MatTableDataSource } from "@angular/material/table";
|
||||
|
|
@ -11,6 +13,7 @@ import { ToastrService } from "ngx-toastr";
|
|||
styleUrl: './boot-so-partition.component.css'
|
||||
})
|
||||
export class BootSoPartitionComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
selectedPartition: any = null;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
|
|
@ -24,32 +27,32 @@ export class BootSoPartitionComponent implements OnInit {
|
|||
columns = [
|
||||
{
|
||||
columnDef: 'diskNumber',
|
||||
header: 'Disco',
|
||||
header: 'labelDisk',
|
||||
cell: (partition: any) => partition.diskNumber
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionNumber',
|
||||
header: 'Particion',
|
||||
header: 'partition',
|
||||
cell: (partition: any) => partition.partitionNumber
|
||||
},
|
||||
{
|
||||
columnDef: 'size',
|
||||
header: 'Tamaño',
|
||||
header: 'labelDiskSize',
|
||||
cell: (partition: any) => `${partition.size} MB`
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionCode',
|
||||
header: 'Tipo de partición',
|
||||
header: 'partitionType',
|
||||
cell: (partition: any) => partition.partitionCode
|
||||
},
|
||||
{
|
||||
columnDef: 'filesystem',
|
||||
header: 'Sistema de ficheros',
|
||||
header: 'fileSystem',
|
||||
cell: (partition: any) => partition.filesystem
|
||||
},
|
||||
{
|
||||
columnDef: 'operativeSystem',
|
||||
header: 'SO',
|
||||
header: 'sW',
|
||||
cell: (partition: any) => partition.operativeSystem?.name
|
||||
}
|
||||
];
|
||||
|
|
@ -92,7 +95,7 @@ export class BootSoPartitionComponent implements OnInit {
|
|||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar los datos del cliente:', error);
|
||||
console.error(this.translate.instant('errorLoadingData1'), error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -133,7 +136,7 @@ export class BootSoPartitionComponent implements OnInit {
|
|||
partition: this.selectedPartition['@id']
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Cliente actualizado correctamente');
|
||||
this.toastService.success(this.translate.instant('clientUpdatedSuccessfully'));
|
||||
this.dialogRef.close();
|
||||
this.loading = false;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Router } from "@angular/router";
|
||||
|
|
@ -15,6 +17,7 @@ import {SoftwareProfilePartitionComponent} from "./software-profile-partition/so
|
|||
styleUrls: ['./execute-command.component.css']
|
||||
})
|
||||
export class ExecuteCommandComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
@Input() runScriptContext: any = null;
|
||||
@Input() clientState: string = 'off';
|
||||
@Input() clientData: any[] = [];
|
||||
|
|
@ -197,7 +200,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
clients: this.clientData.map((client: any) => client['@id'])
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Cliente actualizado correctamente');
|
||||
this.toastService.success(this.translate.instant('clientUpdatedSuccessfully'));
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error de conexión con el cliente');
|
||||
|
|
@ -210,7 +213,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
clients: this.clientData.map((client: any) => client['@id'])
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Petición de refresco enviada correctamente');
|
||||
this.toastService.success(this.translate.instant('refreshmentRequestSentCorrectly'));
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error de conexión con el cliente');
|
||||
|
|
@ -237,7 +240,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.toastService.success('Petición de arranque de SO enviada correctamente');
|
||||
this.toastService.success(this.translate.instant('oSBootRequest'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -261,14 +264,14 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.toastService.success('Petición de borrado de caché de imagen enviada correctamente');
|
||||
this.toastService.success(this.translate.instant('cacheClearRequest'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hardwareInventory(): void {
|
||||
if (this.clientData.length === 0) {
|
||||
this.toastService.error('No hay clientes seleccionados');
|
||||
this.toastService.error(this.translate.instant('noClientsSelected'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -278,7 +281,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
clients: this.clientData.map((client: any) => client['@id'])
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Inventario de hardware actualizado correctamente');
|
||||
this.toastService.success(this.translate.instant('hardwareInventorySuccessfullyUpdated'));
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error de conexión con el cliente');
|
||||
|
|
@ -288,7 +291,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
|
||||
softwareInventory(): void {
|
||||
if (this.clientData.length === 0) {
|
||||
this.toastService.error('No hay clientes seleccionados');
|
||||
this.toastService.error(this.translate.instant('noClientsSelected'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -320,7 +323,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
clients: this.clientData.map((client: any) => client['@id'])
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Petición de encendido enviada correctamente');
|
||||
this.toastService.success(this.translate.instant('ignitionRequestSentCorrectly'));
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error de conexión con el cliente');
|
||||
|
|
@ -333,7 +336,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
clients: this.clientData.map((client: any) => client['@id'])
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Petición de apagado enviada correctamente');
|
||||
this.toastService.success(this.translate.instant('shutdownRequestSentCorrectly'));
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error de conexión con el cliente');
|
||||
|
|
@ -404,7 +407,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
|
||||
deleteClientLease(): void {
|
||||
if (this.clientData.length === 0) {
|
||||
this.toastService.error('No hay clientes seleccionados');
|
||||
this.toastService.error(this.translate.instant('noClientsSelected'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -415,11 +418,11 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
Promise.allSettled(requests).then(results => {
|
||||
const errors = results.filter(r => r.status === 'rejected');
|
||||
if (errors.length === 0) {
|
||||
this.toastService.success('Lease(s) DHCP eliminada(s) correctamente');
|
||||
this.toastService.success(this.translate.instant('dhcpLeasesRemovedSuccessfully'));
|
||||
} else if (errors.length < results.length) {
|
||||
this.toastService.warning(`${results.length - errors.length} lease(s) eliminada(s), ${errors.length} con error`);
|
||||
} else {
|
||||
this.toastService.error('Error al eliminar las leases DHCP');
|
||||
this.toastService.error(this.translate.instant('errorWhenDeletingLeases'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<h2 mat-dialog-title> Seleccionar imagen para eliminar de la cache</h2>
|
||||
<h2 mat-dialog-title> {{ 'selectImageToDeleteFromCache' | translate }}</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
|
|
@ -6,10 +6,9 @@
|
|||
<div *ngIf="!loading" class="select-container">
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> Clientes </mat-panel-title>
|
||||
<mat-panel-title> {{ 'labelClients' | translate }}</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
Listado de clientes para arrancar un SO
|
||||
<mat-icon>desktop_windows</mat-icon>
|
||||
{{ 'clientListBootOs' | translate }}<mat-icon>desktop_windows</mat-icon>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
|
|
@ -42,8 +41,7 @@
|
|||
color="primary"
|
||||
[disabled]="!client.selected"
|
||||
(click)="$event.stopPropagation()">
|
||||
Modelo
|
||||
</mat-radio-button>
|
||||
{{ 'labelModel' | translate }}</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -57,7 +55,7 @@
|
|||
<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>
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: start">{{ 'buttonSelectImage' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-radio-group
|
||||
[(ngModel)]="selectedPartition"
|
||||
|
|
@ -70,7 +68,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header | translate }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef !== 'size' && column.columnDef !== 'operativeSystem'">
|
||||
{{ column.cell(image) }}
|
||||
|
|
@ -100,6 +98,6 @@
|
|||
</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>
|
||||
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="execute()" [disabled]="!selectedPartition">{{ 'buttonExecute' | translate }}</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {Component, Inject} from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {Component, Inject } from '@angular/core';
|
||||
import {MatTableDataSource} from "@angular/material/table";
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {ConfigService} from "@services/config.service";
|
||||
|
|
@ -11,6 +13,7 @@ import {ToastrService} from "ngx-toastr";
|
|||
styleUrl: './remove-cache-image.component.css'
|
||||
})
|
||||
export class RemoveCacheImageComponent {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
selectedPartition: any = null;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
|
|
@ -24,32 +27,32 @@ export class RemoveCacheImageComponent {
|
|||
columns = [
|
||||
{
|
||||
columnDef: 'diskNumber',
|
||||
header: 'Disco',
|
||||
header: 'labelDisk',
|
||||
cell: (partition: any) => partition.diskNumber
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionNumber',
|
||||
header: 'Particion',
|
||||
header: 'partition',
|
||||
cell: (partition: any) => partition.partitionNumber
|
||||
},
|
||||
{
|
||||
columnDef: 'size',
|
||||
header: 'Tamaño',
|
||||
header: 'labelDiskSize',
|
||||
cell: (partition: any) => `${partition.size} MB`
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionCode',
|
||||
header: 'Tipo de partición',
|
||||
header: 'partitionType',
|
||||
cell: (partition: any) => partition.partitionCode
|
||||
},
|
||||
{
|
||||
columnDef: 'filesystem',
|
||||
header: 'Sistema de ficheros',
|
||||
header: 'fileSystem',
|
||||
cell: (partition: any) => partition.filesystem
|
||||
},
|
||||
{
|
||||
columnDef: 'operativeSystem',
|
||||
header: 'SO',
|
||||
header: 'sW',
|
||||
cell: (partition: any) => partition.operativeSystem?.name
|
||||
}
|
||||
];
|
||||
|
|
@ -100,7 +103,7 @@ export class RemoveCacheImageComponent {
|
|||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar los datos del cliente:', error);
|
||||
console.error(this.translate.instant('errorLoadingData1'), error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -141,7 +144,7 @@ export class RemoveCacheImageComponent {
|
|||
partition: this.selectedPartition['@id']
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Cliente actualizado correctamente');
|
||||
this.toastService.success(this.translate.instant('clientUpdatedSuccessfully'));
|
||||
this.dialogRef.close();
|
||||
this.loading = false;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<h2 mat-dialog-title> Seleccionar partición para inventariar</h2>
|
||||
<h2 mat-dialog-title> {{ 'selectPartitionToInventory' | translate }}</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
<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>
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: start">{{ 'buttonSelectImage' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-radio-group
|
||||
[(ngModel)]="selectedPartition"
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header | translate }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef !== 'size' && column.columnDef !== 'operativeSystem'">
|
||||
{{ column.cell(image) }}
|
||||
|
|
@ -50,6 +50,6 @@
|
|||
</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>
|
||||
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="execute()" [disabled]="!selectedPartition">{{ 'buttonExecute' | translate }}</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
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";
|
||||
|
|
@ -11,6 +13,7 @@ import {ToastrService} from "ngx-toastr";
|
|||
styleUrl: './software-profile-partition.component.css'
|
||||
})
|
||||
export class SoftwareProfilePartitionComponent implements OnInit{
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
selectedPartition: any = null;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
|
|
@ -24,32 +27,32 @@ export class SoftwareProfilePartitionComponent implements OnInit{
|
|||
columns = [
|
||||
{
|
||||
columnDef: 'diskNumber',
|
||||
header: 'Disco',
|
||||
header: 'labelDisk',
|
||||
cell: (partition: any) => partition.diskNumber
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionNumber',
|
||||
header: 'Particion',
|
||||
header: 'partition',
|
||||
cell: (partition: any) => partition.partitionNumber
|
||||
},
|
||||
{
|
||||
columnDef: 'size',
|
||||
header: 'Tamaño',
|
||||
header: 'labelDiskSize',
|
||||
cell: (partition: any) => `${partition.size} MB`
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionCode',
|
||||
header: 'Tipo de partición',
|
||||
header: 'partitionType',
|
||||
cell: (partition: any) => partition.partitionCode
|
||||
},
|
||||
{
|
||||
columnDef: 'filesystem',
|
||||
header: 'Sistema de ficheros',
|
||||
header: 'fileSystem',
|
||||
cell: (partition: any) => partition.filesystem
|
||||
},
|
||||
{
|
||||
columnDef: 'operativeSystem',
|
||||
header: 'SO',
|
||||
header: 'sW',
|
||||
cell: (partition: any) => partition.operativeSystem?.name
|
||||
}
|
||||
];
|
||||
|
|
@ -80,7 +83,7 @@ export class SoftwareProfilePartitionComponent implements OnInit{
|
|||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar los datos del cliente:', error);
|
||||
console.error(this.translate.instant('errorLoadingData1'), error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -96,7 +99,7 @@ export class SoftwareProfilePartitionComponent implements OnInit{
|
|||
partition: this.selectedPartition['@id'],
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Inventario de software actualizado correctamente');
|
||||
this.toastService.success(this.translate.instant('successfullyUpdatedSoftwareInventory'));
|
||||
this.dialogRef.close(response);
|
||||
},
|
||||
error => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* ===== HEADER DE BIENVENIDA ===== */
|
||||
.welcome-header {
|
||||
background: #3f51b5;
|
||||
background: linear-gradient(120deg, #3b3080 0%, #2d2b55 50%, #1e1e2f 100%);
|
||||
color: white;
|
||||
padding: 1.2rem 1.5rem;
|
||||
display: flex;
|
||||
|
|
@ -468,7 +468,7 @@ mat-dialog-content {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem 2rem;
|
||||
padding: 0.6rem 1.25rem;
|
||||
background: white;
|
||||
border-top: 1px solid #e9ecef;
|
||||
border-radius: 0 0 12px 12px;
|
||||
|
|
|
|||
|
|
@ -5,26 +5,12 @@
|
|||
</div>
|
||||
<div class="welcome-text">
|
||||
<h1 class="welcome-title">{{ 'GlobalStatus' | translate }}</h1>
|
||||
<p class="welcome-subtitle">Bienvenido a la consola de administración de OpenGnsys</p>
|
||||
<p class="welcome-subtitle">{{ 'welcomeTitle' | translate }}</p>
|
||||
<p class="welcome-description">{{ 'generalPlatformStatus' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="welcome-actions">
|
||||
@if (releaseInfo) {
|
||||
<div class="release-panel">
|
||||
@if (releaseChannel) {
|
||||
<span class="release-channel">{{ releaseChannel }}</span>
|
||||
}
|
||||
<div class="release-chips">
|
||||
@for (pkg of releasePackages; track pkg.key) {
|
||||
<span class="release-chip" [matTooltip]="pkg.value">
|
||||
<span class="chip-name">{{ pkg.key }}</span>
|
||||
<span class="chip-version">{{ pkg.value.split('~')[0] }}</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<button mat-icon-button class="refresh-button" (click)="refreshAll()" matTooltip="Actualizar datos">
|
||||
<button mat-icon-button class="refresh-button" (click)="refreshAll()" [matTooltip]="'buttonUpdateData' | translate">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -35,12 +21,12 @@
|
|||
<div class="spinner-container">
|
||||
<div class="loading-content">
|
||||
<mat-spinner class="loading-spinner"></mat-spinner>
|
||||
<p class="loading-text">Cargando estado del sistema...</p>
|
||||
<p class="loading-text">{{ 'loadingSystemStatus' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(!loading){
|
||||
@if(!loading) {
|
||||
<div class="main-content">
|
||||
<div class="system-overview">
|
||||
<div class="overview-card clickable" [ngClass]="{'active': selectedSection === 'repositories'}"
|
||||
|
|
@ -49,8 +35,8 @@
|
|||
<mat-icon>cloud</mat-icon>
|
||||
</div>
|
||||
<div class="overview-content">
|
||||
<h3>Repositorios</h3>
|
||||
<p>Total: <span class="status-badge info">{{ repositories.length }}</span></p>
|
||||
<h3>{{ 'labelRepository' | translate }}</h3>
|
||||
<p>{{ 'labelTotalColon' | translate }}<span class="status-badge info">{{ repositories.length }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -60,13 +46,8 @@
|
|||
<mat-icon>storage</mat-icon>
|
||||
</div>
|
||||
<div class="overview-content">
|
||||
<h3>OgBoot Server</h3>
|
||||
@if(!errorOgBoot){
|
||||
<p>Estado: <span class="status-badge online">Operativo</span></p>
|
||||
}
|
||||
@else{
|
||||
<p>Estado: <span class="status-badge offline">Error</span></p>
|
||||
}
|
||||
<h3>{{ 'ogbootServers' | translate }}</h3>
|
||||
<p>{{ 'labelTotalColon' | translate }}<span class="status-badge info">{{ bootServers.length }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -76,8 +57,8 @@
|
|||
<mat-icon>router</mat-icon>
|
||||
</div>
|
||||
<div class="overview-content">
|
||||
<h3>Servidores DHCP</h3>
|
||||
<p>Total: <span class="status-badge info">{{ dhcps.length }}</span></p>
|
||||
<h3>{{ 'dhcpServers' | translate }}</h3>
|
||||
<p>{{ 'labelTotalColon' | translate }}<span class="status-badge info">{{ dhcps.length }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -87,252 +68,31 @@
|
|||
<mat-icon>dns</mat-icon>
|
||||
</div>
|
||||
<div class="overview-content">
|
||||
<h3>OgCore Server</h3>
|
||||
<p>Logs del servidor</p>
|
||||
<h3>{{ 'ogCoreServer' | translate }}</h3>
|
||||
<p>{{ 'serverLogs' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dynamic-content">
|
||||
<div *ngIf="selectedSection === 'repositories'" class="section-content">
|
||||
<div class="repositories-container">
|
||||
<div *ngIf="repositories.length === 0" class="no-repositories">
|
||||
<mat-icon class="no-data-icon">cloud_off</mat-icon>
|
||||
<h3>No hay repositorios disponibles</h3>
|
||||
<p>No se encontraron repositorios configurados en el sistema.</p>
|
||||
</div>
|
||||
|
||||
<div *ngIf="repositories.length > 0" class="repositories-selector-container">
|
||||
<div class="repository-selector">
|
||||
<mat-form-field appearance="outline" class="repository-select-field">
|
||||
<mat-label>Seleccionar repositorio</mat-label>
|
||||
<mat-select [(ngModel)]="selectedRepositoryUuid" (selectionChange)="onRepositoryChange($event.value)">
|
||||
<mat-option *ngFor="let repository of repositories" [value]="repository.uuid">
|
||||
{{ repository.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon matSuffix>storage</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="selectedRepositoryUuid && !errorRepositories[selectedRepositoryUuid] && repositoryStatuses[selectedRepositoryUuid]"
|
||||
class="selected-repository-content">
|
||||
<div class="repository-item">
|
||||
<div class="section-header">
|
||||
<h2>{{ getSelectedRepositoryName() }}</h2>
|
||||
<button class="logs-button" (click)="scrollToLogs()">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>Ver logs</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="repository-content">
|
||||
<app-status-tab [loading]="loading" [diskUsage]="repositoryStatuses[selectedRepositoryUuid].disk"
|
||||
[servicesStatus]="repositoryStatuses[selectedRepositoryUuid].services"
|
||||
[processesStatus]="repositoryStatuses[selectedRepositoryUuid].processes"
|
||||
[ramUsage]="repositoryStatuses[selectedRepositoryUuid].ram"
|
||||
[cpuUsage]="repositoryStatuses[selectedRepositoryUuid].cpu" [diskUsageChartData]="[
|
||||
{ name: 'Usado', value: repositoryStatuses[selectedRepositoryUuid].disk.used },
|
||||
{ name: 'Disponible', value: repositoryStatuses[selectedRepositoryUuid].disk.available }
|
||||
]" [ramUsageChartData]="[
|
||||
{ name: 'Usado', value: repositoryStatuses[selectedRepositoryUuid].ram.used },
|
||||
{ name: 'Disponible', value: repositoryStatuses[selectedRepositoryUuid].ram.available }
|
||||
]" [view]="view" [colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels"
|
||||
[isDhcp]="false" [isRepository]="true">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="repository-logs-section" id="repository-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>Datos del repositorio: {{ getSelectedRepositoryName() }}</h3>
|
||||
<p>Información específica del repositorio seleccionado</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe [src]="getRepositoryIframeUrl()" width="100%" height="600" frameborder="0"
|
||||
class="repository-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!selectedRepositoryUuid" class="no-repository-selected">
|
||||
<mat-icon class="no-data-icon">storage</mat-icon>
|
||||
<h3>Selecciona un repositorio</h3>
|
||||
<p>Elige un repositorio de la lista para ver su estado detallado.</p>
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectedRepositoryUuid && errorRepositories[selectedRepositoryUuid]" class="error-container">
|
||||
<div class="error-card">
|
||||
<mat-icon class="error-icon">error_outline</mat-icon>
|
||||
<h3>Error de conexión</h3>
|
||||
<p>No se pudo conectar con el repositorio seleccionado</p>
|
||||
<button mat-raised-button color="primary" (click)="retryRepositoryStatus(selectedRepositoryUuid)">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
Reintentar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectedSection === 'ogboot'" class="section-content">
|
||||
<div *ngIf="!errorOgBoot && !loadingOgBoot" class="tab-content">
|
||||
<div class="section-header">
|
||||
<h2>OgBoot Server</h2>
|
||||
<button class="logs-button" (click)="scrollToLogs()">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>Ver logs</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<app-status-tab [loading]="loadingOgBoot" [diskUsage]="ogBootDiskUsage"
|
||||
[servicesStatus]="ogBootServicesStatus" [installedOgLives]="installedOgLives"
|
||||
[diskUsageChartData]="ogBootDiskUsageChartData" [view]="view" [colorScheme]="colorScheme"
|
||||
[isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="isDhcp" [isRepository]="false">
|
||||
</app-status-tab>
|
||||
|
||||
<div class="logs-section" id="ogboot-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>Logs de OgBoot</h3>
|
||||
<p>Logs en tiempo real del servidor OgBoot</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe [src]="getOgBootIframeUrl()" width="100%" height="800" frameborder="0" class="ogboot-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="loadingOgBoot" class="loading-container">
|
||||
<div class="loading-content">
|
||||
<mat-spinner class="loading-spinner"></mat-spinner>
|
||||
<p class="loading-text">Cargando estado de OgBoot...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="errorOgBoot" class="error-container">
|
||||
<div class="error-card">
|
||||
<mat-icon class="error-icon">error_outline</mat-icon>
|
||||
<h3>Error de conexión</h3>
|
||||
<p>No se pudo conectar con el servidor OgBoot</p>
|
||||
<button mat-raised-button color="primary" (click)="loadOgBootStatus()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
Reintentar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(selectedSection === 'dhcp'){
|
||||
<div class="section-content">
|
||||
<div class="repositories-container">
|
||||
@if(dhcps.length === 0){
|
||||
<div class="no-repositories">
|
||||
<mat-icon class="no-data-icon">cloud_off</mat-icon>
|
||||
<h3>No hay dhcps disponibles</h3>
|
||||
<p>No se encontraron dhcps configurados en el sistema.</p>
|
||||
</div>
|
||||
}
|
||||
@else {
|
||||
<div class="repositories-selector-container">
|
||||
<div class="repository-selector">
|
||||
<mat-form-field appearance="outline" class="repository-select-field">
|
||||
<mat-label>Seleccionar dhcp</mat-label>
|
||||
<mat-select [(ngModel)]="selectedDhcpUuid" (selectionChange)="onDhcpChange($event.value)">
|
||||
<mat-option *ngFor="let dhcp of dhcps" [value]="dhcp.uuid">
|
||||
{{ dhcp.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon matSuffix>storage</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@if(selectedDhcpUuid && !errorDhcps[selectedDhcpUuid] && dhcpStatuses[selectedDhcpUuid]){
|
||||
<div class="selected-repository-content">
|
||||
<div class="repository-item">
|
||||
<div class="section-header">
|
||||
<h2>{{ getSelectedDhcpName() }}</h2>
|
||||
<button class="logs-button" (click)="scrollToLogs()">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>Ver logs</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="repository-content">
|
||||
<app-status-tab [loading]="loadingDhcps" [diskUsage]="dhcpStatuses[selectedDhcpUuid].disk"
|
||||
[servicesStatus]="dhcpStatuses[selectedDhcpUuid].services" [subnets]="subnets"
|
||||
[diskUsageChartData]="dhcpStatuses[selectedDhcpUuid].diskUsageChartData" [view]="view"
|
||||
[colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="true"
|
||||
[isRepository]="false">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logs-section" id="dhcp-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>Logs de DHCP</h3>
|
||||
<p>Logs en tiempo real del servidor DHCP</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe [src]="getDhcpIframeUrl()" width="100%" height="800" frameborder="0" class="dhcp-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div *ngIf="!selectedDhcpUuid" class="no-repository-selected">
|
||||
<mat-icon class="no-data-icon">storage</mat-icon>
|
||||
<h3>Selecciona un dhcp</h3>
|
||||
<p>Elige un dhcp de la lista para ver su estado detallado.</p>
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectedDhcpUuid && errorDhcps[selectedDhcpUuid]" class="error-container">
|
||||
<div class="error-card">
|
||||
<mat-icon class="error-icon">error_outline</mat-icon>
|
||||
<h3>Error de conexión</h3>
|
||||
<p>No se pudo conectar con el dhcp seleccionado</p>
|
||||
<button mat-raised-button color="primary" (click)="retryDhcpStatus(selectedDhcpUuid)">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
Reintentar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@if(selectedSection === 'repositories') {
|
||||
<app-ogrepository-status [repositories]="repositories"></app-ogrepository-status>
|
||||
}
|
||||
|
||||
@if(selectedSection === 'ogboot') {
|
||||
<app-global-ogboot-status [bootServers]="bootServers"></app-global-ogboot-status>
|
||||
}
|
||||
|
||||
@if(selectedSection === 'dhcp') {
|
||||
<app-ogdhcp-status [dhcps]="dhcps"></app-ogdhcp-status>
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
<div *ngIf="selectedSection === 'ogcore'" class="section-content">
|
||||
<div class="tab-content">
|
||||
<div class="section-header">
|
||||
<h2>OgCore Server</h2>
|
||||
</div>
|
||||
|
||||
<div class="logs-section" id="ogcore-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>Logs de OgCore</h3>
|
||||
<p>Logs en tiempo real del servidor OgCore</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe [src]="getOgCoreIframeUrl()" width="100%" height="800" frameborder="0" class="ogcore-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if(selectedSection === 'ogcore') {
|
||||
<app-ogcore-status></app-ogcore-status>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions class="action-container">
|
||||
|
|
@ -342,10 +102,9 @@
|
|||
<div class="action-buttons">
|
||||
<button mat-button class="secondary-button" (click)="refreshAll()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
Actualizar
|
||||
</button>
|
||||
{{ 'buttonUpdate' | translate }}</button>
|
||||
<button mat-raised-button color="primary" [mat-dialog-close]="true">
|
||||
{{ 'closeButton' | translate }}
|
||||
{{ 'buttonClose' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</mat-dialog-actions>
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-global-status',
|
||||
|
|
@ -10,15 +11,17 @@ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
|||
styleUrl: './global-status.component.css'
|
||||
})
|
||||
export class GlobalStatusComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
loading: boolean = false;
|
||||
loadingOgBoot: boolean = false;
|
||||
loadingDhcps: boolean = false;
|
||||
loadingRepositories: boolean = false;
|
||||
errorOgBoot: boolean = false;
|
||||
loadingBootServers: boolean = false;
|
||||
|
||||
errorDhcps: { [key: string]: boolean } = {};
|
||||
errorRepositories: { [key: string]: boolean } = {};
|
||||
installedOgLives: any[] = [];
|
||||
errorBootServers: { [key: string]: boolean } = {};
|
||||
|
||||
subnets: any[] = [];
|
||||
showLabels: boolean = true;
|
||||
isDoughnut: boolean = true;
|
||||
|
|
@ -26,26 +29,28 @@ export class GlobalStatusComponent implements OnInit {
|
|||
domain: ['#df200d', '#26a700']
|
||||
};
|
||||
view: [number, number] = [400, 220];
|
||||
|
||||
repositoriesUrl: string;
|
||||
repositories: any[] = [];
|
||||
repositoryStatuses: { [key: string]: any } = {};
|
||||
lastUpdateTime: string = '';
|
||||
selectedRepositoryUuid: string = '';
|
||||
selectedSection: 'repositories' | 'ogboot' | 'dhcp' | 'ogcore' = 'ogcore';
|
||||
|
||||
ogBootApiUrl: string;
|
||||
ogBootDiskUsage: any = {};
|
||||
ogBootServicesStatus: any = {};
|
||||
ogBootDiskUsageChartData: any[] = [];
|
||||
|
||||
dhcpsApiUrl: string;
|
||||
dhcps: any[] = [];
|
||||
dhcpDiskUsage: any = {};
|
||||
dhcpStatuses: { [key: string]: any } = {};
|
||||
dhcpDiskUsageChartData: any[] = [];
|
||||
selectedDhcpUuid: string = '';
|
||||
isDhcp: boolean = false;
|
||||
isRepository: boolean = false;
|
||||
|
||||
bootServersApiUrl: string;
|
||||
bootServers: any[] = [];
|
||||
bootServerStatuses: { [key: string]: any } = {};
|
||||
selectedBootServerUuid: string = '';
|
||||
|
||||
lastUpdateTime: string = '';
|
||||
|
||||
ogCoreApiUrl: string;
|
||||
|
||||
|
|
@ -62,30 +67,28 @@ export class GlobalStatusComponent implements OnInit {
|
|||
constructor(
|
||||
private configService: ConfigService,
|
||||
private toastService: ToastrService,
|
||||
private http: HttpClient,
|
||||
private sanitizer: DomSanitizer
|
||||
private http: HttpClient
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
this.ogBootApiUrl = `${this.baseUrl}/og-boot/status`;
|
||||
this.dhcpsApiUrl = `${this.baseUrl}/dhcp-servers`;
|
||||
this.ogCoreApiUrl = `${this.baseUrl}`;
|
||||
this.repositoriesUrl = `${this.baseUrl}/image-repositories`;
|
||||
|
||||
this.ogBootDiskUsageChartData = [];
|
||||
this.dhcpDiskUsageChartData = [];
|
||||
this.bootServersApiUrl = `${this.baseUrl}/boot-servers`;
|
||||
this.ogCoreApiUrl = `${this.baseUrl}`;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading = true;
|
||||
this.updateLastUpdateTime();
|
||||
|
||||
this.loadReleaseInfo();
|
||||
this.loadOgBootStatus();
|
||||
this.loadDhcps(false);
|
||||
this.loadRepositories(false);
|
||||
this.loadDhcps(true);
|
||||
this.loadRepositories(true);
|
||||
this.loadBootServers(true);
|
||||
|
||||
this.loadBootServers(false);
|
||||
this.syncSubnets();
|
||||
this.syncTemplates();
|
||||
this.syncOgLives();
|
||||
}
|
||||
|
||||
get releasePackages(): { key: string; value: string }[] {
|
||||
|
|
@ -122,7 +125,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (!hasError) {
|
||||
this.toastService.error('Error al sincronizar las subredes: tiempo de espera agotado');
|
||||
this.toastService.error(this.translate.instant('errorSyncingSubnets'));
|
||||
hasError = true;
|
||||
}
|
||||
}, 5000);
|
||||
|
|
@ -133,7 +136,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
pending--;
|
||||
if (pending === 0 && !hasError) {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.success('Sincronización de subredes de servidores DHCP exitosa');
|
||||
this.toastService.success(this.translate.instant('serverSubnetSynchronization'));
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
|
|
@ -147,403 +150,151 @@ export class GlobalStatusComponent implements OnInit {
|
|||
});
|
||||
});
|
||||
}, error => {
|
||||
console.error('Error fetching DHCP servers for subnet sync', error);
|
||||
console.error(this.translate.instant('errorFetchingDhcpServersFor'), error);
|
||||
});
|
||||
}
|
||||
|
||||
syncTemplates() {
|
||||
const timeoutId = setTimeout(() => {
|
||||
this.toastService.error('Error al sincronizar las plantillas Pxe: tiempo de espera agotado');
|
||||
}, 3500);
|
||||
if (this.bootServers.length === 0) return;
|
||||
|
||||
this.http.post(`${this.baseUrl}/pxe-templates/sync`, {})
|
||||
.subscribe(response => {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.success('Sincronización de las plantillas Pxe completada');
|
||||
}, error => {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al sincronizar las plantillas Pxe');
|
||||
let pending = this.bootServers.length;
|
||||
let hasError = false;
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (!hasError) {
|
||||
this.toastService.error(this.translate.instant('errorSyncingTemplates'));
|
||||
hasError = true;
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
this.bootServers.forEach(server => {
|
||||
this.http.post(`${this.baseUrl}/boot-servers/${server.uuid}/pxe-templates/sync`, {}).subscribe({
|
||||
next: () => {
|
||||
pending--;
|
||||
if (pending === 0 && !hasError) {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.success(this.translate.instant('successfulPxeTemplateSynchronization'));
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
pending--;
|
||||
if (!hasError) {
|
||||
hasError = true;
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.error(error.error?.['hydra:description'] || `Error al sincronizar plantillas Pxe del servidor ${server.name || server.uuid}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
syncOgLives(): void {
|
||||
const timeoutId = setTimeout(() => {
|
||||
this.toastService.error('Error al sincronizar las imagenes ogLive : tiempo de espera agotado');
|
||||
}, 3500);
|
||||
if (this.bootServers.length === 0) return;
|
||||
|
||||
this.http.post(`${this.baseUrl}/og-lives/sync`, {})
|
||||
.subscribe(response => {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.success('Sincronización con los ogLives completada');
|
||||
}, error => {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al sincronizar las imagenes ogLive');
|
||||
let pending = this.bootServers.length;
|
||||
let hasError = false;
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (!hasError) {
|
||||
this.toastService.error(this.translate.instant('errorWhenSynchronizingOgliveImages'));
|
||||
hasError = true;
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
this.bootServers.forEach(server => {
|
||||
this.http.post(`${this.baseUrl}/boot-servers/${server.uuid}/oglives/sync`, {}).subscribe({
|
||||
next: () => {
|
||||
pending--;
|
||||
if (pending === 0 && !hasError) {
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.success(this.translate.instant('synchronizationWithOglivesCompleted'));
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
pending--;
|
||||
if (!hasError) {
|
||||
hasError = true;
|
||||
clearTimeout(timeoutId);
|
||||
this.toastService.error(error.error?.['hydra:description'] || `Error al sincronizar ogLives del servidor ${server.name || server.uuid}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[key: string]: any;
|
||||
|
||||
loadStatus(apiUrl: string, diskUsage: any, servicesStatus: any, diskUsageChartData: any[], installedOgLives: any[], isDhcp: boolean, errorState: string, showLoading: boolean = true): void {
|
||||
if (isDhcp) {
|
||||
this.loadingDhcps = true;
|
||||
} else {
|
||||
this.loadingOgBoot = true;
|
||||
}
|
||||
|
||||
if (showLoading) {
|
||||
this.loading = true;
|
||||
}
|
||||
this[errorState] = false;
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
if (isDhcp) {
|
||||
this.loadingDhcps = false;
|
||||
} else {
|
||||
this.loadingOgBoot = false;
|
||||
}
|
||||
this[errorState] = true;
|
||||
}, 3500);
|
||||
this.http.get<any>(apiUrl).subscribe({
|
||||
next: data => {
|
||||
diskUsage.used = data.message.disk_usage.used;
|
||||
diskUsage.available = data.message.disk_usage.available;
|
||||
diskUsage.total = data.message.disk_usage.total;
|
||||
diskUsage.percentage = data.message.disk_usage.percentage;
|
||||
|
||||
Object.assign(servicesStatus, data.message.services_status);
|
||||
|
||||
if (isDhcp) {
|
||||
this.subnets.length = 0;
|
||||
if (data.message.subnets) {
|
||||
this.subnets.push(...data.message.subnets);
|
||||
}
|
||||
} else {
|
||||
installedOgLives.length = 0;
|
||||
if (data.message.installed_oglives) {
|
||||
installedOgLives.push(...data.message.installed_oglives);
|
||||
}
|
||||
this.loadingOgBootOgLives = false;
|
||||
}
|
||||
|
||||
diskUsageChartData.length = 0;
|
||||
diskUsageChartData.push(
|
||||
{ name: 'Usado', value: parseFloat(diskUsage.used) },
|
||||
{ name: 'Disponible', value: parseFloat(diskUsage.available) }
|
||||
);
|
||||
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
if (isDhcp) {
|
||||
this.loadingDhcps = false;
|
||||
} else {
|
||||
this.loadingOgBoot = false;
|
||||
}
|
||||
clearTimeout(timeoutId);
|
||||
},
|
||||
error: error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al cargar el estado de ogBoot');
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
if (isDhcp) {
|
||||
this.loadingDhcps = false;
|
||||
} else {
|
||||
this.loadingOgBoot = false;
|
||||
}
|
||||
this[errorState] = true;
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadRepositories(showLoading: boolean = true): void {
|
||||
if (showLoading) {
|
||||
this.loading = true;
|
||||
}
|
||||
this.errorRepositories = {};
|
||||
|
||||
this.http.get<any>(`${this.repositoriesUrl}?page=1&itemsPerPage=10`).subscribe(
|
||||
data => {
|
||||
if (showLoading) this.loadingRepositories = true;
|
||||
this.http.get<any>(`${this.repositoriesUrl}?page=1&itemsPerPage=10`).subscribe({
|
||||
next: data => {
|
||||
this.repositories = data['hydra:member'] || [];
|
||||
|
||||
if (this.repositories.length === 0) {
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let remainingRepositories = this.repositories.length;
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
this.repositories.forEach(repository => {
|
||||
if (!(repository.uuid in this.errorRepositories)) {
|
||||
this.errorRepositories[repository.uuid] = true;
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
this.repositories.forEach(repository => {
|
||||
this.loadRepositoryStatus(repository.uuid, (errorOccurred: boolean) => {
|
||||
remainingRepositories--;
|
||||
|
||||
this.errorRepositories[repository.uuid] = errorOccurred;
|
||||
|
||||
if (remainingRepositories === 0) {
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (showLoading) this.loadingRepositories = false;
|
||||
this.checkOverallLoading();
|
||||
},
|
||||
error => {
|
||||
this.loading = false;
|
||||
error: () => {
|
||||
this.repositories = [];
|
||||
this.toastService.error('Error al cargar la lista de repositorios');
|
||||
if (showLoading) this.loadingRepositories = false;
|
||||
this.checkOverallLoading();
|
||||
this.toastService.error(this.translate.instant('errorLoadingList2'));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
loadRepositoryStatus(repositoryUuid: string, callback: (errorOccurred: boolean) => void): void {
|
||||
console.log(`Cargando estado del repositorio: ${repositoryUuid}`);
|
||||
const timeoutId = setTimeout(() => {
|
||||
callback(true);
|
||||
}, 5000);
|
||||
|
||||
this.http.get<any>(`${this.baseUrl}/image-repositories/server/${repositoryUuid}/status`).subscribe(
|
||||
data => {
|
||||
|
||||
if (!data.success) {
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
return;
|
||||
loadBootServers(showLoading: boolean = true): void {
|
||||
if (showLoading) this.loadingBootServers = true;
|
||||
this.http.get<any>(`${this.bootServersApiUrl}?page=1&itemsPerPage=10`).subscribe({
|
||||
next: data => {
|
||||
this.bootServers = data['hydra:member'] || [];
|
||||
if (showLoading) this.loadingBootServers = false;
|
||||
|
||||
if (this.bootServers.length > 0) {
|
||||
this.syncTemplates();
|
||||
this.syncOgLives();
|
||||
}
|
||||
|
||||
const details = data.details;
|
||||
const { disk, services, ram, cpu, processes } = details;
|
||||
|
||||
this.repositoryStatuses[repositoryUuid] = {
|
||||
disk: {
|
||||
...disk,
|
||||
used: parseFloat(disk.used.replace('GB', '')),
|
||||
available: parseFloat(disk.available.replace('GB', '')),
|
||||
total: parseFloat(disk.total.replace('GB', '')),
|
||||
percentage: disk.used_percentage
|
||||
},
|
||||
ram: {
|
||||
...ram,
|
||||
used: parseFloat(ram.used.replace('GB', '')),
|
||||
available: parseFloat(ram.available.replace('GB', '')),
|
||||
total: parseFloat(ram.total.replace('GB', '')),
|
||||
percentage: ram.used_percentage
|
||||
},
|
||||
cpu: {
|
||||
used_percentage: cpu.used_percentage
|
||||
},
|
||||
services: services,
|
||||
processes: processes
|
||||
};
|
||||
clearTimeout(timeoutId);
|
||||
callback(false);
|
||||
|
||||
this.checkOverallLoading();
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al cargar el estado del repositorio');
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
error: () => {
|
||||
this.bootServers = [];
|
||||
if (showLoading) this.loadingBootServers = false;
|
||||
this.checkOverallLoading();
|
||||
this.toastService.error(this.translate.instant('errorLoadingList1'));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
loadOgBootStatus(): void {
|
||||
this.isDhcp = false;
|
||||
this.loadStatus(this.ogBootApiUrl, this.ogBootDiskUsage, this.ogBootServicesStatus, this.ogBootDiskUsageChartData, this.installedOgLives, this.isDhcp, 'errorOgBoot', false);
|
||||
}
|
||||
|
||||
|
||||
loadDhcps(showLoading: boolean = true): void {
|
||||
if (showLoading) {
|
||||
this.loading = true;
|
||||
}
|
||||
this.errorDhcps = {};
|
||||
|
||||
this.http.get<any>(`${this.dhcpsApiUrl}?page=1&itemsPerPage=10`).subscribe(
|
||||
data => {
|
||||
if (showLoading) this.loadingDhcps = true;
|
||||
this.http.get<any>(`${this.dhcpsApiUrl}?page=1&itemsPerPage=10`).subscribe({
|
||||
next: data => {
|
||||
this.dhcps = data['hydra:member'] || [];
|
||||
|
||||
if (this.dhcps.length === 0) {
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let remainingDhcps = this.dhcps.length;
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
this.dhcps.forEach(dhcp => {
|
||||
if (!(dhcp.uuid in this.errorDhcps)) {
|
||||
this.errorDhcps[dhcp.uuid] = true;
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
this.dhcps.forEach(dhcp => {
|
||||
this.loadDhcpStatus(dhcp.uuid, (errorOccurred: boolean) => {
|
||||
remainingDhcps--;
|
||||
|
||||
this.errorDhcps[dhcp.uuid] = errorOccurred;
|
||||
|
||||
if (remainingDhcps === 0) {
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (showLoading) this.loadingDhcps = false;
|
||||
this.checkOverallLoading();
|
||||
},
|
||||
error => {
|
||||
this.loading = false;
|
||||
error: () => {
|
||||
this.dhcps = [];
|
||||
this.toastService.error('Error al cargar la lista de Dhcps');
|
||||
if (showLoading) this.loadingDhcps = false;
|
||||
this.checkOverallLoading();
|
||||
this.toastService.error(this.translate.instant('errorLoadingList'));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
loadDhcpStatus(dhcpUuid: string, callback: (errorOccurred: boolean) => void): void {
|
||||
console.log(`Cargando estado del Dhcp: ${dhcpUuid}`);
|
||||
const timeoutId = setTimeout(() => {
|
||||
callback(true);
|
||||
}, 5000);
|
||||
|
||||
this.http.get<any>(`${this.baseUrl}/dhcp-servers/server/${dhcpUuid}/status`).subscribe(
|
||||
data => {
|
||||
|
||||
if (!data.success) {
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
const disk = data.message.disk_usage;
|
||||
const services = data.message.services_status;
|
||||
let diskUsageChartData: any[] = [];
|
||||
diskUsageChartData.length = 0;
|
||||
diskUsageChartData.push(
|
||||
{ name: 'Usado', value: parseFloat(disk.used) },
|
||||
{ name: 'Disponible', value: parseFloat(disk.available) }
|
||||
);
|
||||
|
||||
this.subnets.length = 0;
|
||||
if (data.message.subnets) {
|
||||
this.subnets.push(...data.message.subnets);
|
||||
}
|
||||
|
||||
this.dhcpStatuses[dhcpUuid] = {
|
||||
disk: {
|
||||
...disk,
|
||||
used: parseFloat(disk.used.replace('GB', '')),
|
||||
available: parseFloat(disk.available.replace('GB', '')),
|
||||
total: parseFloat(disk.total.replace('GB', '')),
|
||||
percentage: disk.used_percentage
|
||||
},
|
||||
services: services,
|
||||
diskUsageChartData: diskUsageChartData
|
||||
};
|
||||
clearTimeout(timeoutId);
|
||||
callback(false);
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al cargar el estado del dhcp');
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
onDhcpChange(dhcpUuid: string): void {
|
||||
this.selectedDhcpUuid = dhcpUuid;
|
||||
if (dhcpUuid && !this.dhcpStatuses[dhcpUuid]) {
|
||||
this.loadDhcpStatus(dhcpUuid, (errorOccurred: boolean) => {
|
||||
if (errorOccurred) {
|
||||
this.errorDhcps[dhcpUuid] = true;
|
||||
}
|
||||
});
|
||||
checkOverallLoading(): void {
|
||||
if (!this.loadingRepositories && !this.loadingBootServers && !this.loadingDhcps) {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedDhcpName(): string {
|
||||
const selectedDhcp = this.dhcps.find(dhcp => dhcp.uuid === this.selectedDhcpUuid);
|
||||
return selectedDhcp ? selectedDhcp.name : '';
|
||||
}
|
||||
|
||||
|
||||
onRepositoryChange(repositoryUuid: string): void {
|
||||
this.selectedRepositoryUuid = repositoryUuid;
|
||||
if (repositoryUuid && !this.repositoryStatuses[repositoryUuid]) {
|
||||
this.loadRepositoryStatus(repositoryUuid, (errorOccurred: boolean) => {
|
||||
if (errorOccurred) {
|
||||
this.errorRepositories[repositoryUuid] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedRepositoryName(): string {
|
||||
const selectedRepo = this.repositories.find(repo => repo.uuid === this.selectedRepositoryUuid);
|
||||
return selectedRepo ? selectedRepo.name : '';
|
||||
}
|
||||
|
||||
getRepositoryIframeUrl(): SafeResourceUrl {
|
||||
const repositoryName = this.getSelectedRepositoryName();
|
||||
if (repositoryName) {
|
||||
const encodedName = encodeURIComponent(repositoryName);
|
||||
const url = `${this.configService.logUrl}/d-solo/ogrepo-logs/ogrepo-logs?orgId=1&timezone=browser&refresh=5s&var-query0=&editIndex=0&var-hostname=${encodedName}&theme=dark&panelId=1&__feature.dashboardSceneSolo`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
}
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl('');
|
||||
}
|
||||
|
||||
getOgBootIframeUrl(): SafeResourceUrl {
|
||||
const url = `${this.configService.logUrl}/d-solo/ogboot-logs/ogboot-logs?orgId=1&timezone=browser&refresh=5s&theme=dark&panelId=1&__feature.dashboardSceneSolo`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
}
|
||||
|
||||
getDhcpIframeUrl(): SafeResourceUrl {
|
||||
const dhcpName = this.getSelectedDhcpName();
|
||||
if (dhcpName) {
|
||||
const encodedName = encodeURIComponent(dhcpName);
|
||||
const url = `${this.configService.logUrl}/d-solo/ogdhcp-logs/ogdhcp-logs?orgId=1&timezone=browser&refresh=5s&var-query0=&editIndex=0&var-hostname=${encodedName}&theme=dark&panelId=1&__feature.dashboardSceneSolo`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
}
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl('');
|
||||
|
||||
}
|
||||
|
||||
getOgCoreIframeUrl(): SafeResourceUrl {
|
||||
const url = `${this.configService.logUrl}/d-solo/ogcore-logs/ogcore-logs?orgId=1&tab=transformations&theme=dark&panelId=1&__feature.dashboardSceneSolo&now-5m&to=now&timezone=browser`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
}
|
||||
|
||||
refreshAll(): void {
|
||||
this.loading = true;
|
||||
this.updateLastUpdateTime();
|
||||
this.loadStatus(this.ogBootApiUrl, this.ogBootDiskUsage, this.ogBootServicesStatus, this.ogBootDiskUsageChartData, this.installedOgLives, false, 'errorOgBoot', true);
|
||||
this.loadBootServers(true);
|
||||
this.loadDhcps(true);
|
||||
this.loadRepositories(true);
|
||||
this.syncSubnets();
|
||||
this.syncTemplates();
|
||||
this.syncOgLives();
|
||||
this.toastService.success('Datos actualizados correctamente');
|
||||
this.toastService.success(this.translate.instant('dataUpdatedCorrectly'));
|
||||
}
|
||||
|
||||
updateLastUpdateTime(): void {
|
||||
|
|
@ -564,88 +315,11 @@ export class GlobalStatusComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
retryRepositoryStatus(repositoryUuid: string): void {
|
||||
this.loadRepositoryStatus(repositoryUuid, (errorOccurred: boolean) => {
|
||||
this.errorRepositories[repositoryUuid] = errorOccurred;
|
||||
if (!errorOccurred) {
|
||||
this.toastService.success('Estado del repositorio actualizado correctamente');
|
||||
} else {
|
||||
this.toastService.error('Error al cargar el estado del repositorio');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
retryDhcpStatus(dhcpUuid: string): void {
|
||||
this.loadDhcpStatus(dhcpUuid, (errorOccurred: boolean) => {
|
||||
this.errorDhcps[dhcpUuid] = errorOccurred;
|
||||
if (!errorOccurred) {
|
||||
this.toastService.success('Estado del dhcp actualizado correctamente');
|
||||
} else {
|
||||
this.toastService.error('Error al cargar el estado del dhcp');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
selectSection(section: 'repositories' | 'ogboot' | 'dhcp' | 'ogcore'): void {
|
||||
this.selectedSection = section;
|
||||
|
||||
switch (section) {
|
||||
case 'repositories':
|
||||
break;
|
||||
case 'ogboot':
|
||||
if (!this.ogBootDiskUsage) {
|
||||
this.loadOgBootStatus();
|
||||
}
|
||||
break;
|
||||
case 'dhcp':
|
||||
break;
|
||||
case 'ogcore':
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
scrollToLogs(): void {
|
||||
let logsSectionId: string;
|
||||
switch (this.selectedSection) {
|
||||
case 'repositories':
|
||||
logsSectionId = 'repository-logs-section';
|
||||
break;
|
||||
case 'ogboot':
|
||||
logsSectionId = 'ogboot-logs-section';
|
||||
break;
|
||||
case 'dhcp':
|
||||
logsSectionId = 'dhcp-logs-section';
|
||||
break;
|
||||
case 'ogcore':
|
||||
logsSectionId = 'ogcore-logs-section';
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
const checkAndScroll = () => {
|
||||
const logsSection = document.getElementById(logsSectionId);
|
||||
if (logsSection) {
|
||||
logsSection.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (checkAndScroll()) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
.section-content {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.repositories-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.repositories-selector-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.repository-selector {
|
||||
display: flex;
|
||||
padding: 0.25rem 0 0;
|
||||
}
|
||||
|
||||
.repository-select-field {
|
||||
min-width: 260px;
|
||||
max-width: 420px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.repository-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.selected-repository-content {
|
||||
animation: fadeInUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.6rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.section-header h2::before {
|
||||
content: '';
|
||||
width: 3px;
|
||||
height: 18px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logs-button {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.4rem 1rem;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.82rem;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.25);
|
||||
}
|
||||
|
||||
.logs-button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 14px rgba(102, 126, 234, 0.35);
|
||||
}
|
||||
|
||||
.logs-button mat-icon {
|
||||
font-size: 1rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.logs-section {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.07);
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.logs-header {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.logs-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.logs-header h3::before {
|
||||
content: '';
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logs-header p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logs-container {
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
padding: 0.4rem;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.ogboot-logs-iframe {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.no-repositories {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.no-repositories .no-data-icon {
|
||||
font-size: 2.5rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.no-repositories h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.no-repositories p {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.no-repository-selected {
|
||||
text-align: center;
|
||||
padding: 1.5rem 1rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.no-repository-selected .no-data-icon {
|
||||
font-size: 2.5rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.no-repository-selected h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.no-repository-selected p {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.error-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 160px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.error-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #e9ecef;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 42px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
color: #dc3545;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.error-card h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.error-card p {
|
||||
margin: 0 0 1rem;
|
||||
color: #6c757d;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<div class="section-content">
|
||||
<div class="repositories-container">
|
||||
@if(bootServers.length === 0) {
|
||||
<div class="no-repositories">
|
||||
<mat-icon class="no-data-icon">cloud_off</mat-icon>
|
||||
<h3>{{ 'noBootServersAvailable' | translate }}</h3>
|
||||
<p>{{ 'noConfiguredBootServers' | translate }}</p>
|
||||
</div>
|
||||
} @else {<div class="repositories-selector-container">
|
||||
<div class="repository-selector">
|
||||
<mat-form-field appearance="outline" class="repository-select-field">
|
||||
<mat-label>{{ 'selectOgBootServer' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedBootServerUuid" (selectionChange)="onBootServerChange($event.value)">
|
||||
@for(server of bootServers; track server.uuid) {
|
||||
<mat-option [value]="server.uuid">
|
||||
{{ server.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-icon matSuffix>storage</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@if(selectedBootServerUuid && !errorBootServers[selectedBootServerUuid] && bootServerStatuses[selectedBootServerUuid]) {
|
||||
<div class="selected-repository-content">
|
||||
<div class="repository-item">
|
||||
<div class="section-header">
|
||||
<h2>{{ getSelectedBootServerName() }}</h2>
|
||||
<button class="logs-button" (click)="scrollToLogs()">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>{{ 'buttonViewLogs' | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="repository-content">
|
||||
<app-status-tab [loading]="loadingBootServers" [diskUsage]="bootServerStatuses[selectedBootServerUuid].disk"
|
||||
[servicesStatus]="bootServerStatuses[selectedBootServerUuid].services"
|
||||
[installedOgLives]="bootServerStatuses[selectedBootServerUuid].installedOgLives"
|
||||
[diskUsageChartData]="bootServerStatuses[selectedBootServerUuid].diskUsageChartData" [view]="view" [colorScheme]="colorScheme"
|
||||
[isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="false" [isRepository]="false">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logs-section" id="ogboot-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>{{ 'ogbootLogs' | translate }}</h3>
|
||||
<p>{{ 'realTimeOgbootLogs' | translate }}</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe [src]="getBootServerIframeUrl()" width="100%" height="800" frameborder="0" class="ogboot-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(!selectedBootServerUuid) {
|
||||
<div class="no-repository-selected">
|
||||
<mat-icon class="no-data-icon">storage</mat-icon>
|
||||
<h3>{{ 'selectABootServer' | translate }}</h3>
|
||||
<p>{{ 'selectBootServerToView' | translate }}</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(selectedBootServerUuid && errorBootServers[selectedBootServerUuid]) {
|
||||
<div class="error-container">
|
||||
<div class="error-card">
|
||||
<mat-icon class="error-icon">error_outline</mat-icon>
|
||||
<h3>{{ 'connectionError' | translate }}</h3>
|
||||
<p>{{ 'cannotConnectBootServer' | translate }}</p>
|
||||
<button mat-raised-button color="primary" (click)="retryBootServerStatus(selectedBootServerUuid)">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
{{ 'buttonRetry' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OgbootStatusComponent } from './ogboot-status.component';
|
||||
|
||||
describe('OgbootStatusComponent', () => {
|
||||
let component: OgbootStatusComponent;
|
||||
let fixture: ComponentFixture<OgbootStatusComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [OgbootStatusComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(OgbootStatusComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-global-ogboot-status',
|
||||
templateUrl: './ogboot-status.component.html',
|
||||
styleUrl: './ogboot-status.component.css'
|
||||
})
|
||||
export class GlobalOgbootStatusComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
loadingBootServers: boolean = false;
|
||||
|
||||
@Input() bootServers: any[] = [];
|
||||
|
||||
bootServerStatuses: { [key: string]: any } = {};
|
||||
selectedBootServerUuid: string = '';
|
||||
errorBootServers: { [key: string]: boolean } = {};
|
||||
|
||||
showLabels: boolean = true;
|
||||
isDoughnut: boolean = true;
|
||||
colorScheme: any = {
|
||||
domain: ['#df200d', '#26a700']
|
||||
};
|
||||
view: [number, number] = [400, 220];
|
||||
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private toastService: ToastrService,
|
||||
private http: HttpClient,
|
||||
private sanitizer: DomSanitizer
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
|
||||
|
||||
loadBootServerStatus(bootServerUuid: string, callback: (errorOccurred: boolean) => void): void {
|
||||
const timeoutId = setTimeout(() => {
|
||||
callback(true);
|
||||
}, 5000);
|
||||
|
||||
this.http.get<any>(`${this.baseUrl}/boot-servers/server/${bootServerUuid}/status`).subscribe(
|
||||
data => {
|
||||
if (data.code && data.code !== 200) {
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const message = data.message || data;
|
||||
const disk = message.disk_usage;
|
||||
const services = message.services_status;
|
||||
const installedOgLives = message.installed_oglives || [];
|
||||
|
||||
let diskUsageChartData: any[] = [];
|
||||
diskUsageChartData.push(
|
||||
{ name: 'Usado', value: parseFloat(disk.used) },
|
||||
{ name: 'Disponible', value: parseFloat(disk.available) }
|
||||
);
|
||||
|
||||
this.bootServerStatuses[bootServerUuid] = {
|
||||
disk: {
|
||||
...disk,
|
||||
used: parseFloat(disk.used.replace('GB', '')),
|
||||
available: parseFloat(disk.available.replace('GB', '')),
|
||||
total: parseFloat(disk.total.replace('GB', '')),
|
||||
percentage: disk.used_percentage || disk.percentage
|
||||
},
|
||||
services: services,
|
||||
installedOgLives: installedOgLives,
|
||||
diskUsageChartData: diskUsageChartData
|
||||
};
|
||||
clearTimeout(timeoutId);
|
||||
callback(false);
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error?.['hydra:description'] || 'Error al cargar el estado del servidor de boot');
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onBootServerChange(bootServerUuid: string): void {
|
||||
this.selectedBootServerUuid = bootServerUuid;
|
||||
if (bootServerUuid && !this.bootServerStatuses[bootServerUuid]) {
|
||||
this.loadBootServerStatus(bootServerUuid, (errorOccurred: boolean) => {
|
||||
if (errorOccurred) {
|
||||
this.errorBootServers[bootServerUuid] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedBootServerName(): string {
|
||||
const selectedBootServer = this.bootServers.find(server => server.uuid === this.selectedBootServerUuid);
|
||||
return selectedBootServer ? selectedBootServer.name : '';
|
||||
}
|
||||
|
||||
getBootServerIframeUrl(): SafeResourceUrl {
|
||||
const bootServerName = this.getSelectedBootServerName();
|
||||
if (bootServerName) {
|
||||
const encodedName = encodeURIComponent(bootServerName);
|
||||
const url = `${this.configService.logUrl}/d-solo/ogboot-logs/ogboot-logs?orgId=1&timezone=browser&refresh=5s&var-hostname=${encodedName}&theme=dark&panelId=1&__feature.dashboardSceneSolo`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
}
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl('');
|
||||
}
|
||||
|
||||
retryBootServerStatus(bootServerUuid: string): void {
|
||||
this.loadBootServerStatus(bootServerUuid, (errorOccurred: boolean) => {
|
||||
this.errorBootServers[bootServerUuid] = errorOccurred;
|
||||
if (!errorOccurred) {
|
||||
this.toastService.success(this.translate.instant('bootServerStatus'));
|
||||
} else {
|
||||
this.toastService.error(this.translate.instant('errorLoadingState2'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scrollToLogs(): void {
|
||||
const checkAndScroll = () => {
|
||||
const logsSection = document.getElementById('ogboot-logs-section');
|
||||
if (logsSection) {
|
||||
logsSection.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (checkAndScroll()) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
.section-content {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.logs-section {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.07);
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.logs-header {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.logs-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.logs-header h3::before {
|
||||
content: '';
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logs-header p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logs-container {
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
padding: 0.4rem;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.ogcore-logs-iframe {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<div class="section-content">
|
||||
<div class="tab-content">
|
||||
<div class="logs-section" id="ogcore-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>{{ 'ogcoreLogs' | translate }}</h3>
|
||||
<p>{{ 'realTimeOgcoreLogs' | translate }}</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe [src]="getOgCoreIframeUrl()" width="100%" height="800" frameborder="0" class="ogcore-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OgcoreStatusComponent } from './ogcore-status.component';
|
||||
|
||||
describe('OgcoreStatusComponent', () => {
|
||||
let component: OgcoreStatusComponent;
|
||||
let fixture: ComponentFixture<OgcoreStatusComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [OgcoreStatusComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(OgcoreStatusComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-ogcore-status',
|
||||
templateUrl: './ogcore-status.component.html',
|
||||
styleUrl: './ogcore-status.component.css'
|
||||
})
|
||||
export class OgcoreStatusComponent {
|
||||
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private sanitizer: DomSanitizer
|
||||
) {}
|
||||
|
||||
getOgCoreIframeUrl(): SafeResourceUrl {
|
||||
const url = `${this.configService.logUrl}/d-solo/ogcore-logs/ogcore-logs?orgId=1&tab=transformations&theme=dark&panelId=1&__feature.dashboardSceneSolo&now-5m&to=now&timezone=browser`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
.section-content {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.repositories-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.repositories-selector-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.repository-selector {
|
||||
display: flex;
|
||||
padding: 0.25rem 0 0;
|
||||
}
|
||||
|
||||
.repository-select-field {
|
||||
min-width: 260px;
|
||||
max-width: 420px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.repository-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.selected-repository-content {
|
||||
animation: fadeInUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.6rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.section-header h2::before {
|
||||
content: '';
|
||||
width: 3px;
|
||||
height: 18px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logs-button {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.4rem 1rem;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.82rem;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.25);
|
||||
}
|
||||
|
||||
.logs-button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 14px rgba(102, 126, 234, 0.35);
|
||||
}
|
||||
|
||||
.logs-button mat-icon {
|
||||
font-size: 1rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.logs-section {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.07);
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.logs-header {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.logs-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.logs-header h3::before {
|
||||
content: '';
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logs-header p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logs-container {
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
padding: 0.4rem;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.repository-logs-iframe {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.no-repositories {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.no-repositories .no-data-icon {
|
||||
font-size: 2.5rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.no-repositories h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.no-repositories p {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.no-repository-selected {
|
||||
text-align: center;
|
||||
padding: 1.5rem 1rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.no-repository-selected .no-data-icon {
|
||||
font-size: 2.5rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.no-repository-selected h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.no-repository-selected p {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.error-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 160px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.error-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #e9ecef;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 42px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
color: #dc3545;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.error-card h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.error-card p {
|
||||
margin: 0 0 1rem;
|
||||
color: #6c757d;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<div class="section-content">
|
||||
<div class="repositories-container">
|
||||
@if(dhcps.length === 0) {
|
||||
<div class="no-repositories">
|
||||
<mat-icon class="no-data-icon">cloud_off</mat-icon>
|
||||
<h3>{{ 'noDhcpsAvailable' | translate }}</h3>
|
||||
<p>{{ 'noConfiguredDhcps' | translate }}</p>
|
||||
</div>
|
||||
} @else {<div class="repositories-selector-container">
|
||||
<div class="repository-selector">
|
||||
<mat-form-field appearance="outline" class="repository-select-field">
|
||||
<mat-label>{{ 'buttonSelectDhcp' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedDhcpUuid" (selectionChange)="onDhcpChange($event.value)">
|
||||
@for(dhcp of dhcps; track dhcp.uuid) {
|
||||
<mat-option [value]="dhcp.uuid">
|
||||
{{ dhcp.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-icon matSuffix>storage</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@if(selectedDhcpUuid && !errorDhcps[selectedDhcpUuid] && dhcpStatuses[selectedDhcpUuid]) {
|
||||
<div class="selected-repository-content">
|
||||
<div class="repository-item">
|
||||
<div class="section-header">
|
||||
<h2>{{ getSelectedDhcpName() }}</h2>
|
||||
<button class="logs-button" (click)="scrollToLogs()">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>{{ 'buttonViewLogs' | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="repository-content">
|
||||
<app-status-tab [loading]="loadingDhcps" [diskUsage]="dhcpStatuses[selectedDhcpUuid].disk"
|
||||
[servicesStatus]="dhcpStatuses[selectedDhcpUuid].services" [subnets]="subnets"
|
||||
[diskUsageChartData]="dhcpStatuses[selectedDhcpUuid].diskUsageChartData" [view]="view"
|
||||
[colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="true"
|
||||
[isRepository]="false">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logs-section" id="dhcp-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>{{ 'dhcpLogs' | translate }}</h3>
|
||||
<p>{{ 'realTimeDhcpLogs' | translate }}</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe [src]="getDhcpIframeUrl()" width="100%" height="800" frameborder="0" class="dhcp-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(!selectedDhcpUuid) {
|
||||
<div class="no-repository-selected">
|
||||
<mat-icon class="no-data-icon">storage</mat-icon>
|
||||
<h3>{{ 'selectADhcp' | translate }}</h3>
|
||||
<p>{{ 'selectDhcpToView' | translate }}</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(selectedDhcpUuid && errorDhcps[selectedDhcpUuid]) {
|
||||
<div class="error-container">
|
||||
<div class="error-card">
|
||||
<mat-icon class="error-icon">error_outline</mat-icon>
|
||||
<h3>{{ 'connectionError' | translate }}</h3>
|
||||
<p>{{ 'cannotConnectDhcp' | translate }}</p>
|
||||
<button mat-raised-button color="primary" (click)="retryDhcpStatus(selectedDhcpUuid)">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
{{ 'buttonRetry' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OgdhcpStatusComponent } from './ogdhcp-status.component';
|
||||
|
||||
describe('OgdhcpStatusComponent', () => {
|
||||
let component: OgdhcpStatusComponent;
|
||||
let fixture: ComponentFixture<OgdhcpStatusComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [OgdhcpStatusComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(OgdhcpStatusComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-ogdhcp-status',
|
||||
templateUrl: './ogdhcp-status.component.html',
|
||||
styleUrl: './ogdhcp-status.component.css'
|
||||
})
|
||||
export class OgdhcpStatusComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
loadingDhcps: boolean = false;
|
||||
|
||||
@Input() dhcps: any[] = [];
|
||||
|
||||
dhcpStatuses: { [key: string]: any } = {};
|
||||
selectedDhcpUuid: string = '';
|
||||
errorDhcps: { [key: string]: boolean } = {};
|
||||
subnets: any[] = [];
|
||||
|
||||
showLabels: boolean = true;
|
||||
isDoughnut: boolean = true;
|
||||
colorScheme: any = {
|
||||
domain: ['#df200d', '#26a700']
|
||||
};
|
||||
view: [number, number] = [400, 220];
|
||||
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private toastService: ToastrService,
|
||||
private http: HttpClient,
|
||||
private sanitizer: DomSanitizer
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
|
||||
|
||||
loadDhcpStatus(dhcpUuid: string, callback: (errorOccurred: boolean) => void): void {
|
||||
const timeoutId = setTimeout(() => {
|
||||
callback(true);
|
||||
}, 5000);
|
||||
|
||||
this.http.get<any>(`${this.baseUrl}/dhcp-servers/server/${dhcpUuid}/status`).subscribe(
|
||||
data => {
|
||||
|
||||
if (!data.success && !data.message) {
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
const message = data.message || data;
|
||||
const disk = message.disk_usage;
|
||||
const services = message.services_status;
|
||||
let diskUsageChartData: any[] = [];
|
||||
diskUsageChartData.push(
|
||||
{ name: 'Usado', value: parseFloat(disk.used) },
|
||||
{ name: 'Disponible', value: parseFloat(disk.available) }
|
||||
);
|
||||
|
||||
this.subnets.length = 0;
|
||||
if (message.subnets) {
|
||||
this.subnets.push(...message.subnets);
|
||||
}
|
||||
|
||||
this.dhcpStatuses[dhcpUuid] = {
|
||||
disk: {
|
||||
...disk,
|
||||
used: parseFloat(disk.used.replace('GB', '')),
|
||||
available: parseFloat(disk.available.replace('GB', '')),
|
||||
total: parseFloat(disk.total.replace('GB', '')),
|
||||
percentage: disk.used_percentage || disk.percentage
|
||||
},
|
||||
services: services,
|
||||
diskUsageChartData: diskUsageChartData
|
||||
};
|
||||
clearTimeout(timeoutId);
|
||||
callback(false);
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error?.['hydra:description'] || 'Error al cargar el estado del dhcp');
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onDhcpChange(dhcpUuid: string): void {
|
||||
this.selectedDhcpUuid = dhcpUuid;
|
||||
if (dhcpUuid && !this.dhcpStatuses[dhcpUuid]) {
|
||||
this.loadDhcpStatus(dhcpUuid, (errorOccurred: boolean) => {
|
||||
if (errorOccurred) {
|
||||
this.errorDhcps[dhcpUuid] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedDhcpName(): string {
|
||||
const selectedDhcp = this.dhcps.find(dhcp => dhcp.uuid === this.selectedDhcpUuid);
|
||||
return selectedDhcp ? selectedDhcp.name : '';
|
||||
}
|
||||
|
||||
getDhcpIframeUrl(): SafeResourceUrl {
|
||||
const dhcpName = this.getSelectedDhcpName();
|
||||
if (dhcpName) {
|
||||
const encodedName = encodeURIComponent(dhcpName);
|
||||
const url = `${this.configService.logUrl}/d-solo/ogdhcp-logs/ogdhcp-logs?orgId=1&timezone=browser&refresh=5s&var-query0=&editIndex=0&var-hostname=${encodedName}&theme=dark&panelId=1&__feature.dashboardSceneSolo`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
}
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl('');
|
||||
}
|
||||
|
||||
retryDhcpStatus(dhcpUuid: string): void {
|
||||
this.loadDhcpStatus(dhcpUuid, (errorOccurred: boolean) => {
|
||||
this.errorDhcps[dhcpUuid] = errorOccurred;
|
||||
if (!errorOccurred) {
|
||||
this.toastService.success(this.translate.instant('dhcpStatusUpdatedSuccessfully'));
|
||||
} else {
|
||||
this.toastService.error(this.translate.instant('errorLoadingState'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scrollToLogs(): void {
|
||||
const checkAndScroll = () => {
|
||||
const logsSection = document.getElementById('dhcp-logs-section');
|
||||
if (logsSection) {
|
||||
logsSection.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (checkAndScroll()) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
.section-content {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.repositories-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.repositories-selector-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.repository-selector {
|
||||
display: flex;
|
||||
padding: 0.25rem 0 0;
|
||||
}
|
||||
|
||||
.repository-select-field {
|
||||
min-width: 260px;
|
||||
max-width: 420px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.repository-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.selected-repository-content {
|
||||
animation: fadeInUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.6rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.section-header h2::before {
|
||||
content: '';
|
||||
width: 3px;
|
||||
height: 18px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logs-button {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.4rem 1rem;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.82rem;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.25);
|
||||
}
|
||||
|
||||
.logs-button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 14px rgba(102, 126, 234, 0.35);
|
||||
}
|
||||
|
||||
.logs-button mat-icon {
|
||||
font-size: 1rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.logs-section {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.07);
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.logs-header {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.logs-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.logs-header h3::before {
|
||||
content: '';
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logs-header p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logs-container {
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
padding: 0.4rem;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.repository-logs-iframe {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.no-repositories {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.no-repositories .no-data-icon {
|
||||
font-size: 2.5rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.no-repositories h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.no-repositories p {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.no-repository-selected {
|
||||
text-align: center;
|
||||
padding: 1.5rem 1rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.no-repository-selected .no-data-icon {
|
||||
font-size: 2.5rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.no-repository-selected h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.no-repository-selected p {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.error-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 160px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.error-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #e9ecef;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 42px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
color: #dc3545;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.error-card h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.error-card p {
|
||||
margin: 0 0 1rem;
|
||||
color: #6c757d;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<div class="section-content">
|
||||
<div class="repositories-container">
|
||||
@if(repositories.length === 0) {
|
||||
<div class="no-repositories">
|
||||
<mat-icon class="no-data-icon">cloud_off</mat-icon>
|
||||
<h3>{{ 'noReposAvailable' | translate }}</h3>
|
||||
<p>{{ 'noConfiguredRepos' | translate }}</p>
|
||||
</div>
|
||||
} @else {<div class="repositories-selector-container">
|
||||
<div class="repository-selector">
|
||||
<mat-form-field appearance="outline" class="repository-select-field">
|
||||
<mat-label>{{ 'selectRepository' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedRepositoryUuid" (selectionChange)="onRepositoryChange($event.value)">
|
||||
@for(repository of repositories; track repository.uuid) {
|
||||
<mat-option [value]="repository.uuid">
|
||||
{{ repository.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-icon matSuffix>storage</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@if(selectedRepositoryUuid && !errorRepositories[selectedRepositoryUuid] && repositoryStatuses[selectedRepositoryUuid]) {
|
||||
<div class="selected-repository-content">
|
||||
<div class="repository-item">
|
||||
<div class="section-header">
|
||||
<h2>{{ getSelectedRepositoryName() }}</h2>
|
||||
<button class="logs-button" (click)="scrollToLogs()">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>{{ 'buttonViewLogs' | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="repository-content">
|
||||
<app-status-tab [loading]="loading" [diskUsage]="repositoryStatuses[selectedRepositoryUuid].disk"
|
||||
[servicesStatus]="repositoryStatuses[selectedRepositoryUuid].services"
|
||||
[processesStatus]="repositoryStatuses[selectedRepositoryUuid].processes"
|
||||
[ramUsage]="repositoryStatuses[selectedRepositoryUuid].ram"
|
||||
[cpuUsage]="repositoryStatuses[selectedRepositoryUuid].cpu" [diskUsageChartData]="[
|
||||
{ name: 'Usado', value: repositoryStatuses[selectedRepositoryUuid].disk.used },
|
||||
{ name: 'Disponible', value: repositoryStatuses[selectedRepositoryUuid].disk.available }
|
||||
]" [ramUsageChartData]="[
|
||||
{ name: 'Usado', value: repositoryStatuses[selectedRepositoryUuid].ram.used },
|
||||
{ name: 'Disponible', value: repositoryStatuses[selectedRepositoryUuid].ram.available }
|
||||
]" [view]="view" [colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels"
|
||||
[isDhcp]="false" [isRepository]="true">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="repository-logs-section" id="repository-logs-section">
|
||||
<div class="logs-header">
|
||||
<h3>Datos del repositorio: {{ getSelectedRepositoryName() }}</h3>
|
||||
<p>{{ 'specificRepoInfo' | translate }}</p>
|
||||
</div>
|
||||
<div class="logs-container">
|
||||
<iframe [src]="getRepositoryIframeUrl()" width="100%" height="600" frameborder="0"
|
||||
class="repository-logs-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(!selectedRepositoryUuid) {
|
||||
<div class="no-repository-selected">
|
||||
<mat-icon class="no-data-icon">storage</mat-icon>
|
||||
<h3>{{ 'selectARepository' | translate }}</h3>
|
||||
<p>{{ 'selectRepoToView' | translate }}</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(selectedRepositoryUuid && errorRepositories[selectedRepositoryUuid]) {
|
||||
<div class="error-container">
|
||||
<div class="error-card">
|
||||
<mat-icon class="error-icon">error_outline</mat-icon>
|
||||
<h3>{{ 'connectionError' | translate }}</h3>
|
||||
<p>{{ 'cannotConnectRepo' | translate }}</p>
|
||||
<button mat-raised-button color="primary" (click)="retryRepositoryStatus(selectedRepositoryUuid)">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
{{ 'buttonRetry' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OgrepositoryStatusComponent } from './ogrepository-status.component';
|
||||
|
||||
describe('OgrepositoryStatusComponent', () => {
|
||||
let component: OgrepositoryStatusComponent;
|
||||
let fixture: ComponentFixture<OgrepositoryStatusComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [OgrepositoryStatusComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(OgrepositoryStatusComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-ogrepository-status',
|
||||
templateUrl: './ogrepository-status.component.html',
|
||||
styleUrl: './ogrepository-status.component.css'
|
||||
})
|
||||
export class OgrepositoryStatusComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
loading: boolean = false;
|
||||
|
||||
@Input() repositories: any[] = [];
|
||||
|
||||
repositoryStatuses: { [key: string]: any } = {};
|
||||
selectedRepositoryUuid: string = '';
|
||||
errorRepositories: { [key: string]: boolean } = {};
|
||||
|
||||
showLabels: boolean = true;
|
||||
isDoughnut: boolean = true;
|
||||
colorScheme: any = {
|
||||
domain: ['#df200d', '#26a700']
|
||||
};
|
||||
view: [number, number] = [400, 220];
|
||||
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private toastService: ToastrService,
|
||||
private http: HttpClient,
|
||||
private sanitizer: DomSanitizer
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Parent passes repositories list
|
||||
}
|
||||
|
||||
// removed loadRepositories function that fetched from API
|
||||
|
||||
loadRepositoryStatus(repositoryUuid: string, callback: (errorOccurred: boolean) => void): void {
|
||||
const timeoutId = setTimeout(() => {
|
||||
callback(true);
|
||||
}, 5000);
|
||||
|
||||
this.http.get<any>(`${this.baseUrl}/image-repositories/server/${repositoryUuid}/status`).subscribe(
|
||||
data => {
|
||||
if (!data.success) {
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const details = data.details;
|
||||
const { disk, services, ram, cpu, processes } = details;
|
||||
|
||||
this.repositoryStatuses[repositoryUuid] = {
|
||||
disk: {
|
||||
...disk,
|
||||
used: parseFloat(disk.used.replace('GB', '')),
|
||||
available: parseFloat(disk.available.replace('GB', '')),
|
||||
total: parseFloat(disk.total.replace('GB', '')),
|
||||
percentage: disk.used_percentage
|
||||
},
|
||||
ram: {
|
||||
...ram,
|
||||
used: parseFloat(ram.used.replace('GB', '')),
|
||||
available: parseFloat(ram.available.replace('GB', '')),
|
||||
total: parseFloat(ram.total.replace('GB', '')),
|
||||
percentage: ram.used_percentage
|
||||
},
|
||||
cpu: {
|
||||
used_percentage: cpu.used_percentage
|
||||
},
|
||||
services: services,
|
||||
processes: processes
|
||||
};
|
||||
clearTimeout(timeoutId);
|
||||
callback(false);
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al cargar el estado del repositorio');
|
||||
clearTimeout(timeoutId);
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onRepositoryChange(repositoryUuid: string): void {
|
||||
this.selectedRepositoryUuid = repositoryUuid;
|
||||
if (repositoryUuid && !this.repositoryStatuses[repositoryUuid]) {
|
||||
this.loadRepositoryStatus(repositoryUuid, (errorOccurred: boolean) => {
|
||||
if (errorOccurred) {
|
||||
this.errorRepositories[repositoryUuid] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedRepositoryName(): string {
|
||||
const selectedRepo = this.repositories.find(repo => repo.uuid === this.selectedRepositoryUuid);
|
||||
return selectedRepo ? selectedRepo.name : '';
|
||||
}
|
||||
|
||||
getRepositoryIframeUrl(): SafeResourceUrl {
|
||||
const repositoryName = this.getSelectedRepositoryName();
|
||||
if (repositoryName) {
|
||||
const encodedName = encodeURIComponent(repositoryName);
|
||||
const url = `${this.configService.logUrl}/d-solo/ogrepo-logs/ogrepo-logs?orgId=1&timezone=browser&refresh=5s&var-query0=&editIndex=0&var-hostname=${encodedName}&theme=dark&panelId=1&__feature.dashboardSceneSolo`;
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
}
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl('');
|
||||
}
|
||||
|
||||
retryRepositoryStatus(repositoryUuid: string): void {
|
||||
this.loadRepositoryStatus(repositoryUuid, (errorOccurred: boolean) => {
|
||||
this.errorRepositories[repositoryUuid] = errorOccurred;
|
||||
if (!errorOccurred) {
|
||||
this.toastService.success(this.translate.instant('successfullyUpdatedRepositoryStatus'));
|
||||
} else {
|
||||
this.toastService.error(this.translate.instant('errorLoadingState1'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scrollToLogs(): void {
|
||||
const checkAndScroll = () => {
|
||||
const logsSection = document.getElementById('repository-logs-section');
|
||||
if (logsSection) {
|
||||
logsSection.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (checkAndScroll()) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,15 @@
|
|||
.dashboard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
gap: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ===== SECCIÓN DE RECURSOS ===== */
|
||||
.resources-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 1.5rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Layout específico para repositorios - gráficas en una sola fila */
|
||||
|
|
@ -25,8 +25,8 @@
|
|||
|
||||
.resource-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
|
|
@ -59,8 +59,8 @@
|
|||
.resource-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.resource-icon {
|
||||
|
|
@ -173,14 +173,14 @@
|
|||
/* ===== SECCIÓN DE SERVICIOS ===== */
|
||||
.services-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
|
|
@ -194,8 +194,8 @@
|
|||
.service-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
|
|
@ -298,13 +298,13 @@
|
|||
.data-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.data-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
|
|
@ -318,8 +318,8 @@
|
|||
.data-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.data-icon {
|
||||
|
|
|
|||
|
|
@ -17,19 +17,19 @@
|
|||
</div>
|
||||
<div class="resource-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'totalLabel' | translate }}:</span>
|
||||
<span class="info-label">{{ 'labelDiskTotal' | translate }}:</span>
|
||||
<span class="info-value">{{ isRepository ? diskUsage.total : formatBytes(diskUsage.total) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'usedLabel' | translate }}:</span>
|
||||
<span class="info-label">{{ 'labelDiskUsed' | translate }}:</span>
|
||||
<span class="info-value">{{ isRepository ? diskUsage.used : formatBytes(diskUsage.used) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'availableLabel' | translate }}:</span>
|
||||
<span class="info-label">{{ 'labelAvailable' | translate }}:</span>
|
||||
<span class="info-value">{{ isRepository ? diskUsage.available : formatBytes(diskUsage.available) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'usedPercentageLabel' | translate }}:</span>
|
||||
<span class="info-label">{{ 'labelDiskUsed' | translate }}:</span>
|
||||
<span class="info-value usage-percentage">{{ isRepository ? diskUsage.used_percentage : diskUsage.percentage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -51,19 +51,19 @@
|
|||
</div>
|
||||
<div class="resource-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'totalLabel' | translate }}:</span>
|
||||
<span class="info-label">{{ 'labelDiskTotal' | translate }}:</span>
|
||||
<span class="info-value">{{ ramUsage.total }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'usedLabel' | translate }}:</span>
|
||||
<span class="info-label">{{ 'labelDiskUsed' | translate }}:</span>
|
||||
<span class="info-value">{{ ramUsage.used }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'availableLabel' | translate }}:</span>
|
||||
<span class="info-label">{{ 'labelAvailable' | translate }}:</span>
|
||||
<span class="info-value">{{ ramUsage.available }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'usedPercentageLabel' | translate }}:</span>
|
||||
<span class="info-label">{{ 'labelDiskUsed' | translate }}:</span>
|
||||
<span class="info-value usage-percentage">{{ ramUsage.used_percentage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
<div class="cpu-usage-display">
|
||||
<div class="cpu-circle">
|
||||
<div class="cpu-percentage">{{ cpuUsage.used_percentage }}</div>
|
||||
<div class="cpu-label">Uso actual</div>
|
||||
<div class="cpu-label">{{ 'currentUsage' | translate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
<div class="service-icon">
|
||||
<mat-icon>settings</mat-icon>
|
||||
</div>
|
||||
<h3 class="service-title">{{ 'servicesTitle' | translate }}</h3>
|
||||
<h3 class="service-title">{{ 'labelServices' | translate }}</h3>
|
||||
</div>
|
||||
<div class="service-list">
|
||||
<div class="service-item" *ngFor="let service of getServices()">
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
<div class="service-icon">
|
||||
<mat-icon>list_alt</mat-icon>
|
||||
</div>
|
||||
<h3 class="service-title">{{ 'processes' | translate }}</h3>
|
||||
<h3 class="service-title">{{ 'labelProcesses' | translate }}</h3>
|
||||
</div>
|
||||
<div class="service-list">
|
||||
<div class="service-item" *ngFor="let process of getProcesses()">
|
||||
|
|
@ -144,10 +144,10 @@
|
|||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'idLabel' | translate }}</th>
|
||||
<th>{{ 'labelId' | translate }}</th>
|
||||
<th>{{ 'kernelLabel' | translate }}</th>
|
||||
<th>{{ 'architectureLabel' | translate }}</th>
|
||||
<th>{{ 'revisionLabel' | translate }}</th>
|
||||
<th>{{ 'labelArchitecture' | translate }}</th>
|
||||
<th>{{ 'labelRevision' | translate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -173,11 +173,11 @@
|
|||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'idLabel' | translate }}</th>
|
||||
<th>{{ 'bootFileNameLabel' | translate }}</th>
|
||||
<th>{{ 'nextServerLabel' | translate }}</th>
|
||||
<th>{{ 'ipLabel' | translate }}</th>
|
||||
<th>{{ 'clientsLabel' | translate }}</th>
|
||||
<th>{{ 'labelId' | translate }}</th>
|
||||
<th>{{ 'labelBootFileName' | translate }}</th>
|
||||
<th>{{ 'labelNextServer' | translate }}</th>
|
||||
<th>{{ 'labelIp' | translate }}</th>
|
||||
<th>{{ 'labelClients' | translate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="table-header-container">
|
||||
<h2 class="title" i18n="@@adminImagesTitle">Discos/Particiones</h2>
|
||||
<h2 class="title" i18n="@@adminImagesTitle">{{ 'labelDisksPartitions' | translate }}</h2>
|
||||
<mat-chip *ngIf="clientData.firmwareType" class="firmware-chip">
|
||||
<mat-icon>memory</mat-icon>
|
||||
{{ clientData.firmwareType }}
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
<div class="table-container">
|
||||
<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>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header | translate }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef !== 'size'">
|
||||
{{ column.cell(image) }}
|
||||
|
|
|
|||
|
|
@ -14,21 +14,20 @@
|
|||
<div class="destination-badge">
|
||||
<mat-icon class="destination-icon">cloud_upload</mat-icon>
|
||||
<div class="destination-content">
|
||||
<span class="destination-label">Destino</span>
|
||||
<span class="destination-label">{{ 'labelDestination' | translate }}</span>
|
||||
<span class="destination-value">{{ selectedRepository?.name || 'No hay repositorio asociado' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button class="action-button" id="execute-button" [disabled]="!selectedPartition || loading" (click)="save()">Ejecutar</button>
|
||||
<button class="action-button" id="execute-button" [disabled]="!selectedPartition || loading" (click)="save()">{{ 'buttonExecute' | translate }}</button>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button class="action-button" color="accent"
|
||||
[disabled]="!isFormValid()"
|
||||
(click)="openScheduleModal()">
|
||||
Opciones de programación
|
||||
</button>
|
||||
{{ 'schedulingOptions' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -37,13 +36,12 @@
|
|||
<div class="form-section compact-section type-section">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>settings</mat-icon>
|
||||
Tipo de imagen
|
||||
</div>
|
||||
{{ 'imageType' | translate }}</div>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Tipo de imagen</mat-label>
|
||||
<mat-label>{{ 'imageType' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="imageType" class="full-width" (selectionChange)="onImageTypeSelected($event.value)">
|
||||
<mat-option [value]="'monolithic'">Monolítica</mat-option>
|
||||
<mat-option [value]="'git'">Git</mat-option>
|
||||
<mat-option [value]="'monolithic'">{{ 'labelMonolithic' | translate }}</mat-option>
|
||||
<mat-option [value]="'git'">{{ 'labelGit' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
|
@ -51,40 +49,39 @@
|
|||
<div class="form-section compact-section config-section" *ngIf="imageType === 'monolithic'">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>image</mat-icon>
|
||||
Configuración
|
||||
</div>
|
||||
{{ 'configurationTitle' | translate }}</div>
|
||||
|
||||
<div class="action-chips-container">
|
||||
<mat-chip-listbox [(ngModel)]="monolithicAction" required class="action-chip-listbox">
|
||||
<mat-chip-option value="create" class="action-chip create-chip firmware-chip" (click)="onMonolithicActionSelected({value: 'create'})">
|
||||
<span>Crear imagen</span>
|
||||
<span>{{ 'buttonCreateImage' | translate }}</span>
|
||||
</mat-chip-option>
|
||||
<mat-chip-option value="update" class="action-chip update-chip firmware-chip" (click)="onMonolithicActionSelected({value: 'update'})">
|
||||
<span>Actualizar imagen</span>
|
||||
<span>{{ 'buttonUpdateImage' | translate }}</span>
|
||||
</mat-chip-option>
|
||||
</mat-chip-listbox>
|
||||
</div>
|
||||
|
||||
<div *ngIf="monolithicAction === 'create'">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Nombre canónico</mat-label>
|
||||
<input matInput [(ngModel)]="name" placeholder="Nombre canónico. En minúscula y sin espacios" required>
|
||||
<mat-hint>Nombre para la nueva imagen</mat-hint>
|
||||
<mat-label>{{ 'canonicalName' | translate }}</mat-label>
|
||||
<input matInput [(ngModel)]="name" [placeholder]="'canonicalNameFormatHint' | translate" required>
|
||||
<mat-hint>{{ 'labelNewImageName' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="monolithicAction === 'update'">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione imagen</mat-label>
|
||||
<mat-label>{{ 'selectImagePrompt' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedImage" name="selectedImage" (selectionChange)="resetCanonicalName()" required>
|
||||
<mat-option [value]="null">Seleccionar imagen para actualizar</mat-option>
|
||||
<mat-option [value]="null">{{ 'selectImageToUpdate' | translate }}</mat-option>
|
||||
<mat-option *ngFor="let image of images" [value]="image">{{ image?.name }}</mat-option>
|
||||
</mat-select>
|
||||
<button *ngIf="selectedImage" mat-icon-button matSuffix aria-label="Clear client search"
|
||||
<button *ngIf="selectedImage" mat-icon-button matSuffix [attr.aria-label]="'clearClientSearch' | translate"
|
||||
(click)="selectedImage = null; resetCanonicalName()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
<mat-hint>Selecciona la imagen existente que quieres actualizar.</mat-hint>
|
||||
<mat-hint>{{ 'selectImageToUpdateHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -92,14 +89,13 @@
|
|||
<div class="form-section compact-section config-section" *ngIf="imageType === 'git'">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>code</mat-icon>
|
||||
Configuración Git
|
||||
</div>
|
||||
{{ 'gitConfiguration' | translate }}</div>
|
||||
|
||||
<div class="git-info-container" *ngIf="selectedPartition && imageType === 'git' && (hasValidGitData || loadingGitData)">
|
||||
<div class="git-info-card">
|
||||
<div class="git-info-header">
|
||||
<mat-icon class="git-info-icon">info</mat-icon>
|
||||
<span class="git-info-title">La imagen en caché a actualizar es:</span>
|
||||
<span class="git-info-title">{{ 'cacheImageToUpdateIs' | translate }}</span>
|
||||
<mat-spinner *ngIf="loadingGitData" diameter="20" class="git-loading-spinner"></mat-spinner>
|
||||
</div>
|
||||
<div *ngIf="!loadingGitData && hasValidGitData">
|
||||
|
|
@ -108,8 +104,7 @@
|
|||
<mat-form-field appearance="outline" class="git-form-field">
|
||||
<mat-label>
|
||||
<mat-icon class="git-item-icon">folder</mat-icon>
|
||||
Repositorio
|
||||
</mat-label>
|
||||
{{ 'labelRepository' | translate }}</mat-label>
|
||||
<input matInput [value]="gitData.repo" readonly>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
|
@ -117,8 +112,7 @@
|
|||
<mat-form-field appearance="outline" class="git-form-field">
|
||||
<mat-label>
|
||||
<mat-icon class="git-item-icon">account_tree</mat-icon>
|
||||
Rama origen
|
||||
</mat-label>
|
||||
{{ 'sourceBranch' | translate }}</mat-label>
|
||||
<input matInput [value]="gitData.branch" readonly>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
|
@ -126,12 +120,11 @@
|
|||
<mat-form-field appearance="outline" class="git-form-field">
|
||||
<mat-label>
|
||||
<mat-icon class="git-item-icon">call_split</mat-icon>
|
||||
Rama destino
|
||||
</mat-label>
|
||||
{{ 'destinationBranch' | translate }}</mat-label>
|
||||
<input matInput
|
||||
[(ngModel)]="destinationBranch"
|
||||
[readonly]="!isDestinationBranchEditable"
|
||||
placeholder="Nombre de la rama destino"
|
||||
[placeholder]="'destinationBranchNameLabel' | translate"
|
||||
[matTooltip]="isDestinationBranchEditable ? 'Guardar cambios' : 'Editar nombre de la rama destino'">
|
||||
<button mat-icon-button
|
||||
matSuffix
|
||||
|
|
@ -146,7 +139,7 @@
|
|||
<div class="repository-warning" *ngIf="repositoryNotFound">
|
||||
<mat-icon class="warning-icon">warning</mat-icon>
|
||||
<div class="warning-content">
|
||||
<div class="warning-title">Repositorio no encontrado</div>
|
||||
<div class="warning-title">{{ 'repositoryNotFound' | translate }}</div>
|
||||
<div class="warning-message">
|
||||
El repositorio "{{ gitData.repo }}" no existe en el listado de repositorios disponibles.
|
||||
Es posible que necesites crearlo primero.
|
||||
|
|
@ -155,18 +148,17 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="git-info-loading" *ngIf="loadingGitData">
|
||||
<span>Cargando información de Git...</span>
|
||||
<span>{{ 'loadingGitInfo' | translate }}</span>
|
||||
</div>
|
||||
<div class="git-info-empty" *ngIf="!loadingGitData && !hasValidGitData">
|
||||
<span>No se encontró información de Git para esta partición</span>
|
||||
<span>{{ 'noGitInfoForPartition' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="force-push-container" *ngIf="imageType === 'git' && hasValidGitData">
|
||||
<mat-checkbox [(ngModel)]="forcePush" class="force-push-checkbox">
|
||||
Opción 'Forzar push'
|
||||
</mat-checkbox>
|
||||
{{ 'forcePushOption' | translate }}</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="repository-header" *ngIf="!hasValidGitData">
|
||||
|
|
@ -175,24 +167,22 @@
|
|||
(click)="openCreateRepositoryModal()"
|
||||
[disabled]="creatingRepository">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span>Crear nuevo repositorio / SO</span>
|
||||
<span>{{ 'createNewRepoOs' | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="selector" *ngIf="!hasValidGitData">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccionar repositorio Git</mat-label>
|
||||
<mat-label>{{ 'selectGitRepository' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedGitRepository" (selectionChange)="onGitRepositorySelected($event.value)" required>
|
||||
<mat-option [value]="null">Seleccionar repositorio git / SO</mat-option>
|
||||
<mat-option [value]="null">{{ 'selectGitRepoOs' | translate }}</mat-option>
|
||||
<mat-option *ngFor="let repo of gitRepositories" [value]="repo">{{ repo.name }}</mat-option>
|
||||
</mat-select>
|
||||
<mat-spinner *ngIf="loadingGitRepositories" matSuffix diameter="20"></mat-spinner>
|
||||
<mat-hint>
|
||||
<mat-icon>info</mat-icon>
|
||||
Selecciona el repositorio git para obtener las imágenes disponibles.
|
||||
<span *ngIf="gitRepositories.length === 0" class="no-repositories-hint">
|
||||
No hay repositorios disponibles. Crea uno nuevo para continuar.
|
||||
</span>
|
||||
{{ 'selectGitRepoHint' | translate }}<span *ngIf="gitRepositories.length === 0" class="no-repositories-hint">
|
||||
{{ 'noReposAvailableCreateNew' | translate }}</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
|
@ -202,8 +192,7 @@
|
|||
<div class="form-section" #partitionSection id="partition-selection">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>storage</mat-icon>
|
||||
Selección de partición
|
||||
</div>
|
||||
{{ 'partitionSelection' | translate }}</div>
|
||||
|
||||
<div class="partition-table-container">
|
||||
<app-disk-partition-selector
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {Component, EventEmitter, OnInit, Output, ViewChild, ElementRef} from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {Component, EventEmitter, OnInit, Output, ViewChild, ElementRef } from '@angular/core';
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
|
@ -17,6 +19,7 @@ import {CreateTaskComponent} from "../../../../commands/commands-task/create-tas
|
|||
styleUrl: './create-image.component.css'
|
||||
})
|
||||
export class CreateClientImageComponent implements OnInit{
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
@Output() dataChange = new EventEmitter<any>();
|
||||
@ViewChild('partitionSection', { static: false }) partitionSection!: ElementRef;
|
||||
|
|
@ -69,27 +72,27 @@ export class CreateClientImageComponent implements OnInit{
|
|||
columns = [
|
||||
{
|
||||
columnDef: 'partitionNumber',
|
||||
header: 'Particion',
|
||||
header: 'partition',
|
||||
cell: (partition: any) => partition.partitionNumber
|
||||
},
|
||||
{
|
||||
columnDef: 'size',
|
||||
header: 'Tamaño',
|
||||
header: 'labelDiskSize',
|
||||
cell: (partition: any) => `${partition.size} MB`
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionCode',
|
||||
header: 'Tipo de partición',
|
||||
header: 'partitionType',
|
||||
cell: (partition: any) => partition.partitionCode
|
||||
},
|
||||
{
|
||||
columnDef: 'filesystem',
|
||||
header: 'Sistema de ficheros',
|
||||
header: 'fileSystem',
|
||||
cell: (partition: any) => partition.filesystem
|
||||
},
|
||||
{
|
||||
columnDef: 'operativeSystem',
|
||||
header: 'SO',
|
||||
header: 'sW',
|
||||
cell: (partition: any) => partition.operativeSystem?.name
|
||||
}
|
||||
];
|
||||
|
|
@ -139,7 +142,7 @@ export class CreateClientImageComponent implements OnInit{
|
|||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar los datos del cliente:', error);
|
||||
console.error(this.translate.instant('errorLoadingData1'), error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -155,7 +158,7 @@ export class CreateClientImageComponent implements OnInit{
|
|||
this.images = response['hydra:member'];
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar las imágenes:', error);
|
||||
console.error(this.translate.instant('errorLoadingImages3'), error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -186,7 +189,7 @@ export class CreateClientImageComponent implements OnInit{
|
|||
this.checkRepositoryExists();
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar los repositorios git:', error);
|
||||
console.error(this.translate.instant('errorLoadingRepositories2'), error);
|
||||
this.loadingGitRepositories = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -201,7 +204,7 @@ export class CreateClientImageComponent implements OnInit{
|
|||
this.loadingGitImageRepositories = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar las imágenes de repositorio git:', error);
|
||||
console.error(this.translate.instant('errorLoadingImages'), error);
|
||||
this.loadingGitImageRepositories = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -237,8 +240,8 @@ export class CreateClientImageComponent implements OnInit{
|
|||
}
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching branches', error);
|
||||
this.toastService.error('Error al cargar las ramas del repositorio');
|
||||
console.error(this.translate.instant('fetchingBranchesError'), error);
|
||||
this.toastService.error(this.translate.instant('errorLoadingBranches1'));
|
||||
this.loadingBranches = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -261,7 +264,7 @@ export class CreateClientImageComponent implements OnInit{
|
|||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.toastService.success('Rama creada correctamente');
|
||||
this.toastService.success(this.translate.instant('branchCreatedSuccessfully'));
|
||||
this.destinationBranch = result.branchName || result;
|
||||
this.loadBranches();
|
||||
}
|
||||
|
|
@ -270,7 +273,7 @@ export class CreateClientImageComponent implements OnInit{
|
|||
}
|
||||
|
||||
if (!this.selectedGitRepository) {
|
||||
this.toastService.error('Debe seleccionar un repositorio primero');
|
||||
this.toastService.error(this.translate.instant('youMustSelectARepositoryFirst'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +288,7 @@ export class CreateClientImageComponent implements OnInit{
|
|||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.toastService.success('Rama creada correctamente');
|
||||
this.toastService.success(this.translate.instant('branchCreatedSuccessfully'));
|
||||
this.destinationBranch = result.branchName || result;
|
||||
this.loadBranches();
|
||||
}
|
||||
|
|
@ -319,9 +322,9 @@ export class CreateClientImageComponent implements OnInit{
|
|||
this.loadingExistingImages = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar las imágenes existentes:', error);
|
||||
console.error(this.translate.instant('errorLoadingImages2'), error);
|
||||
this.loadingExistingImages = false;
|
||||
this.toastService.error('Error al cargar las imágenes existentes');
|
||||
this.toastService.error(this.translate.instant('errorLoadingImages1'));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -347,18 +350,18 @@ export class CreateClientImageComponent implements OnInit{
|
|||
|
||||
save(): void {
|
||||
if (!this.selectedPartition) {
|
||||
this.toastService.error('Debes seleccionar una partición');
|
||||
this.toastService.error(this.translate.instant('youMustSelectAPartition'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.imageType === 'monolithic') {
|
||||
if (this.monolithicAction === 'create' && !this.name) {
|
||||
this.toastService.error('Debes introducir un nombre canónico para la imagen');
|
||||
this.toastService.error(this.translate.instant('youMustEnterACanonicalName'));
|
||||
return;
|
||||
}
|
||||
if (this.monolithicAction === 'update' && !this.selectedImage) {
|
||||
this.toastService.error('Debes seleccionar una imagen para actualizar');
|
||||
this.toastService.error(this.translate.instant('youMustSelectAnImageTo'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -572,8 +575,8 @@ export class CreateClientImageComponent implements OnInit{
|
|||
}
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al cargar datos de Git:', error);
|
||||
this.toastService.error('Error al cargar información de Git');
|
||||
console.error(this.translate.instant('errorLoadingData'), error);
|
||||
this.toastService.error(this.translate.instant('errorLoadingInformation'));
|
||||
this.loadingGitData = false;
|
||||
}
|
||||
});
|
||||
|
|
@ -603,7 +606,7 @@ export class CreateClientImageComponent implements OnInit{
|
|||
this.loadingBranches = false;
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching branches for Git data', error);
|
||||
console.error(this.translate.instant('errorFetchingBranchesForGit'), error);
|
||||
this.loadingBranches = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -643,7 +646,7 @@ export class CreateClientImageComponent implements OnInit{
|
|||
let scope = 'clients';
|
||||
|
||||
if (!this.client) {
|
||||
this.toastService.error('No se ha cargado la información del cliente');
|
||||
this.toastService.error(this.translate.instant('hasNotBeenLoaded'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -663,7 +666,7 @@ export class CreateClientImageComponent implements OnInit{
|
|||
dialogRef.afterClosed().subscribe((result: any) => {
|
||||
if (result) {
|
||||
if (!this.selectedPartition) {
|
||||
this.toastService.error('Debe seleccionar una partición');
|
||||
this.toastService.error(this.translate.instant('youMustSelectAPartition'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -699,7 +702,7 @@ export class CreateClientImageComponent implements OnInit{
|
|||
type: 'create-image',
|
||||
}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Tarea de creación de imagen programada con éxito');
|
||||
this.toastService.success(this.translate.instant('imageCreationTask'));
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al programar la tarea');
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<h2 mat-dialog-title>Crear nuevo repositorio de imágenes git</h2>
|
||||
<h2 mat-dialog-title>{{ 'createNewGitRepo' | translate }}</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<form [formGroup]="repositoryForm" (ngSubmit)="save()" class="repository-form">
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>Nombre del repositorio</mat-label>
|
||||
<mat-label>{{ 'labelRepoName' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
|
||||
<div mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="close()">Cancelar</button>
|
||||
<button class="submit-button" (click)="save()">Guardar</button>
|
||||
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="save()">{{ 'labelSaveButton' | translate }}</button>
|
||||
</div>
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
|
|
@ -11,6 +13,7 @@ import { ConfigService } from '@services/config.service';
|
|||
styleUrl: './create-repository-modal.component.css'
|
||||
})
|
||||
export class CreateRepositoryModalComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
repositoryForm: FormGroup<any>;
|
||||
loading: boolean = false;
|
||||
|
|
@ -45,7 +48,7 @@ export class CreateRepositoryModalComponent implements OnInit {
|
|||
|
||||
this.http.post(`${this.baseUrl}/git-repositories`, payload).subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Repositorio creado correctamente');
|
||||
this.toastService.success(this.translate.instant('successfullyCreatedRepository'));
|
||||
this.dialogRef.close(response);
|
||||
},
|
||||
error: (error) => {
|
||||
|
|
@ -54,7 +57,7 @@ export class CreateRepositoryModalComponent implements OnInit {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
this.toastService.error('Por favor, complete todos los campos requeridos');
|
||||
this.toastService.error(this.translate.instant('pleaseCompleteAll'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,45 +9,40 @@
|
|||
<div class="destination-badge">
|
||||
<mat-icon class="destination-icon">cloud_download</mat-icon>
|
||||
<div class="destination-content">
|
||||
<span class="destination-label">Destino</span>
|
||||
<span class="destination-label">{{ 'labelDestination' | translate }}</span>
|
||||
<span class="destination-value">{{ runScriptTitle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button class="action-button" id="execute-button" [disabled]="!isFormValid()" (click)="save()">Ejecutar</button>
|
||||
<button class="action-button" id="execute-button" [disabled]="!isFormValid()" (click)="save()">{{ 'buttonExecute' | translate }}</button>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<button class="action-button" (click)="generateOgInstructions()">
|
||||
Generar instrucciones
|
||||
</button>
|
||||
{{ 'generateInstructions' | translate }}</button>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<button class="action-button" color="accent" [disabled]="!isFormValid()" (click)="openScheduleModal()">
|
||||
Opciones de programación
|
||||
</button>
|
||||
{{ 'schedulingOptions' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="select-container">
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> Clientes </mat-panel-title>
|
||||
<mat-panel-title> {{ 'labelClients' | translate }}</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
Listado de clientes donde se desplegará la imagen
|
||||
<mat-icon>desktop_windows</mat-icon>
|
||||
{{ 'clientListDeployImage' | translate }}<mat-icon>desktop_windows</mat-icon>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div class="section-hint" (click)="$event.stopPropagation()">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>
|
||||
Puedes marcar o desmarcar un cliente haciendo clic en el área del ordenador. Además, selecciona con "Modelo" el
|
||||
equipo que actuará como modelo.
|
||||
</span>
|
||||
{{ 'markClientModelInstruction' | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
|
|
@ -74,8 +69,7 @@
|
|||
<div class="model-selector" (click)="$event.stopPropagation()">
|
||||
<mat-radio-group [(ngModel)]="selectedModelClient" (change)="onModelSelected(selectedModelClient)">
|
||||
<mat-radio-button [value]="client" color="primary" (click)="$event.stopPropagation()">
|
||||
Modelo
|
||||
</mat-radio-button>
|
||||
{{ 'labelModel' | translate }}</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -92,15 +86,14 @@
|
|||
<div class="form-section compact-section image-config-section">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>image</mat-icon>
|
||||
Configuración de imagen
|
||||
</div>
|
||||
{{ 'imageConfiguration' | translate }}</div>
|
||||
|
||||
<div class="selector">
|
||||
<mat-form-field appearance="fill" class="half-width">
|
||||
<mat-label>Tipo de imagen</mat-label>
|
||||
<mat-label>{{ 'imageType' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="imageType" (selectionChange)="onImageTypeSelected($event.value)">
|
||||
<mat-option [value]="'monolithic'">Monolítica</mat-option>
|
||||
<mat-option [value]="'git'">Git</mat-option>
|
||||
<mat-option [value]="'monolithic'">{{ 'labelMonolithic' | translate }}</mat-option>
|
||||
<mat-option [value]="'git'">{{ 'labelGit' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
|
@ -108,13 +101,13 @@
|
|||
<!-- Campos monolítica -->
|
||||
<div class="monolithic-row" *ngIf="imageType === 'monolithic'">
|
||||
<mat-form-field appearance="fill" class="half-width">
|
||||
<mat-label>Seleccione método de deploy</mat-label>
|
||||
<mat-label>{{ 'selectDeployMethod' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedMethod" name="selectedMethod" (selectionChange)="validateImageSize()">
|
||||
<mat-option *ngFor="let method of allMethods" [value]="method.value">{{ method.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione imagen</mat-label>
|
||||
<mat-label>{{ 'selectImagePrompt' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedImage" name="selectedImage" [disabled]="!imageType">
|
||||
<mat-option *ngFor="let image of images" [value]="image">
|
||||
<div class="unit-name"> {{ image.name }}</div>
|
||||
|
|
@ -127,7 +120,7 @@
|
|||
<!-- Campos Git -->
|
||||
<div *ngIf="imageType === 'git'" class="filters-row git-gap">
|
||||
<mat-form-field appearance="fill" style="width: 300px;">
|
||||
<mat-label>Seleccionar Repositorio</mat-label>
|
||||
<mat-label>{{ 'buttonSelectRepository' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedGitRepository" (selectionChange)="onGitRepositoryChange($event.value)"
|
||||
[disabled]="loadingRepositories">
|
||||
<mat-option *ngFor="let repo of repositories" [value]="repo">
|
||||
|
|
@ -138,7 +131,7 @@
|
|||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" style="width: 300px;">
|
||||
<mat-label>Seleccionar Rama</mat-label>
|
||||
<mat-label>{{ 'buttonSelectBranch' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedBranch" (selectionChange)="onGitBranchChange()"
|
||||
[disabled]="loadingBranches || !selectedRepository">
|
||||
<mat-option *ngFor="let branch of branches" [value]="branch">
|
||||
|
|
@ -149,10 +142,10 @@
|
|||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" style="width: 200px;">
|
||||
<mat-label>Tag</mat-label>
|
||||
<mat-label>{{ 'labelTag' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="showOnlyTagged" (selectionChange)="onTagFilterChange()" [disabled]="!commits.length">
|
||||
<mat-option [value]="false">No</mat-option>
|
||||
<mat-option [value]="true">Sí</mat-option>
|
||||
<mat-option [value]="false">{{ 'buttonNo' | translate }}</mat-option>
|
||||
<mat-option [value]="true">{{ 'buttonYes' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
|
@ -175,8 +168,7 @@
|
|||
<span class="summary-text">{{ getMethodConfigSummary() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="method-config-pending">
|
||||
} @else {<div class="method-config-pending">
|
||||
<div class="pending-message">
|
||||
<mat-icon class="pending-icon">warning</mat-icon>
|
||||
<span class="pending-text">Se requiere configurar los parámetros de {{ getMethodLabel() }}</span>
|
||||
|
|
@ -204,15 +196,15 @@
|
|||
<div class="form-section-row deploy-mode-row">
|
||||
<div class="deploy-mode-label">
|
||||
<mat-icon>cached</mat-icon>
|
||||
<span>Modo de operación</span>
|
||||
<span>{{ 'operationMode' | translate }}</span>
|
||||
</div>
|
||||
<mat-radio-group [(ngModel)]="deployMode" name="deployMode" class="deploy-mode-radio-group">
|
||||
<mat-radio-button value="deploy" color="primary" class="deploy-mode-radio">
|
||||
<div class="deploy-mode-option">
|
||||
<mat-icon>storage</mat-icon>
|
||||
<div>
|
||||
<span class="deploy-mode-option-title">Despliegue completo</span>
|
||||
<span class="deploy-mode-option-desc">Actualiza caché y restaura partición</span>
|
||||
<span class="deploy-mode-option-title">{{ 'fullDeployment' | translate }}</span>
|
||||
<span class="deploy-mode-option-desc">{{ 'updateCacheRestorePartition' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</mat-radio-button>
|
||||
|
|
@ -220,8 +212,8 @@
|
|||
<div class="deploy-mode-option">
|
||||
<mat-icon>download</mat-icon>
|
||||
<div>
|
||||
<span class="deploy-mode-option-title">Solo actualizar caché</span>
|
||||
<span class="deploy-mode-option-desc">Descarga imagen sin restaurar partición</span>
|
||||
<span class="deploy-mode-option-title">{{ 'onlyUpdateCache' | translate }}</span>
|
||||
<span class="deploy-mode-option-desc">{{ 'downloadImageNoRestore' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</mat-radio-button>
|
||||
|
|
@ -234,15 +226,13 @@
|
|||
<div class="form-section compact-section partition-select-section" id="partition-selection">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>storage</mat-icon>
|
||||
Selección de partición
|
||||
</div>
|
||||
{{ 'partitionSelection' | translate }}</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;">
|
||||
{{ 'generatedInstructions' | translate }}<button mat-icon-button (click)="showInstructions = false" style="float: right;">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-card-title>
|
||||
|
|
@ -267,8 +257,7 @@
|
|||
<div class="form-section compact-section partition-select-section" id="partition-info">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>info</mat-icon>
|
||||
Estado actual de particiones
|
||||
</div>
|
||||
{{ 'currentPartitionState' | translate }}</div>
|
||||
<div class="partition-table-container">
|
||||
<app-disk-partition-selector
|
||||
[(selectedPartition)]="selectedPartition"
|
||||
|
|
@ -288,13 +277,12 @@
|
|||
<div *ngIf="imageType === 'git' && filteredCommits.length > 0" class="form-section compact-section">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>history</mat-icon>
|
||||
Selección de commit
|
||||
</div>
|
||||
{{ 'commitSelection' | translate }}</div>
|
||||
|
||||
<div class="commits-table-container">
|
||||
<table mat-table [dataSource]="filteredCommits" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef>Seleccionar</th>
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'buttonSelect' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let commit">
|
||||
<mat-radio-group [(ngModel)]="selectedCommit" name="selectedCommit">
|
||||
<mat-radio-button [value]="commit"></mat-radio-button>
|
||||
|
|
@ -302,7 +290,7 @@
|
|||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="hexsha">
|
||||
<th mat-header-cell *matHeaderCellDef>Commit ID</th>
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'commitId' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let commit">
|
||||
<code style="background-color: #f5f5f5; padding: 2px 4px; border-radius: 3px; font-family: monospace;">
|
||||
{{ commit.hexsha }}
|
||||
|
|
@ -310,22 +298,22 @@
|
|||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="message">
|
||||
<th mat-header-cell *matHeaderCellDef>Mensaje</th>
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labelMessage' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let commit">{{ commit.message }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="committed_date">
|
||||
<th mat-header-cell *matHeaderCellDef>Fecha</th>
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labelDate' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let commit">{{ commit.committed_date * 1000 | date:'dd/MM/yyyy HH:mm:ss' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="tags">
|
||||
<th mat-header-cell *matHeaderCellDef>Tags</th>
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labelTags' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let commit">
|
||||
<mat-chip-list>
|
||||
<mat-chip *ngFor="let tag of commit.tags" color="primary" selected class="custom-tag-chip"
|
||||
style="background: rgba(102, 126, 234, 0.1) !important; color: #667eea !important; border: 1px solid rgba(102, 126, 234, 0.3) !important; border-radius: 8px !important; font-weight: 500 !important; font-size: 10px !important; padding: 2px 6px !important; margin: 1px !important; box-shadow: none !important;">
|
||||
{{ tag }}
|
||||
</mat-chip>
|
||||
<span *ngIf="!commit.tags || commit.tags.length === 0" class="no-tags-text">Sin tags</span>
|
||||
<span *ngIf="!commit.tags || commit.tags.length === 0" class="no-tags-text">{{ 'noTags' | translate }}</span>
|
||||
</mat-chip-list>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||
import { MatTableDataSource } from "@angular/material/table";
|
||||
import { SelectionModel } from "@angular/cdk/collections";
|
||||
|
|
@ -16,6 +18,7 @@ import { QueueConfirmationModalComponent } from "../../../../../shared/queue-con
|
|||
styleUrl: './deploy-image.component.css'
|
||||
})
|
||||
export class DeployImageComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
@Output() dataChange = new EventEmitter<any>();
|
||||
|
||||
|
|
@ -75,22 +78,22 @@ export class DeployImageComponent implements OnInit {
|
|||
columns = [
|
||||
{
|
||||
columnDef: 'partitionNumber',
|
||||
header: 'Partición',
|
||||
header: 'labelPartition',
|
||||
cell: (partition: any) => `${partition.partitionNumber}`
|
||||
},
|
||||
{
|
||||
columnDef: 'size',
|
||||
header: 'Tamaño',
|
||||
header: 'labelDiskSize',
|
||||
cell: (partition: any) => `${partition.size} MB`
|
||||
},
|
||||
{
|
||||
columnDef: 'filesystem',
|
||||
header: 'Sistema de ficheros',
|
||||
header: 'fileSystem',
|
||||
cell: (partition: any) => `${partition.filesystem}`
|
||||
},
|
||||
{
|
||||
columnDef: 'operativeSystem',
|
||||
header: 'SO',
|
||||
header: 'sW',
|
||||
cell: (partition: any) => `${partition.operativeSystem?.name}`
|
||||
}
|
||||
];
|
||||
|
|
@ -229,7 +232,7 @@ export class DeployImageComponent implements OnInit {
|
|||
this.loadImages();
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar las particiones:', error);
|
||||
console.error(this.translate.instant('errorLoadingPartitions'), error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -261,7 +264,7 @@ export class DeployImageComponent implements OnInit {
|
|||
this.images = response?.['hydra:member'] || [];
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar las imágenes:', error);
|
||||
console.error(this.translate.instant('errorLoadingImages3'), error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -350,27 +353,27 @@ export class DeployImageComponent implements OnInit {
|
|||
const needsPartition = this.deployMode !== 'update-cache';
|
||||
|
||||
if (!this.selectedClients.length) {
|
||||
this.toastService.error('Debe seleccionar al menos un cliente');
|
||||
this.toastService.error(this.translate.instant('youMustSelectAtLeastOne'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.selectedImage && this.imageType !== 'git') {
|
||||
this.toastService.error('Debe seleccionar una imagen');
|
||||
this.toastService.error(this.translate.instant('youMustSelectAnImage'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.selectedMethod && this.imageType !== 'git') {
|
||||
this.toastService.error('Debe seleccionar un método');
|
||||
this.toastService.error(this.translate.instant('youMustSelectAMethod'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsPartition && !this.selectedPartition) {
|
||||
this.toastService.error('Debe seleccionar una partición');
|
||||
this.toastService.error(this.translate.instant('youMustSelectAPartition'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.imageType === 'git' && !this.selectedCommit) {
|
||||
this.toastService.error('Debe seleccionar un commit');
|
||||
this.toastService.error(this.translate.instant('youMustSelectACommit'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -381,7 +384,7 @@ export class DeployImageComponent implements OnInit {
|
|||
backdropClass: 'non-clickable-backdrop'
|
||||
});
|
||||
|
||||
this.toastService.info('Preparando petición de despliegue');
|
||||
this.toastService.info(this.translate.instant('preparingDeploymentRequest'));
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result !== undefined) {
|
||||
|
|
@ -428,7 +431,7 @@ export class DeployImageComponent implements OnInit {
|
|||
this.http.post(url, payload)
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Petición de despliegue enviada correctamente');
|
||||
this.toastService.success(this.translate.instant('deployRequestSentCorrectly'));
|
||||
this.loading = false;
|
||||
this.router.navigate(['/commands-logs']);
|
||||
},
|
||||
|
|
@ -573,7 +576,7 @@ export class DeployImageComponent implements OnInit {
|
|||
type: 'deploy-image',
|
||||
}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Script añadido con éxito a la tarea');
|
||||
this.toastService.success(this.translate.instant('scriptSuccessfullyAddedTo'));
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
|
|
@ -632,7 +635,7 @@ export class DeployImageComponent implements OnInit {
|
|||
}
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error al cargar los repositorios git');
|
||||
this.toastService.error(this.translate.instant('errorLoadingRepositories1'));
|
||||
this.loadingRepositories = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -662,7 +665,7 @@ export class DeployImageComponent implements OnInit {
|
|||
}
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error al cargar las ramas');
|
||||
this.toastService.error(this.translate.instant('errorLoadingBranches'));
|
||||
this.loadingBranches = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -694,7 +697,7 @@ export class DeployImageComponent implements OnInit {
|
|||
}
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error al cargar los commits');
|
||||
this.toastService.error(this.translate.instant('errorLoadingCommits'));
|
||||
this.loadingCommits = false;
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,17 +7,17 @@
|
|||
<!-- Multicast options -->
|
||||
<div *ngIf="isMulticast()" class="config-grid">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Dirección</mat-label>
|
||||
<mat-label>{{ 'labelAddress' | translate }}</mat-label>
|
||||
<input matInput [(ngModel)]="mcastIp" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Puerto</mat-label>
|
||||
<mat-label>{{ 'labelPort' | translate }}</mat-label>
|
||||
<input matInput [(ngModel)]="mcastPort" type="number" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Modo Multicast</mat-label>
|
||||
<mat-label>{{ 'labelMcastMode' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="mcastMode" required>
|
||||
<mat-option *ngFor="let option of data.multicastModeOptions" [value]="option.value">
|
||||
{{ option.name }}
|
||||
|
|
@ -26,17 +26,17 @@
|
|||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Velocidad</mat-label>
|
||||
<mat-label>{{ 'labelSpeed' | translate }}</mat-label>
|
||||
<input matInput [(ngModel)]="mcastSpeed" type="number" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Máx. Clientes</mat-label>
|
||||
<mat-label>{{ 'maxClients' | translate }}</mat-label>
|
||||
<input matInput [(ngModel)]="mcastMaxClients" type="number" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Tiempo máx. espera</mat-label>
|
||||
<mat-label>{{ 'maxWaitTime' | translate }}</mat-label>
|
||||
<input matInput [(ngModel)]="mcastMaxTime" type="number" required>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
<!-- P2P options -->
|
||||
<div *ngIf="isP2P()" class="config-grid">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Modo P2P</mat-label>
|
||||
<mat-label>{{ 'labelP2pMode' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="p2pMode" required>
|
||||
<mat-option *ngFor="let option of data.p2pModeOptions" [value]="option.value">
|
||||
{{ option.name }}
|
||||
|
|
@ -53,15 +53,14 @@
|
|||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" *ngIf="p2pMode === 'seeder'">
|
||||
<mat-label>Semilla (minutos)</mat-label>
|
||||
<mat-label>{{ 'seedMinutes' | translate }}</mat-label>
|
||||
<input matInput [(ngModel)]="p2pTime" type="number" required>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button (click)="onCancel()">Cancelar</button>
|
||||
<button mat-button (click)="onCancel()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button mat-flat-button color="primary" [disabled]="!isValid()" (click)="onSave()">
|
||||
Guardar configuración
|
||||
</button>
|
||||
{{ 'buttonSaveConfig' | translate }}</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
|||
|
|
@ -9,30 +9,27 @@
|
|||
<div class="destination-badge">
|
||||
<mat-icon class="destination-icon">cloud_download</mat-icon>
|
||||
<div class="destination-content">
|
||||
<span class="destination-label">Destino</span>
|
||||
<span class="destination-label">{{ 'labelDestination' | translate }}</span>
|
||||
<span class="destination-value">{{ runScriptTitle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button class="action-button" [disabled]="!selectedModelClient || !allSelected || !selectedDisk || (selectedDisk.totalDiskSize - selectedDisk.used) <= 0 || partitionValidationStatus === 'error'" (click)="save()">Ejecutar</button>
|
||||
<button class="action-button" [disabled]="!selectedModelClient || !allSelected || !selectedDisk || (selectedDisk.totalDiskSize - selectedDisk.used) <= 0 || partitionValidationStatus === 'error'" (click)="save()">{{ 'buttonExecute' | translate }}</button>
|
||||
<button class="action-button secondary-button" (click)="generateInstructions()">
|
||||
Generar instrucciones
|
||||
</button>
|
||||
{{ 'generateInstructions' | translate }}</button>
|
||||
<button class="action-button" color="accent" [disabled]="!selectedModelClient || !allSelected || !selectedDisk || (selectedDisk.totalDiskSize - selectedDisk.used) <= 0" (click)="openScheduleModal()">
|
||||
Opciones de programación
|
||||
</button>
|
||||
{{ 'schedulingOptions' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="showInstructions" class="instructions-box instructions-top" (click)="$event.stopPropagation()">
|
||||
<mat-card class="instructions-card">
|
||||
<mat-card-title>
|
||||
Script generado
|
||||
<span style="float: right;">
|
||||
<button mat-icon-button matTooltip="Copiar" (click)="copyInstructions()"><mat-icon>content_copy</mat-icon></button>
|
||||
<button mat-icon-button matTooltip="Cerrar" (click)="showInstructions = false"><mat-icon>close</mat-icon></button>
|
||||
{{ 'generatedScript' | translate }}<span style="float: right;">
|
||||
<button mat-icon-button [matTooltip]="'buttonCopy' | translate" (click)="copyInstructions()"><mat-icon>content_copy</mat-icon></button>
|
||||
<button mat-icon-button [matTooltip]="'buttonClose' | translate" (click)="showInstructions = false"><mat-icon>close</mat-icon></button>
|
||||
</span>
|
||||
</mat-card-title>
|
||||
<mat-card-content>
|
||||
|
|
@ -44,18 +41,16 @@
|
|||
<div class="select-container">
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> Clientes </mat-panel-title>
|
||||
<mat-panel-title> {{ 'labelClients' | translate }}</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
Listado de clientes donde se realizará el particionado
|
||||
<mat-icon>desktop_windows</mat-icon>
|
||||
{{ 'clientListPartition' | translate }}<mat-icon>desktop_windows</mat-icon>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div class="section-hint" (click)="$event.stopPropagation()">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>
|
||||
Puedes marcar o desmarcar un cliente haciendo clic en el área del ordenador. Además, selecciona con "Modelo" el equipo que actuará como modelo.
|
||||
</span>
|
||||
{{ 'markClientModelInstruction2' | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
|
|
@ -90,8 +85,7 @@
|
|||
<mat-radio-button [value]="client"
|
||||
color="primary"
|
||||
(click)="$event.stopPropagation()">
|
||||
Modelo
|
||||
</mat-radio-button>
|
||||
{{ 'labelModel' | translate }}</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
|
|
@ -105,7 +99,7 @@
|
|||
<div class="disk-and-partitions-container" id="disk-selector">
|
||||
<div class="disk-toolbar">
|
||||
<mat-form-field appearance="fill" class="disk-toolbar-select">
|
||||
<mat-label>Disco</mat-label>
|
||||
<mat-label>{{ 'labelDisk' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedDiskNumber" (selectionChange)="onDiskSelectionChange()">
|
||||
<mat-option *ngFor="let disk of disks" [value]="disk.diskNumber">
|
||||
<div class="disk-option">
|
||||
|
|
@ -123,15 +117,15 @@
|
|||
|
||||
<div class="disk-toolbar-info" *ngIf="selectedDisk">
|
||||
<div class="disk-stat">
|
||||
<span class="disk-stat-label">Usado</span>
|
||||
<span class="disk-stat-label">{{ 'labelDiskUsed' | translate }}</span>
|
||||
<span class="disk-stat-value used">{{ selectedDisk.used | number:'1.0-0' }} MB</span>
|
||||
</div>
|
||||
<div class="disk-stat">
|
||||
<span class="disk-stat-label">Libre</span>
|
||||
<span class="disk-stat-label">{{ 'labelFree' | translate }}</span>
|
||||
<span class="disk-stat-value free" [ngClass]="{'warning': (selectedDisk.used / selectedDisk.totalDiskSize) >= 0.9 && (selectedDisk.used / selectedDisk.totalDiskSize) < 1, 'danger': (selectedDisk.used / selectedDisk.totalDiskSize) >= 1}">{{ (selectedDisk.totalDiskSize - selectedDisk.used) | number:'1.0-0' }} MB</span>
|
||||
</div>
|
||||
<div class="disk-stat">
|
||||
<span class="disk-stat-label">Total</span>
|
||||
<span class="disk-stat-label">{{ 'labelDiskTotal' | translate }}</span>
|
||||
<span class="disk-stat-value">{{ selectedDisk.totalDiskSize | number:'1.0-0' }} MB</span>
|
||||
</div>
|
||||
<div class="disk-usage-bar-inline">
|
||||
|
|
@ -154,15 +148,14 @@
|
|||
</div>
|
||||
|
||||
<button class="action-button add-partition-btn" *ngIf="selectedDisk" (click)="addPartition(selectedDisk.diskNumber)">
|
||||
<mat-icon>add</mat-icon> Añadir partición
|
||||
</button>
|
||||
<mat-icon>add</mat-icon> {{ 'addPartition' | translate }}</button>
|
||||
</div>
|
||||
|
||||
<div class="no-disks-message" *ngIf="!disks || disks.length === 0">
|
||||
<mat-icon class="warning-icon">warning</mat-icon>
|
||||
<div class="message-text">
|
||||
<span class="message-title">No hay discos disponibles</span>
|
||||
<span class="message-subtitle">Asegúrate de que el cliente modelo tenga discos configurados</span>
|
||||
<span class="message-title">{{ 'noDisksAvailable' | translate }}</span>
|
||||
<span class="message-subtitle">{{ 'ensureModelClientHasDisks' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -176,7 +169,7 @@
|
|||
<div class="validation-status" [ngClass]="partitionValidationStatus">
|
||||
<mat-icon *ngIf="partitionValidationStatus === 'loading'" class="validation-icon loading">hourglass_empty</mat-icon>
|
||||
<mat-icon *ngIf="partitionValidationStatus === 'success'" class="validation-icon success">check_circle</mat-icon>
|
||||
<mat-icon *ngIf="partitionValidationStatus === 'error'" class="validation-icon error">error</mat-icon>
|
||||
<mat-icon *ngIf="partitionValidationStatus === 'error'" class="validation-icon error">{{ 'labelError' | translate }}</mat-icon>
|
||||
<span class="validation-message" *ngIf="partitionValidationMessage">{{ partitionValidationMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -184,12 +177,12 @@
|
|||
<table class="partition-table" id="partition-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Partición</th>
|
||||
<th>Tipo partición</th>
|
||||
<th>S. ficheros</th>
|
||||
<th>Tamaño (MB)</th>
|
||||
<th>Tamaño (%)</th>
|
||||
<th>Formatear</th>
|
||||
<th>{{ 'labelPartition' | translate }}</th>
|
||||
<th>{{ 'partitionType' | translate }}</th>
|
||||
<th>{{ 'labelFsShort' | translate }}</th>
|
||||
<th>{{ 'labelSizeMb' | translate }}</th>
|
||||
<th>{{ 'labelSizePercentage' | translate }}</th>
|
||||
<th>{{ 'buttonFormat' | translate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -232,7 +225,7 @@
|
|||
|
||||
<div class="chart-container" *ngIf="selectedDisk" #chartContainer>
|
||||
<div class="chart-header">
|
||||
<h3>Distribución de Particiones</h3>
|
||||
<h3>{{ 'partitionDistribution' | translate }}</h3>
|
||||
</div>
|
||||
<div class="chart-wrapper">
|
||||
<ngx-charts-pie-chart
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {Component, EventEmitter, Inject, Input, OnInit, Output, AfterViewInit, OnDestroy, ViewChild, ElementRef, HostListener} from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {Component, EventEmitter, Inject, Input, OnInit, Output, AfterViewInit, OnDestroy, ViewChild, ElementRef, HostListener } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import {ActivatedRoute, Router} from "@angular/router";
|
||||
|
|
@ -32,6 +34,7 @@ interface Partition {
|
|||
styleUrls: ['./partition-assistant.component.css']
|
||||
})
|
||||
export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDestroy{
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
private apiUrl: string;
|
||||
@Output() dataChange = new EventEmitter<any>();
|
||||
|
|
@ -190,7 +193,7 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
this.initializeDisks();
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar los datos del cliente:', error);
|
||||
console.error(this.translate.instant('errorLoadingData1'), error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -412,7 +415,7 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
this.resizeChart();
|
||||
}, 100);
|
||||
} else {
|
||||
this.toastService.error('No hay suficiente espacio libre en el disco para crear una nueva partición.');
|
||||
this.toastService.error(this.translate.instant('notEnoughFreeSpace'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -457,7 +460,7 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
}
|
||||
|
||||
if (!this.selectedDisk) {
|
||||
this.toastService.error('No se ha seleccionado un disco.');
|
||||
this.toastService.error(this.translate.instant('noOneHasBeenSelected'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -466,14 +469,14 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
.reduce((sum: any, partition: any) => sum + partition.size, 0);
|
||||
|
||||
if (totalPartitionSize > this.selectedDisk.totalDiskSize) {
|
||||
this.toastService.error('El tamaño total de las particiones en el disco seleccionado excede el tamaño total del disco.');
|
||||
this.toastService.error(this.translate.instant('theTotalSizeOfThe'));
|
||||
return;
|
||||
}
|
||||
|
||||
const modifiedPartitions = this.selectedDisk.partitions.filter((partition: { removed: any; format: any; }) => !partition.removed || partition.format);
|
||||
|
||||
if (modifiedPartitions.length === 0) {
|
||||
this.toastService.info('No hay cambios para guardar en el disco seleccionado.');
|
||||
this.toastService.info(this.translate.instant('noChangesToSave'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -507,13 +510,13 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
|
||||
this.http.post(this.apiUrl, bulkPayload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Particiones creadas exitosamente para el disco seleccionado.');
|
||||
this.toastService.success(this.translate.instant('partitionsSuccessfullyCreatedForThe'));
|
||||
this.loading = false;
|
||||
this.router.navigate(['/commands-logs']);
|
||||
},
|
||||
(error) => {
|
||||
this.loading = false;
|
||||
this.toastService.error('Error al crear las particiones.');
|
||||
this.toastService.error(this.translate.instant('errorCreatingPartitions'));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -680,7 +683,7 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
|
||||
if (modifiedPartitions.length === 0) {
|
||||
this.loading = false;
|
||||
this.toastService.info('No hay cambios para guardar en el disco seleccionado.');
|
||||
this.toastService.info(this.translate.instant('noChangesToSave'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -711,7 +714,7 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
type: 'partition-assistant',
|
||||
}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Script añadido con éxito a la tarea');
|
||||
this.toastService.success(this.translate.instant('scriptSuccessfullyAddedTo'));
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
|
|
@ -723,7 +726,7 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
|
||||
generateInstructions(): void {
|
||||
if (!this.selectedModelClient || !this.selectedDisk) {
|
||||
this.toastService.error('Selecciona un cliente modelo y un disco.');
|
||||
this.toastService.error(this.translate.instant('selectAModelClientAnd'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -777,8 +780,8 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
copyInstructions(): void {
|
||||
if (!this.generatedInstructions) { return; }
|
||||
navigator.clipboard?.writeText(this.generatedInstructions)
|
||||
.then(() => this.toastService.success('Script copiado al portapapeles'))
|
||||
.catch(() => this.toastService.error('No se pudo copiar el script.'));
|
||||
.then(() => this.toastService.success(this.translate.instant('scriptCopiedToClipboard')))
|
||||
.catch(() => this.toastService.error(this.translate.instant('couldNotCopy')));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -837,7 +840,7 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
});
|
||||
console.log('Scroll hacia botón ejecutar completado');
|
||||
} else {
|
||||
console.error('No se encontró el botón execute-button');
|
||||
console.error(this.translate.instant('theButtonWasNotFound'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,38 +9,35 @@
|
|||
<div class="destination-badge">
|
||||
<mat-icon class="destination-icon">terminal</mat-icon>
|
||||
<div class="destination-content">
|
||||
<span class="destination-label">Destino</span>
|
||||
<span class="destination-label">{{ 'labelDestination' | translate }}</span>
|
||||
<span class="destination-value">{{ runScriptTitle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button class="action-button" [disabled]="selectedClients.length < 1 || (commandType === 'existing' && !selectedScript) || (commandType === 'new' && !newScript.trim()) || loading" (click)="save()">Ejecutar</button>
|
||||
<button class="action-button" [disabled]="selectedClients.length < 1 || (commandType === 'existing' && !selectedScript) || (commandType === 'new' && !newScript.trim()) || loading" (click)="save()">{{ 'buttonExecute' | translate }}</button>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<button color="accent" class="action-button" [disabled]="selectedClients.length < 1 || (commandType === 'existing' && !selectedScript) || (commandType === 'new' && !newScript.trim()) || loading" (click)="openScheduleModal()">
|
||||
Opciones de programación
|
||||
</button>
|
||||
{{ 'schedulingOptions' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="select-container">
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> Clientes </mat-panel-title>
|
||||
<mat-panel-title> {{ 'labelClients' | translate }}</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
Listado de clientes donde se ejecutara el script seleccionado
|
||||
<mat-icon>desktop_windows</mat-icon>
|
||||
{{ 'clientListExecuteScript' | translate }}<mat-icon>desktop_windows</mat-icon>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div class="section-hint" (click)="$event.stopPropagation()">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>
|
||||
Puedes marcar o desmarcar un cliente haciendo clic en el área del ordenador.
|
||||
</span>
|
||||
{{ 'markClientInstruction' | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
|
|
@ -79,24 +76,22 @@
|
|||
<div class="form-section">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>code</mat-icon>
|
||||
Configuración de script
|
||||
</div>
|
||||
{{ 'scriptConfiguration' | translate }}</div>
|
||||
|
||||
<div class="run-as-admin-container">
|
||||
<mat-checkbox [(ngModel)]="runAsAdmin" color="primary">
|
||||
Ejecutar como administrador
|
||||
</mat-checkbox>
|
||||
{{ 'executeAsAdmin' | translate }}</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="action-chips-container">
|
||||
<mat-chip-listbox [(ngModel)]="commandType" required class="action-chip-listbox">
|
||||
<mat-chip-option value="new" class="action-chip create-chip firmware-chip" (click)="commandType = 'new'">
|
||||
<mat-icon>add_circle</mat-icon>
|
||||
<span>Nuevo Script</span>
|
||||
<span>{{ 'newScript' | translate }}</span>
|
||||
</mat-chip-option>
|
||||
<mat-chip-option value="existing" class="action-chip update-chip firmware-chip" (click)="commandType = 'existing'">
|
||||
<mat-icon>folder</mat-icon>
|
||||
<span>Script Guardado</span>
|
||||
<span>{{ 'savedScript' | translate }}</span>
|
||||
</mat-chip-option>
|
||||
</mat-chip-listbox>
|
||||
</div>
|
||||
|
|
@ -106,8 +101,7 @@
|
|||
<div class="script-input-header">
|
||||
<h4>
|
||||
<mat-icon>edit_note</mat-icon>
|
||||
Editor de Script
|
||||
</h4>
|
||||
{{ 'scriptEditorTitle' | translate }}</h4>
|
||||
<div class="script-stats" *ngIf="newScript.trim()">
|
||||
<span class="stat-item">
|
||||
<mat-icon>format_list_numbered</mat-icon>
|
||||
|
|
@ -125,23 +119,22 @@
|
|||
</div>
|
||||
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>Ingrese el script</mat-label>
|
||||
<mat-label>{{ 'enterScript' | translate }}</mat-label>
|
||||
<textarea
|
||||
matInput
|
||||
[(ngModel)]="newScript"
|
||||
(ngModelChange)="onNewScriptChange()"
|
||||
rows="8"
|
||||
placeholder="#!/bin/bash # Escriba su script aquí echo 'Iniciando script...' # Sus comandos aquí"
|
||||
[placeholder]="'bashScriptBoilerplate'|translate"
|
||||
class="script-textarea"></textarea>
|
||||
<mat-hint>Escriba comandos de shell/bash. Use @parametro para variables dinámicas.</mat-hint>
|
||||
<mat-hint>{{ 'writeShellCommandsHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Vista previa de parámetros detectados en nuevo script -->
|
||||
<div *ngIf="newScriptParameters.length > 0" class="detected-params">
|
||||
<h4>
|
||||
<mat-icon>tune</mat-icon>
|
||||
Parámetros detectados
|
||||
</h4>
|
||||
{{ 'detectedParameters' | translate }}</h4>
|
||||
<div class="params-grid">
|
||||
<mat-chip-listbox>
|
||||
<mat-chip-option *ngFor="let param of newScriptParameters" class="param-chip" selected>
|
||||
|
|
@ -159,8 +152,7 @@
|
|||
[disabled]="!isScriptValid(newScript)"
|
||||
(click)="saveNewScript()">
|
||||
<mat-icon>save</mat-icon>
|
||||
Guardar Script
|
||||
</button>
|
||||
{{ 'buttonSaveScript' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -168,7 +160,7 @@
|
|||
<div *ngIf="commandType === 'existing'">
|
||||
<div class="script-selector-container">
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>Seleccione script a ejecutar</mat-label>
|
||||
<mat-label>{{ 'selectScriptToRun' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="selectedScript" (selectionChange)="onScriptChange()">
|
||||
<mat-option *ngFor="let script of scripts" [value]="script">
|
||||
<div class="script-option">
|
||||
|
|
@ -177,7 +169,7 @@
|
|||
</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint *ngIf="scripts.length === 0">No hay scripts guardados disponibles</mat-hint>
|
||||
<mat-hint *ngIf="scripts.length === 0">{{ 'noSavedScripts' | translate }}</mat-hint>
|
||||
<mat-hint *ngIf="scripts.length > 0">{{ scripts.length }} script(s) disponible(s)</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
|
@ -188,8 +180,7 @@
|
|||
<mat-card-header>
|
||||
<mat-card-title>
|
||||
<mat-icon>preview</mat-icon>
|
||||
Vista previa del script
|
||||
</mat-card-title>
|
||||
{{ 'scriptPreview' | translate }}</mat-card-title>
|
||||
<mat-card-subtitle>{{ selectedScript.name }}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
|
|
@ -206,10 +197,9 @@
|
|||
<mat-card-header>
|
||||
<mat-card-title>
|
||||
<mat-icon>settings</mat-icon>
|
||||
Parámetros del script
|
||||
<mat-chip class="params-count">{{ parameterNames.length }}</mat-chip>
|
||||
{{ 'scriptParameters' | translate }}<mat-chip class="params-count">{{ parameterNames.length }}</mat-chip>
|
||||
</mat-card-title>
|
||||
<mat-card-subtitle>Configure los valores para los parámetros requeridos</mat-card-subtitle>
|
||||
<mat-card-subtitle>{{ 'configureRequiredParameters' | translate }}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
|
|
@ -225,7 +215,7 @@
|
|||
[required]="true">
|
||||
<mat-icon matPrefix>label</mat-icon>
|
||||
<mat-hint>Parámetro: @{{ paramName }}</mat-hint>
|
||||
<mat-error *ngIf="!parameters[paramName]">Este parámetro es requerido</mat-error>
|
||||
<mat-error *ngIf="!parameters[paramName]">{{ 'parameterRequired' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -239,8 +229,8 @@
|
|||
<div *ngIf="commandType === 'existing' && !selectedScript" class="empty-state">
|
||||
<div class="empty-state-content">
|
||||
<mat-icon>folder_open</mat-icon>
|
||||
<h3>Selecciona un script</h3>
|
||||
<p>Elige un script guardado de la lista para ver su contenido y configurar parámetros.</p>
|
||||
<h3>{{ 'selectAScript' | translate }}</h3>
|
||||
<p>{{ 'selectScriptToView' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -248,8 +238,8 @@
|
|||
<div *ngIf="commandType === 'new' && !newScript.trim()" class="empty-state">
|
||||
<div class="empty-state-content">
|
||||
<mat-icon>edit_note</mat-icon>
|
||||
<h3>Escribe tu script</h3>
|
||||
<p>Crea un nuevo script escribiendo comandos de shell/bash en el editor de texto.</p>
|
||||
<h3>{{ 'writeYourScript' | translate }}</h3>
|
||||
<p>{{ 'createScriptHint' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||
import { SelectionModel } from "@angular/cdk/collections";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
|
|
@ -15,6 +17,7 @@ import {QueueConfirmationModalComponent} from "../../../../../shared/queue-confi
|
|||
styleUrl: './run-script-assistant.component.css'
|
||||
})
|
||||
export class RunScriptAssistantComponent implements OnInit{
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
@Output() dataChange = new EventEmitter<any>();
|
||||
|
||||
|
|
@ -96,7 +99,7 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
|
||||
saveNewScript() {
|
||||
if (!this.newScript.trim()) {
|
||||
this.toastService.error('Debe ingresar un script antes de guardar.');
|
||||
this.toastService.error(this.translate.instant('youMustEnterAScriptBefore'));
|
||||
return;
|
||||
}
|
||||
const dialogRef = this.dialog.open(SaveScriptComponent, {
|
||||
|
|
@ -106,7 +109,7 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.toastService.success('Script guardado correctamente');
|
||||
this.toastService.success(this.translate.instant('scriptSavedSuccessfully'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -205,13 +208,13 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
runAsAdmin: this.runAsAdmin
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Script ejecutado correctamente');
|
||||
this.toastService.success(this.translate.instant('scriptExecutedSuccessfully'));
|
||||
this.dataChange.emit();
|
||||
this.router.navigate(['/commands-logs']);
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error al ejecutar el script');
|
||||
this.toastService.error(this.translate.instant('errorWhenRunningScript'));
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
|
|
@ -258,7 +261,7 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
type: 'run-script',
|
||||
}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Script añadido con éxito a la tarea');
|
||||
this.toastService.success(this.translate.instant('scriptSuccessfullyAddedTo'));
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<h2 mat-dialog-title>Guardar Comando</h2>
|
||||
<h2 mat-dialog-title>{{ 'buttonSaveCommand' | translate }}</h2>
|
||||
<mat-dialog-content>
|
||||
<p>Introduce un nombre para el comando:</p>
|
||||
<p>{{ 'enterCommandName' | translate }}</p>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Nombre del Comando</mat-label>
|
||||
<mat-label>{{ 'commandNameTitle' | translate }}</mat-label>
|
||||
<input matInput [(ngModel)]="commandName">
|
||||
</mat-form-field>
|
||||
</mat-dialog-content>
|
||||
<div mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="close()">Cancelar</button>
|
||||
<button class="submit-button" (click)="save()">Guardar</button>
|
||||
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="save()">{{ 'labelSaveButton' | translate }}</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {Component, Inject} from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {Component, Inject } from '@angular/core';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {FormBuilder, Validators} from "@angular/forms";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
|
|
@ -12,6 +14,7 @@ import {DataService} from "../../../../../commands/main-commands/data.service";
|
|||
styleUrl: './save-script.component.css'
|
||||
})
|
||||
export class SaveScriptComponent {
|
||||
public translate = inject(TranslateService);
|
||||
commandName: string = '';
|
||||
baseUrl: string;
|
||||
|
||||
|
|
@ -36,7 +39,7 @@ export class SaveScriptComponent {
|
|||
|
||||
this.http.post(`${this.baseUrl}/commands`, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Comando añadido correctamente');
|
||||
this.toastService.success(this.translate.instant('commandAddedSuccessfully'));
|
||||
this.dialogRef.close();
|
||||
},
|
||||
(error) => {
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@
|
|||
</div>
|
||||
<button class="ordinary-button" (click)="openBottomSheet()" joyrideStep="keyStep"
|
||||
text="{{ 'keyStepText' | translate }}" matTooltipShowDelay="1000">
|
||||
{{ 'legendButton' | translate }}
|
||||
{{ 'Legend' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Menú desplegable para pantallas pequeñas -->
|
||||
<div *ngIf="isSmallScreen" class="groups-menu">
|
||||
<button mat-icon-button [matMenuTriggerFor]="smallScreenMenu" matTooltip="Opciones" matTooltipShowDelay="1000">
|
||||
<button mat-icon-button [matMenuTriggerFor]="smallScreenMenu" [matTooltip]="'labelOptions' | translate" matTooltipShowDelay="1000">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
<mat-menu #smallScreenMenu="matMenu">
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
{{ 'newMultipleClientButton' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="openBottomSheet()">
|
||||
{{ 'legendButton' | translate }}
|
||||
{{ 'Legend' | translate }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
|
@ -76,8 +76,8 @@
|
|||
<mat-form-field class="filter-form-field" appearance="outline">
|
||||
<mat-label>{{ 'searchTree' | translate }}</mat-label>
|
||||
<input matInput #treeSearchInput (input)="onTreeFilterInput($event)"
|
||||
placeholder="Centro, aula, grupos ..." />
|
||||
<button *ngIf="treeSearchInput.value" mat-icon-button matSuffix aria-label="Clear tree search"
|
||||
[placeholder]="'placeholderOrgUnitTypes' | translate" />
|
||||
<button *ngIf="treeSearchInput.value" mat-icon-button matSuffix [attr.aria-label]="'clearTreeSearch' | translate"
|
||||
(click)="clearTreeSearch(treeSearchInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
|
|
@ -85,8 +85,8 @@
|
|||
<mat-form-field class="filter-form-field" appearance="outline">
|
||||
<mat-label>{{ 'searchClient' | translate }}</mat-label>
|
||||
<input matInput #clientSearchInput (input)="onClientFilterInput($event)"
|
||||
placeholder="Nombre, IP, estado o MAC" />
|
||||
<button *ngIf="clientSearchInput.value" mat-icon-button matSuffix aria-label="Clear client search"
|
||||
[placeholder]="'searchNameIpStatusMac' | translate" />
|
||||
<button *ngIf="clientSearchInput.value" mat-icon-button matSuffix [attr.aria-label]="'clearClientSearch' | translate"
|
||||
(click)="clearClientSearch(clientSearchInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
{{ option.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<button *ngIf="clientSearchStatusInput.value" mat-icon-button matSuffix aria-label="Clear tree search"
|
||||
<button *ngIf="clientSearchStatusInput.value" mat-icon-button matSuffix [attr.aria-label]="'clearTreeSearch' | translate"
|
||||
(click)="clearStatusFilter($event, clientSearchStatusInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
|
|
@ -116,7 +116,7 @@
|
|||
|
||||
<!-- Funcionalidad actualmente deshabilitada-->
|
||||
<!-- <mat-form-field appearance="outline">
|
||||
<mat-select (selectionChange)="loadSelectedFilter($event.value)" placeholder="Cargar filtro" disabled>
|
||||
<mat-select (selectionChange)="loadSelectedFilter($event.value)" [placeholder]="'buttonLoadFilter' | translate" disabled>
|
||||
<mat-option *ngFor="let savedFilter of savedFilterNames" [value]="savedFilter">
|
||||
{{ savedFilter[0] }}
|
||||
</mat-option>
|
||||
|
|
@ -270,11 +270,11 @@
|
|||
</button>
|
||||
<button mat-menu-item (click)="onEditNode($event, selectedNode)" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>{{ 'edit' | translate }}</span>
|
||||
<span>{{ 'buttonEdit' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, selectedNode)" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>{{ 'delete' | translate }}</span>
|
||||
<span>{{ 'buttonDelete' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openPartitionTypeModal($event, selectedNode)">
|
||||
<mat-icon>storage</mat-icon>
|
||||
|
|
@ -300,7 +300,7 @@
|
|||
<!-- CLIENTS HEADER -->
|
||||
<div class="clients-view-header">
|
||||
<span [ngStyle]="{ visibility: isLoadingClients ? 'hidden' : 'visible' }" class="clients-title-name">
|
||||
{{ 'clients' | translate }}
|
||||
{{ 'labelClients' | translate }}
|
||||
<strong>{{ selectedNode?.name }}</strong>
|
||||
</span>
|
||||
<div class="view-type-container">
|
||||
|
|
@ -308,7 +308,7 @@
|
|||
(change)="toggleAllCards()"
|
||||
[checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()"
|
||||
matTooltip="Seleccionar todos">
|
||||
[matTooltip]="'buttonSelectAll' | translate">
|
||||
</mat-checkbox>
|
||||
<button class="action-button" [disabled]="selection.selected.length === 0" (click)="changeParent($event)"
|
||||
matTooltip="{{ 'moveClientsTooltip' | translate }}" matTooltipShowDelay="1000">
|
||||
|
|
@ -325,7 +325,7 @@
|
|||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
|
||||
<mat-button-toggle-group name="viewType" aria-label="View Type" [hideSingleSelectionIndicator]="true"
|
||||
<mat-button-toggle-group name="viewType" [attr.aria-label]="'viewType' | translate" [hideSingleSelectionIndicator]="true"
|
||||
(change)="toggleView($event.value)" joyrideStep="tabsStep" text="{{ 'tabsStepText' | translate }}">
|
||||
<mat-button-toggle value="list" [disabled]="currentView === 'list'">
|
||||
<mat-icon>list</mat-icon> <span class="type-view-text">{{ 'vistalista' | translate }}</span>
|
||||
|
|
@ -419,16 +419,16 @@
|
|||
<mat-menu #clientMenu="matMenu">
|
||||
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>{{ 'edit' | translate }}</span>
|
||||
<span>{{ 'buttonEdit' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onShowClientDetail($event, client)">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
<span>{{ 'viewDetails' | translate }}</span>
|
||||
<span>{{ 'buttonViewDetails' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item *ngIf="!syncingClientIds.has(client.uuid)"
|
||||
(click)="getStatus(client, selectedNode)">
|
||||
<mat-icon>sync</mat-icon>
|
||||
<span>{{ 'sync' | translate }}</span>
|
||||
<span>{{ 'buttonSync' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openClientTaskLogs($event, client)">
|
||||
<mat-icon>list_alt</mat-icon>
|
||||
|
|
@ -440,15 +440,15 @@
|
|||
</button>
|
||||
<button mat-menu-item (click)="openClientLogsInNewTab($event, client)">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>Logs en tiempo real</span>
|
||||
<span>{{ 'realTimeLogs' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openClientLogsModal($event, client)">
|
||||
<mat-icon>analytics</mat-icon>
|
||||
<span>Logs de cliente</span>
|
||||
<span>{{ 'clientLogs' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, client)" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>{{ 'delete' | translate }}</span>
|
||||
<span>{{ 'buttonDelete' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
|
@ -471,7 +471,7 @@
|
|||
<span>{{ 'showingResults' | translate: { from: getPaginationFrom(), to: getPaginationTo(), total: getPaginationTotal() } }}</span>
|
||||
</div>
|
||||
<div class="table-actions">
|
||||
<button mat-icon-button (click)="refreshClientData()" matTooltip="{{ 'refresh' | translate }}">
|
||||
<button mat-icon-button (click)="refreshClientData()" matTooltip="{{ 'buttonUpdate' | translate }}">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button (click)="exportToCSV()" matTooltip="{{ 'exportCSV' | translate }}">
|
||||
|
|
@ -498,7 +498,7 @@
|
|||
<ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<div class="column-header">
|
||||
<span>{{ 'status' | translate }}</span>
|
||||
<span>{{ 'labelState' | translate }}</span>
|
||||
<button mat-icon-button (click)="sortColumn('status')" class="sort-button">
|
||||
<mat-icon>{{ getSortIcon('status') }}</mat-icon>
|
||||
</button>
|
||||
|
|
@ -535,7 +535,7 @@
|
|||
<ng-container matColumnDef="ip">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<div class="column-header">
|
||||
<span>IP</span>
|
||||
<span>{{ 'labelIpCaps' | translate }}</span>
|
||||
<button mat-icon-button (click)="sortColumn('ip')" class="sort-button">
|
||||
<mat-icon>{{ getSortIcon('ip') }}</mat-icon>
|
||||
</button>
|
||||
|
|
@ -567,7 +567,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="oglive">
|
||||
<th mat-header-cell *matHeaderCellDef> OG Live </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'ogLiveTitle' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let client">
|
||||
<div class="oglive-cell">
|
||||
<span class="oglive-kernel">{{ client.ogLive?.kernel }}</span>
|
||||
|
|
@ -590,7 +590,7 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="parentName">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'parent' | translate }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'labelEditOrgUnitParent' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client" class="truncate-cell-medium" [matTooltip]="client.parentName"> {{ client.parentName }} </td>
|
||||
</ng-container>
|
||||
|
||||
|
|
@ -608,22 +608,22 @@
|
|||
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"
|
||||
[runScriptContext]="getRunScriptContext([client])" matTooltip="{{ 'executeCommand' | translate }}">
|
||||
</app-execute-command>
|
||||
<button mat-icon-button color="primary" (click)="onShowClientDetail($event, client)" matTooltip="{{ 'viewDetails' | translate }}">
|
||||
<button mat-icon-button color="primary" (click)="onShowClientDetail($event, client)" matTooltip="{{ 'buttonViewDetails' | translate }}">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<mat-menu #clientMenu="matMenu">
|
||||
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>{{ 'edit' | translate }}</span>
|
||||
<span>{{ 'buttonEdit' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onShowClientDetail($event, client)">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
<span>{{ 'viewDetails' | translate }}</span>
|
||||
<span>{{ 'buttonViewDetails' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="getStatus(client, selectedNode)">
|
||||
<mat-icon>sync</mat-icon>
|
||||
<span>{{ 'sync' | translate }}</span>
|
||||
<span>{{ 'buttonSync' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openClientTaskLogs($event, client)">
|
||||
<mat-icon>list_alt</mat-icon>
|
||||
|
|
@ -635,15 +635,15 @@
|
|||
</button>
|
||||
<button mat-menu-item *ngIf="client.status === 'og-live'" (click)="openClientLogsInNewTab($event, client)">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>Logs en tiempo real</span>
|
||||
<span>{{ 'realTimeLogs' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openClientLogsModal($event, client)">
|
||||
<mat-icon>analytics</mat-icon>
|
||||
<span>Logs de cliente</span>
|
||||
<span>{{ 'clientLogs' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, client)" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>{{ 'delete' | translate }}</span>
|
||||
<span>{{ 'buttonDelete' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit, OnDestroy, ViewChild, QueryList, ViewChildren, ChangeDetectorRef } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Router } from '@angular/router';
|
||||
|
|
@ -48,6 +50,7 @@ enum NodeType {
|
|||
styleUrls: ['./groups.component.css'],
|
||||
})
|
||||
export class GroupsComponent implements OnInit, OnDestroy {
|
||||
public translate = inject(TranslateService);
|
||||
@ViewChildren(MatMenuTrigger) menuTriggers!: QueryList<MatMenuTrigger>;
|
||||
isSmallScreen: boolean = false;
|
||||
baseUrl: string;
|
||||
|
|
@ -283,7 +286,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
// this.savedFilterNames = data.map((filter: { name: string; uuid: string; }) => [filter.name, filter.uuid]);
|
||||
// },
|
||||
// (error) => {
|
||||
// console.error('Error fetching filters:', error);
|
||||
// console.error(this.translate.instant('errorFetchingFilters1'), error);
|
||||
// }
|
||||
// )
|
||||
// );
|
||||
|
|
@ -297,7 +300,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
this.savedFilterNames = data.map((filter: { name: string; uuid: string; }) => [filter.name, filter.uuid]);
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching filters:', error);
|
||||
console.error(this.translate.instant('errorFetchingFilters1'), error);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
@ -350,8 +353,8 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error fetching organizational units', error);
|
||||
this.toastr.error('Ocurrió un error al cargar las unidades organizativas');
|
||||
console.error(this.translate.instant('errorFetchingOrganizationalUnits'), error);
|
||||
this.toastr.error(this.translate.instant('anErrorOccurredWhileLoading'));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -561,7 +564,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
console.log('Nodo padre encontrado para actualización:', parentNode);
|
||||
this.refreshData(parentNode.uuid);
|
||||
} else {
|
||||
console.error('No se encontró el nodo padre después de la creación masiva.');
|
||||
console.error(this.translate.instant('nodeNotFound'));
|
||||
}
|
||||
}
|
||||
this.loading = false;
|
||||
|
|
@ -635,7 +638,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error deleting entity:', error);
|
||||
console.error(this.translate.instant('errorDeletingEntity'), error);
|
||||
const entityType = type === NodeType.Client ? 'cliente' : 'entidad';
|
||||
this.toastr.error(`Error al eliminar el ${entityType}`);
|
||||
},
|
||||
|
|
@ -675,7 +678,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching room data:', error);
|
||||
console.error(this.translate.instant('errorFetchingRoomData'), error);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
@ -685,7 +688,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
executeCommand(command: Command, selectedNode: TreeNode | null): void {
|
||||
this.loading = true;
|
||||
if (!selectedNode) {
|
||||
this.toastr.error('No hay un nodo seleccionado.');
|
||||
this.toastr.error(this.translate.instant('thereIsNoNodeSelected'));
|
||||
return;
|
||||
} else {
|
||||
this.toastr.success(`Ejecutando comando: ${command.name} en ${selectedNode.name}`);
|
||||
|
|
@ -834,7 +837,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
this.subscriptions.add(
|
||||
this.http.post(`${this.baseUrl}${client['@id']}/agent/status`, {}).subscribe(
|
||||
() => {
|
||||
this.toastr.success('Cliente actualizado correctamente');
|
||||
this.toastr.success(this.translate.instant('clientUpdatedSuccessfully'));
|
||||
this.syncingClientIds.delete(client.uuid);
|
||||
this.refreshData(parentNodeId)
|
||||
},
|
||||
|
|
@ -869,12 +872,12 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
this.arrayClients = this.selectedClients.data;
|
||||
this.calculateLocalStats();
|
||||
this.toastr.success('Estado del aula actualizado');
|
||||
this.toastr.success(this.translate.instant('classroomStatusUpdated'));
|
||||
},
|
||||
error: () => {
|
||||
this.isCheckingNodeStatus = false;
|
||||
this.selectedClients.data.forEach(c => c.uuid && this.syncingClientIds.delete(c.uuid));
|
||||
this.toastr.error('Error al comprobar el estado del aula');
|
||||
this.toastr.error(this.translate.instant('errorCheckingStatus'));
|
||||
},
|
||||
})
|
||||
);
|
||||
|
|
@ -1054,14 +1057,14 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
`);
|
||||
}
|
||||
} else {
|
||||
this.toastr.error('No se puede acceder a los logs: IP del cliente no disponible', 'Error');
|
||||
this.toastr.error(this.translate.instant('cannotAccess'), 'Error');
|
||||
}
|
||||
}
|
||||
|
||||
openClientLogsModal(event: MouseEvent, client: Client): void {
|
||||
event.stopPropagation();
|
||||
if (!client.mac) {
|
||||
this.toastr.error('No se puede acceder a los logs: MAC del cliente no disponible', 'Error');
|
||||
this.toastr.error(this.translate.instant('cantAccess1'), 'Error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1088,7 +1091,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
const allClients = response['hydra:member'] || [];
|
||||
|
||||
if (allClients.length === 0) {
|
||||
this.toastr.warning('Esta unidad organizativa no tiene clientes');
|
||||
this.toastr.warning(this.translate.instant('thisOrganizationalUnitDoesNotHave'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1113,8 +1116,8 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al obtener los clientes de la unidad organizativa:', error);
|
||||
this.toastr.error('Error al cargar los clientes de la unidad organizativa');
|
||||
console.error(this.translate.instant('errorGettingClients1'), error);
|
||||
this.toastr.error(this.translate.instant('errorLoadingClients'));
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
|
|
@ -1135,7 +1138,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
|
||||
refreshClientData(): void {
|
||||
this.fetchClientsForNode(this.selectedNode);
|
||||
this.toastr.success('Datos actualizados', 'Éxito');
|
||||
this.toastr.success(this.translate.instant('updatedData'), 'Éxito');
|
||||
}
|
||||
|
||||
exportToCSV(): void {
|
||||
|
|
@ -1164,7 +1167,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
this.toastr.success('Archivo CSV exportado correctamente', 'Éxito');
|
||||
this.toastr.success(this.translate.instant('csvFileExportedSuccessfully'), 'Éxito');
|
||||
}
|
||||
|
||||
private calculateLocalStats(): void {
|
||||
|
|
@ -1318,7 +1321,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
event.stopPropagation();
|
||||
|
||||
if (this.selection.selected.length === 0) {
|
||||
this.toastr.warning('No hay clientes seleccionados para eliminar');
|
||||
this.toastr.warning(this.translate.instant('thereAreNoClientsSelectedFor'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1355,8 +1358,8 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
},
|
||||
error: (error) => {
|
||||
this.loading = false;
|
||||
console.error('Error deleting multiple clients:', error);
|
||||
this.toastr.error('Error al eliminar los clientes seleccionados');
|
||||
console.error(this.translate.instant('errorDeletingMultipleClients1'), error);
|
||||
this.toastr.error(this.translate.instant('errorDeletingClients'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import {HttpClient, HttpParams} from '@angular/common/http';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
|
|
@ -9,6 +11,7 @@ import { ConfigService } from '@services/config.service';
|
|||
providedIn: 'root'
|
||||
})
|
||||
export class DataService {
|
||||
public translate = inject(TranslateService);
|
||||
|
||||
baseUrl: string;
|
||||
private apiUrl: string;
|
||||
|
|
@ -35,7 +38,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching unidades organizativas', error);
|
||||
console.error(this.translate.instant('errorFetchingOrganizationalUnits'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -51,7 +54,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching children', error);
|
||||
console.error(this.translate.instant('errorFetchingChildren'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -67,7 +70,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching clients', error);
|
||||
console.error(this.translate.instant('errorFetchingClients'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -84,7 +87,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching clients', error);
|
||||
console.error(this.translate.instant('errorFetchingClients'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -101,7 +104,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching clients', error);
|
||||
console.error(this.translate.instant('errorFetchingClients'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -118,7 +121,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching clients', error);
|
||||
console.error(this.translate.instant('errorFetchingClients'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -132,7 +135,7 @@ export class DataService {
|
|||
console.log('DELETE URL:', url); // Depuración
|
||||
return this.http.delete<void>(url).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error deleting element', error);
|
||||
console.error(this.translate.instant('errorDeletingElement'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -144,7 +147,7 @@ export class DataService {
|
|||
// @ts-ignore
|
||||
return this.http.post<void>(url).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error deleting element', error);
|
||||
console.error(this.translate.instant('errorDeletingElement'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -185,7 +188,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching data', error);
|
||||
console.error(this.translate.instant('fetchingDataError'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -201,7 +204,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching filters', error);
|
||||
console.error(this.translate.instant('errorFetchingFilters'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -219,7 +222,7 @@ export class DataService {
|
|||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching filters', error);
|
||||
console.error(this.translate.instant('errorFetchingFilters'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -229,7 +232,7 @@ export class DataService {
|
|||
const url = `${this.baseUrl}/organizational-units/${id}`;
|
||||
return this.http.get<any>(url).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error fetching organizational unit', error);
|
||||
console.error(this.translate.instant('errorFetchingOrganizationalUnit'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
@ -255,7 +258,7 @@ export class DataService {
|
|||
|
||||
return this.http.post<any>(url, body).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error deleting multiple clients', error);
|
||||
console.error(this.translate.instant('errorDeletingMultipleClients'), error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<h2 mat-dialog-title>Mover clientes a:</h2>
|
||||
<h2 mat-dialog-title>{{ 'moveClientsTo' | translate }}</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione aula</mat-label>
|
||||
<mat-label>{{ 'selectClassroomPrompt' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="newOU">
|
||||
<mat-option *ngFor="let unit of units" [value]="unit">{{ unit.name }}</mat-option>
|
||||
</mat-select>
|
||||
|
|
@ -10,6 +10,6 @@
|
|||
</mat-dialog-content>
|
||||
|
||||
<div class="action-container">
|
||||
<button class="ordinary-button" (click)="close()">Cancelar</button>
|
||||
<button class="submit-button" (click)="save()" [disabled]="!newOU">Continuar</button>
|
||||
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button class="submit-button" (click)="save()" [disabled]="!newOU">{{ 'buttonContinue' | translate }}</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/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";
|
||||
|
|
@ -11,6 +13,7 @@ import {ConfigService} from "@services/config.service";
|
|||
styleUrl: './change-parent.component.css'
|
||||
})
|
||||
export class ChangeParentComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
baseUrl: string;
|
||||
loading: boolean = true;
|
||||
units: any[] = [];
|
||||
|
|
@ -38,7 +41,7 @@ export class ChangeParentComponent implements OnInit {
|
|||
this.units = response['hydra:member'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => console.error('Error fetching organizational units:', error)
|
||||
error => console.error(this.translate.instant('errorFetchingOrganizationalUnits1'), error)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +51,7 @@ export class ChangeParentComponent implements OnInit {
|
|||
organizationalUnit: this.newOU['@id']
|
||||
}).subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Parent changed successfully');
|
||||
this.toastService.success(this.translate.instant('parentChangedSuccessfully'));
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
error: error => {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue