Compare commits
10 Commits
0cde34aedb
...
3dd127d46f
Author | SHA1 | Date |
---|---|---|
|
3dd127d46f | |
|
a7472027e6 | |
|
0e3e3f56e3 | |
|
298e9b0c38 | |
|
cd09659e6c | |
|
1bde11689c | |
|
2d4b0e58ea | |
|
dfc7fd01ce | |
|
ddfbcdf46d | |
|
4dfc45b125 |
|
@ -23,7 +23,6 @@ import { TaskLogsComponent } from './components/commands/commands-task/task-logs
|
|||
import { StatusComponent } from "./components/ogdhcp/og-dhcp-subnets/status/status.component";
|
||||
import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component';
|
||||
import { ImagesComponent } from './components/images/images.component';
|
||||
import { RestoreImageComponent } from './components/groups/components/client-main-view/restore-image/restore-image.component';
|
||||
import {SoftwareComponent} from "./components/software/software.component";
|
||||
import {SoftwareProfileComponent} from "./components/software-profile/software-profile.component";
|
||||
import {OperativeSystemComponent} from "./components/operative-system/operative-system.component";
|
||||
|
@ -31,6 +30,16 @@ import {
|
|||
PartitionAssistantComponent
|
||||
} from "./components/groups/components/client-main-view/partition-assistant/partition-assistant.component";
|
||||
import {RepositoriesComponent} from "./components/repositories/repositories.component";
|
||||
import {
|
||||
CreateImageComponent
|
||||
} from "./components/groups/components/client-main-view/create-image/create-image.component";
|
||||
import {
|
||||
DeployImageComponent
|
||||
} from "./components/groups/components/client-main-view/deploy-image/deploy-image.component";
|
||||
import {
|
||||
MainRepositoryViewComponent
|
||||
} from "./components/repositories/main-repository-view/main-repository-view.component";
|
||||
import {EnvVarsComponent} from "./components/admin/env-vars/env-vars.component";
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
|
||||
{
|
||||
|
@ -40,6 +49,7 @@ const routes: Routes = [
|
|||
{ path: 'dashboard', component: DashboardComponent },
|
||||
{ path: 'admin', component: AdminComponent },
|
||||
{ path: 'users', component: UsersComponent },
|
||||
{ path: 'env-vars', component: EnvVarsComponent },
|
||||
{ path: 'user-groups', component: RolesComponent },
|
||||
{ path: 'groups', component: GroupsComponent },
|
||||
{ path: 'pxe-images', component: PXEimagesComponent },
|
||||
|
@ -54,11 +64,13 @@ const routes: Routes = [
|
|||
{ path: 'commands-task', component: CommandsTaskComponent },
|
||||
{ path: 'commands-logs', component: TaskLogsComponent },
|
||||
{ path: 'calendars', component: CalendarComponent },
|
||||
{ path: 'client/:id', component: ClientMainViewComponent },
|
||||
{ path: 'client/:id/partition-assistant', component: PartitionAssistantComponent },
|
||||
{ path: 'clients/:id', component: ClientMainViewComponent },
|
||||
{ path: 'clients/:id/partition-assistant', component: PartitionAssistantComponent },
|
||||
{ path: 'clients/:id/create-image', component: CreateImageComponent },
|
||||
{ path: 'clients/:id/deploy-image', component: DeployImageComponent },
|
||||
{ path: 'images', component: ImagesComponent },
|
||||
{ path: 'repositories', component: RepositoriesComponent },
|
||||
{ path: 'restore-image', component: RestoreImageComponent},
|
||||
{ path: 'repository/:id', component: MainRepositoryViewComponent },
|
||||
{ path: 'software', component: SoftwareComponent },
|
||||
{ path: 'software-profiles', component: SoftwareProfileComponent },
|
||||
{ path: 'operative-systems', component: OperativeSystemComponent },
|
||||
|
|
|
@ -106,7 +106,6 @@ import { ClientMainViewComponent } from './components/groups/components/client-m
|
|||
import { ImagesComponent } from './components/images/images.component';
|
||||
import { CreateImageComponent } from './components/images/create-image/create-image.component';
|
||||
import { PartitionAssistantComponent } from './components/groups/components/client-main-view/partition-assistant/partition-assistant.component';
|
||||
import { RestoreImageComponent } from './components/groups/components/client-main-view/restore-image/restore-image.component';
|
||||
import { SoftwareComponent } from './components/software/software.component';
|
||||
import { CreateSoftwareComponent } from './components/software/create-software/create-software.component';
|
||||
import { SoftwareProfileComponent } from './components/software-profile/software-profile.component';
|
||||
|
@ -119,10 +118,13 @@ import { ClientsComponent } from './components/ogboot/pxe/clients/clients.compon
|
|||
import { RepositoriesComponent } from './components/repositories/repositories.component';
|
||||
import { CreateRepositoryComponent } from './components/repositories/create-repository/create-repository.component';
|
||||
import { ExecuteCommandComponent } from './components/commands/main-commands/execute-command/execute-command.component';
|
||||
import { DeployImageComponent } from './components/groups/components/client-main-view/deploy-image/deploy-image.component';
|
||||
import { MainRepositoryViewComponent } from './components/repositories/main-repository-view/main-repository-view.component';
|
||||
import { ExecuteCommandOuComponent } from './components/groups/shared/execute-command-ou/execute-command-ou.component';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { EnvVarsComponent } from './components/admin/env-vars/env-vars.component';
|
||||
|
||||
export function HttpLoaderFactory(http: HttpClient) {
|
||||
return new TranslateHttpLoader(http, './locale/', '.json');
|
||||
|
@ -191,7 +193,6 @@ export function HttpLoaderFactory(http: HttpClient) {
|
|||
ImagesComponent,
|
||||
CreateImageComponent,
|
||||
PartitionAssistantComponent,
|
||||
RestoreImageComponent,
|
||||
SoftwareComponent,
|
||||
CreateSoftwareComponent,
|
||||
SoftwareProfileComponent,
|
||||
|
@ -204,7 +205,11 @@ export function HttpLoaderFactory(http: HttpClient) {
|
|||
RepositoriesComponent,
|
||||
CreateRepositoryComponent,
|
||||
ExecuteCommandComponent,
|
||||
ExecuteCommandOuComponent
|
||||
ExecuteCommandOuComponent,
|
||||
DeployImageComponent,
|
||||
MainRepositoryViewComponent,
|
||||
ExecuteCommandOuComponent,
|
||||
EnvVarsComponent,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [BrowserModule,
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
.env-settings {
|
||||
padding: 16px;
|
||||
|
||||
h1 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mat-table {
|
||||
margin-bottom: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.value-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
|
||||
button {
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<div class="env-settings">
|
||||
<h1>Editar Variables de Entorno</h1>
|
||||
|
||||
<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-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-cell *matCellDef="let variable">
|
||||
<mat-form-field class="value-input">
|
||||
<input matInput [(ngModel)]="variable.value" />
|
||||
</mat-form-field>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<div class="actions" >
|
||||
<button mat-raised-button color="primary" (click)="saveEnvVars()">Guardar Cambios</button>
|
||||
<button mat-raised-button color="accent" (click)="loadEnvVars()">Recargar</button>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EnvVarsComponent } from './env-vars.component';
|
||||
|
||||
describe('EnvVarsComponent', () => {
|
||||
let component: EnvVarsComponent;
|
||||
let fixture: ComponentFixture<EnvVarsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [EnvVarsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EnvVarsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
import { Component } from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: 'app-env-vars',
|
||||
templateUrl: './env-vars.component.html',
|
||||
styleUrl: './env-vars.component.css'
|
||||
})
|
||||
export class EnvVarsComponent {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
envVars: { name: string; value: string }[] = [];
|
||||
displayedColumns: string[] = ['name', 'value'];
|
||||
|
||||
private apiUrl = `${this.baseUrl}/env-vars`;
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadEnvVars();
|
||||
}
|
||||
|
||||
loadEnvVars(): void {
|
||||
this.http.get<{ vars: Record<string, string> }>(this.apiUrl).subscribe({
|
||||
next: (response) => {
|
||||
this.envVars = Object.entries(response.vars).map(([name, value]) => ({ name, value }));
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error al cargar las variables de entorno:', err);
|
||||
this.toastService.error('No se pudieron cargar las variables de entorno.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
saveEnvVars(): void {
|
||||
const vars = this.envVars.reduce((acc, variable) => {
|
||||
acc[variable.name] = variable.value;
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
this.http.post(this.apiUrl, { vars }).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Variables de entorno guardadas correctamente.');
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error al guardar las variables de entorno:', err);
|
||||
this.toastService.error('No se pudieron cargar las variables de entorno.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -10,63 +10,6 @@
|
|||
<textarea matInput formControlName="notes" placeholder="Ingresa tus notas aquí"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona Comandos</mat-label>
|
||||
<mat-select formControlName="commandGroup" (selectionChange)="onCommandGroupChange()">
|
||||
<mat-option *ngFor="let group of availableCommandGroups" [value]="group.uuid">
|
||||
{{ group.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="taskForm.get('commandGroup')?.invalid">Este campo es obligatorio</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona Comandos Individuales (Opcional)</mat-label>
|
||||
<mat-select formControlName="extraCommands" multiple>
|
||||
<mat-option *ngFor="let command of availableIndividualCommands" [value]="command.uuid">
|
||||
{{ command.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<h3 class="section-title">Fecha y hora de ejecución</h3>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Fecha de Ejecución</mat-label>
|
||||
<input matInput [matDatepicker]="picker" formControlName="date" placeholder="Selecciona una fecha">
|
||||
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
<mat-error *ngIf="taskForm.get('date')?.invalid">Este campo es obligatorio</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Hora de Ejecución</mat-label>
|
||||
<input matInput type="time" formControlName="time" placeholder="Selecciona una hora">
|
||||
<mat-error *ngIf="taskForm.get('time')?.invalid">Este campo es obligatorio</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<h3 class="section-title">Selecciona destino</h3>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona Unidad Organizacional</mat-label>
|
||||
<mat-select formControlName="organizationalUnit" (selectionChange)="onOrganizationalUnitChange()">
|
||||
<mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
|
||||
{{ unit.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="taskForm.get('organizationalUnit')?.invalid">Este campo es obligatorio</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona aula</mat-label>
|
||||
<mat-select formControlName="selectedChild" (selectionChange)="onChildChange()">
|
||||
<mat-option *ngFor="let child of selectedUnitChildren" [value]="child['@id']">
|
||||
{{ child.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
=======
|
||||
<h3 class="section-title">{{ 'informationSectionTitle' | translate }}</h3>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
|
@ -147,7 +90,6 @@
|
|||
<div class="button-container">
|
||||
<button mat-raised-button color="primary" (click)="saveTask()">{{ 'buttonSave' | translate }}</button>
|
||||
</div>
|
||||
>>>>>>> Stashed changes
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Selecciona Clientes</mat-label>
|
||||
|
|
|
@ -70,13 +70,23 @@ table {
|
|||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.mat-chip-readonly-true {
|
||||
background-color: #4CAF50 !important;
|
||||
color: white !important;
|
||||
.chip-failed {
|
||||
background-color: #f15d5d !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mat-chip-readonly-false {
|
||||
background-color: #F44336 !important;
|
||||
color: white !important;
|
||||
.chip-success {
|
||||
background-color: #32c532 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chip-pending {
|
||||
background-color: #bebdbd !important;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.chip-in-progress {
|
||||
background-color: #f5a623 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,16 @@
|
|||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="search-boolean">
|
||||
<mat-label i18n="@@searchLabel">Estado</mat-label>
|
||||
<mat-select [(ngModel)]="filters['status']" (selectionChange)="loadTraces()" placeholder="Seleccionar opción" >
|
||||
<mat-option [value]="'failed'">Fallido</mat-option>
|
||||
<mat-option [value]="'pending'">Pendiente de ejecutar</mat-option>
|
||||
<mat-option [value]="'in-progress'">Ejecutando</mat-option>
|
||||
<mat-option [value]="'success'">Completado con éxito</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
|
@ -41,7 +51,28 @@
|
|||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let trace">
|
||||
{{ column.cell(trace) }}
|
||||
|
||||
<ng-container *ngIf="column.columnDef === 'status'; else defaultCell">
|
||||
<mat-chip [ngClass]="{
|
||||
'chip-failed': trace.status === 'failed',
|
||||
'chip-success': trace.status === 'success',
|
||||
'chip-pending': trace.status === 'pending',
|
||||
'chip-in-progress': trace.status === 'in-progress'
|
||||
}">
|
||||
{{
|
||||
trace.status === 'failed' ? 'Fallido' :
|
||||
trace.status === 'success' ? 'Finalizado con éxito' :
|
||||
trace.status === 'pending' ? 'Pendiente de ejecutar' :
|
||||
trace.status === 'in-progress' ? 'Ejecutando' :
|
||||
trace.status
|
||||
}}
|
||||
</mat-chip>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #defaultCell>
|
||||
{{ column.cell(trace) }}
|
||||
</ng-template>
|
||||
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -45,16 +45,21 @@ export class TaskLogsComponent implements OnInit {
|
|||
header: 'Estado',
|
||||
cell: (trace: any) => `${trace.status}`
|
||||
},
|
||||
{
|
||||
columnDef: 'jobId',
|
||||
header: 'Hilo de trabajo',
|
||||
cell: (trace: any) => `${trace.jobId}`
|
||||
},
|
||||
{
|
||||
columnDef: 'executedAt',
|
||||
header: 'Programación de ejecución',
|
||||
cell: (trace: any) => `${this.datePipe.transform(trace.executedAt, 'dd/MM/yyyy hh:mm:ss')}`,
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
header: 'Fecha de creación',
|
||||
cell: (trace: any) => `${this.datePipe.transform(trace.createdAt, 'dd/MM/yyyy hh:mm:ss')}`,
|
||||
}
|
||||
columnDef: 'finishedAt',
|
||||
header: 'Finalización',
|
||||
cell: (trace: any) => `${this.datePipe.transform(trace.finishedAt, 'dd/MM/yyyy hh:mm:ss')}`,
|
||||
},
|
||||
];
|
||||
displayedColumns = [...this.columns.map(column => column.columnDef)];
|
||||
|
||||
|
|
|
@ -78,9 +78,15 @@ mat-card-subtitle a:hover {
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 10px;
|
||||
margin-bottom: 20px;
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-group button {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
|
@ -88,10 +94,6 @@ button {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.item-content mat-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.clickable-item:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -143,7 +145,7 @@ mat-spinner {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header mat-form-field {
|
||||
|
@ -186,12 +188,8 @@ mat-spinner {
|
|||
}
|
||||
|
||||
.result-card.small-card {
|
||||
width: 100%;
|
||||
max-width: 180px;
|
||||
height: auto;
|
||||
min-height: 130px;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
|
@ -199,6 +197,36 @@ mat-spinner {
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result-card.card-og-live {
|
||||
background-color: #4caf50; /* Verde */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.result-card.card-busy {
|
||||
background-color: #f44336; /* Naranja */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.result-card.card-windows {
|
||||
background-color: #2196f3; /* Azul */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.result-card.card-linux {
|
||||
background-color: #9c27b0; /* Púrpura */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.result-card.card-macos {
|
||||
background-color: #ff9800; /* Rojo */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.result-card.card-off {
|
||||
background-color: #9e9e9e; /* Gris */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
|
@ -296,3 +324,41 @@ mat-card {
|
|||
.result-list {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.result-checkbox {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.result-title,
|
||||
.result-content {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.result-content p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.card-content-wrapper {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
<button mat-button (click)="changeViewMode('list')" [class.active]="viewMode === 'list'">
|
||||
<mat-icon>list</mat-icon> {{ 'listViewButton' | translate }}
|
||||
</button>
|
||||
<button mat-button (click)="toggleSelectAll()">
|
||||
<mat-icon>checkbox</mat-icon> Seleccionar/Deseleccionar Todos</button>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
|
@ -42,41 +44,68 @@
|
|||
<input matInput placeholder="{{ 'namePlaceholder' | translate }}" (input)="applyFilter()" [(ngModel)]="filterName">
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-raised-button color="primary" (click)="toggleSelectAll()">
|
||||
{{ 'selectAllButton' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="saveFilters()">
|
||||
{{ 'saveFiltersButton' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="accent" (click)="sendActions()" [disabled]="selectedElements.length === 0">
|
||||
{{ 'sendFiltersButton' | translate }}
|
||||
</button>
|
||||
<button mat-flat-button color="primary" [disabled]="selectedElements.length === 0" (click)="onPxeBootFile()">
|
||||
{{ 'addPxeButton' | translate }}
|
||||
</button>
|
||||
<div class="button-group">
|
||||
<button mat-raised-button color="primary" (click)="saveFilters()" i18n="@@saveFiltersButton" joyrideStep="saveFiltersStep" text="Guarda tus filtros seleccionados para usarlos en el futuro.">{{ 'selectAllButton' | translate }}</button>
|
||||
<button mat-raised-button color="accent" (click)="sendActions()" i18n="@@sendFiltersButton" [disabled]="selectedElements.length === 0" joyrideStep="sendActionStep" text="Envía una acción a los elementos seleccionados.">{{ 'saveFiltersButton' | translate }}</button>
|
||||
<button mat-flat-button color="primary" [disabled]="selectedElements.length === 0" (click)="onPxeBootFile()" joyrideStep="addPxeStep" text="Añade un archivo PXE a los elementos seleccionados.">{{ 'addPxeButton' | translate }}</button>
|
||||
<button mat-raised-button color="primary" [matMenuTriggerFor]="menu" [disabled]="selectedFilter1 === 'ou'">
|
||||
Asistentes
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item [disabled]="selectedElements.length > 1 || !selectedElements.length" (click)="onCommandSelect('partition')">Asistente de particionado</button>
|
||||
<button mat-menu-item [disabled]="selectedElements.length > 1 || !selectedElements.length" (click)="onCommandSelect('create-image')">Crear una imagen</button>
|
||||
<button mat-menu-item [disabled]="selectedElements.length > 1 || !selectedElements.length" (click)="onCommandSelect('deploy-image')">Desplegar una imagen</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="results" joyrideStep="resultsStep" text="Aquí verás los resultados de tu búsqueda filtrada.">
|
||||
<ng-container *ngIf="filteredResults && filteredResults.length > 0; else noResults">
|
||||
<ng-container *ngIf="viewMode === 'grid'">
|
||||
<mat-grid-list cols="8" rowHeight="1:1">
|
||||
<mat-grid-list cols="8" rowHeight="1:1" >
|
||||
<mat-grid-tile *ngFor="let result of filteredResults">
|
||||
<mat-card class="result-card small-card">
|
||||
<mat-checkbox [checked]="isSelected(result.name)" (change)="onCheckboxChange($event, result.name, result['@id'])" class="result-checkbox"></mat-checkbox>
|
||||
<mat-card-title class="result-title">{{ result.name }}</mat-card-title>
|
||||
<mat-card-content class="result-content">
|
||||
<p class="result-type">{{ result.type !== 'client' ? result.type : '' }}</p>
|
||||
<p class="result-ip" *ngIf="result.type === 'client'">{{ result.ip }}</p>
|
||||
<p class="result-mac" *ngIf="result.type === 'client'">{{ result.mac }}</p>
|
||||
<p class="result-status" *ngIf="result.type === 'client'">{{ result.status }}</p>
|
||||
<p *ngIf="result.type !== 'client'" class="result-internal-units">{{ 'internalUnits' | translate }}: {{ result.children.length }}</p>
|
||||
<p *ngIf="result.type !== 'client'" class="result-clients">{{ 'clients' | translate }}: {{ result.clients.length }}</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card
|
||||
class="result-card small-card"
|
||||
[ngClass]="{
|
||||
'card-og-live': result.status === 'og-live',
|
||||
'card-busy': result.status === 'busy',
|
||||
'card-windows': result.status === 'windows' || result.status === 'windows-session',
|
||||
'card-linux': result.status === 'linux' || result.status === 'linux-session',
|
||||
'card-macos': result.status === 'macos',
|
||||
'card-off': result.status === 'off'
|
||||
}"
|
||||
matTooltip="{{ result.status }}"
|
||||
>
|
||||
<div class="card-content-wrapper">
|
||||
<mat-checkbox
|
||||
[checked]="isSelected(result.name)"
|
||||
(change)="onCheckboxChange($event, result.name, result['@id'])"
|
||||
class="result-checkbox"
|
||||
>
|
||||
</mat-checkbox>
|
||||
<div class="text-content">
|
||||
<mat-card-title class="result-title">{{ result.name }}</mat-card-title>
|
||||
<mat-card-content class="result-content">
|
||||
<p class="result-type">{{ result.type !== 'client' ? result.type : '' }}</p>
|
||||
<p class="result-ip" *ngIf="result.type === 'client'">{{ result.ip }}</p>
|
||||
<p class="result-mac" *ngIf="result.type === 'client'">{{ result.mac }}</p>
|
||||
<p class="result-status" *ngIf="result.type === 'client'">{{ result.status }}</p>
|
||||
<p *ngIf="result.type !== 'client'" class="result-internal-units">
|
||||
{{ 'internalUnits' | translate }}: {{ result.children.length }}
|
||||
</p>
|
||||
<p *ngIf="result.type !== 'client'" class="result-clients">
|
||||
{{ 'clients' | translate }}: {{ result.clients.length }}
|
||||
</p>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card>
|
||||
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-container *ngIf="viewMode === 'list'">
|
||||
<div class="result-list" *ngFor="let result of filteredResults">
|
||||
<mat-card class="result-card-list">
|
||||
|
@ -92,7 +121,7 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="paginator-container" joyrideStep="paginationStep" text="Usa el paginador para navegar entre los resultados.">
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions" (page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
|
|
@ -430,6 +430,42 @@ export class AdvancedSearchComponent {
|
|||
});
|
||||
}
|
||||
|
||||
onCommandSelect(action: any): void {
|
||||
if (action === 'partition') {
|
||||
this.openPartitionAssistant();
|
||||
}
|
||||
|
||||
if (action === 'create-image') {
|
||||
this.openCreateImageAssistant();
|
||||
}
|
||||
|
||||
if (action === 'deploy-image') {
|
||||
this.openDeployImageAssistant();
|
||||
}
|
||||
}
|
||||
|
||||
openPartitionAssistant(): void {
|
||||
const client = this.selectedElements[0];
|
||||
console.log(client)
|
||||
this.router.navigate([`${client}/partition-assistant`]).then(r => {
|
||||
console.log('navigated', r);
|
||||
});
|
||||
}
|
||||
|
||||
openCreateImageAssistant(): void {
|
||||
const client = this.selectedElements[0];
|
||||
this.router.navigate([`${client}/create-image`]).then(r => {
|
||||
console.log('navigated', r);
|
||||
});
|
||||
}
|
||||
|
||||
openDeployImageAssistant(): void {
|
||||
const client = this.selectedElements[0];
|
||||
this.router.navigate([`${client}/deploy-image`]).then(r => {
|
||||
console.log('navigated', r);
|
||||
});
|
||||
}
|
||||
|
||||
onDobleClick(event: MouseEvent, data: any, type: string): void {
|
||||
if (type === 'client') {
|
||||
this.router.navigate(['client', data]);
|
||||
|
@ -449,8 +485,7 @@ export class AdvancedSearchComponent {
|
|||
'saveFiltersStep',
|
||||
'sendActionStep',
|
||||
'addPxeStep',
|
||||
'resultsStep',
|
||||
'paginationStep'
|
||||
'resultsStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
|
|
|
@ -4,90 +4,77 @@
|
|||
<button mat-flat-button color="primary" [matMenuTriggerFor]="commandMenu">{{ 'commandsButton' | translate }}</button>
|
||||
</div>
|
||||
<mat-menu #commandMenu="matMenu">
|
||||
<button mat-menu-item *ngFor="let command of commands" (click)="onCommandSelect(command)">
|
||||
<button mat-menu-item *ngFor="let command of arrayCommands" (click)="onCommandSelect(command.slug)">
|
||||
{{ command.name }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
<mat-tab-group dynamicHeight>
|
||||
<mat-tab label="Datos generales">
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!loading" class="client-info">
|
||||
<div class="info-section">
|
||||
<mat-tab-group dynamicHeight>
|
||||
<mat-tab label="{{ 'generalDataTab' | translate }}">
|
||||
<div *ngIf="!loading" class="client-info">
|
||||
<div class="info-section">
|
||||
<div class="two-column-table">
|
||||
<div class="table-row" *ngFor="let clientData of generalData">
|
||||
<div class="column property">{{ clientData?.property }}</div>
|
||||
<div class="column value">{{ clientData?.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="{{ 'networkPropertiesTab' | translate }}">
|
||||
<div class="two-column-table">
|
||||
<div class="table-row" *ngFor="let clientData of networkData">
|
||||
<div class="column property">{{ clientData?.property }}</div>
|
||||
<div class="column value">{{ clientData?.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
|
||||
<div class="header-container">
|
||||
<h2 class="title">{{ 'disksPartitionsTitle' | translate }}</h2>
|
||||
</div>
|
||||
|
||||
<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 | translate }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef !== 'size'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'size'">
|
||||
<mat-chip color="primary">
|
||||
{{ (image.size / 1024).toFixed(2) }} GB
|
||||
</mat-chip>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="charts-wrapper">
|
||||
<ng-container *ngIf="diskUsageData && diskUsageData.length > 0">
|
||||
<div class="charts-row">
|
||||
<div *ngFor="let disk of diskUsageData" class="disk-usage">
|
||||
<h3>{{ 'diskTitle' | translate }} {{ disk.diskNumber }}</h3>
|
||||
<div class="chart">
|
||||
<svg viewBox="0 0 36 36" class="circular-chart">
|
||||
<path
|
||||
class="circle-bg"
|
||||
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||
|
||||
<ng-container *ngFor="let partition of disk.partitions; let i = index">
|
||||
<path
|
||||
class="circle partition-{{ i }}"
|
||||
[attr.stroke-dasharray]="(partition.size / 1024).toFixed(2) + ', 100'"
|
||||
[attr.stroke-dashoffset]="getStrokeOffset(disk.partitions, i)"
|
||||
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
|
||||
</ng-container>
|
||||
|
||||
<text x="18" y="20.35" class="percentage">{{ (disk.used / disk.total * 100).toFixed(0) }}%</text>
|
||||
</svg>
|
||||
</div>
|
||||
<p>{{ 'diskUsedLabel' | translate }}: {{ disk.used }} GB ({{ disk.percentage }}%)</p>
|
||||
<p>{{ 'diskTotalLabel' | translate }}: {{ disk.total }} GB</p>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Discos/Particiones">
|
||||
<div class="header-container">
|
||||
<h2 class="title" i18n="@@adminImagesTitle">Discos/Particiones</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<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>
|
||||
<td mat-cell *matCellDef="let image" >
|
||||
<ng-container *ngIf="column.columnDef !== 'size'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'size'">
|
||||
<mat-chip color="primary" >
|
||||
{{ (image.size / 1024).toFixed(2) }} GB
|
||||
</mat-chip>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="charts-wrapper">
|
||||
<ng-container *ngIf="diskUsageData && diskUsageData.length > 0">
|
||||
<div class="charts-row">
|
||||
<div *ngFor="let disk of chartDisk" class="disk-usage">
|
||||
<ngx-charts-pie-chart
|
||||
[view]="view"
|
||||
[results]="disk.chartData"
|
||||
[legend]="showLegend">
|
||||
</ngx-charts-pie-chart>
|
||||
|
||||
<h3>Disco {{ disk.diskNumber }}</h3>
|
||||
<p>Usado: {{ (disk.used).toFixed(2) }} GB ({{ disk.percentage }}%)</p>
|
||||
<p>Total: {{ disk.total }} GB</p>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
|
|
|
@ -31,6 +31,24 @@ export class ClientMainViewComponent implements OnInit {
|
|||
diskUsageData: any[] = [];
|
||||
partitions: any[] = [];
|
||||
commands: any[] = [];
|
||||
chartDisk: any[] = [];
|
||||
view: [number, number] = [600, 300];
|
||||
showLegend: boolean = true;
|
||||
|
||||
arrayCommands: any[] = [
|
||||
{name: 'Enceder', slug: 'power-on'},
|
||||
{name: 'Apagar', slug: 'power-off'},
|
||||
{name: 'Reiniciar', slug: 'reboot'},
|
||||
{name: 'Iniciar Sesión', slug: 'login'},
|
||||
{name: 'Crear Image', slug: 'create-image'},
|
||||
{name: 'Deploy Image', slug: 'deploy-image'},
|
||||
{name: 'Eliminar Imagen Cache', slug: 'delete-image-cache'},
|
||||
{name: 'Particionar y Formatear', slug: 'partition'},
|
||||
{name: 'Inventario Software', slug: 'software-inventory'},
|
||||
{name: 'Inventario Hardware', slug: 'hardware-inventory'},
|
||||
{name: 'Ejecutar script', slug: 'run-script'},
|
||||
];
|
||||
|
||||
datePipe: DatePipe = new DatePipe('es-ES');
|
||||
columns = [
|
||||
{
|
||||
|
@ -58,6 +76,11 @@ export class ClientMainViewComponent implements OnInit {
|
|||
header: 'Uso',
|
||||
cell: (partition: any) => `${partition.memoryUsage} %`
|
||||
},
|
||||
{
|
||||
columnDef: 'operativeSystem',
|
||||
header: 'SO',
|
||||
cell: (partition: any) => `${partition.operativeSystem?.name}`
|
||||
},
|
||||
];
|
||||
displayedColumns = [...this.columns.map(column => column.columnDef)];
|
||||
isDiskUsageEmpty: boolean = true;
|
||||
|
@ -100,11 +123,11 @@ export class ClientMainViewComponent implements OnInit {
|
|||
this.networkData = [
|
||||
{ property: 'Remote Pc', value: this.clientData.remotePc || '' },
|
||||
{ property: 'Subred', value: this.clientData?.subnet || '' },
|
||||
{ property: 'OGlive', value: '' },
|
||||
{ property: 'OGlive', value: this.clientData?.ogLive?.name || '' },
|
||||
{ property: 'Autoexec', value: '' },
|
||||
{ property: 'Repositorio', value: '' },
|
||||
{ property: 'Repositorio', value: this.clientData?.repository?.name || '' },
|
||||
{ property: 'Validación', value: this.clientData?.organizationalUnit?.networkSettings?.validation || '' },
|
||||
{ property: 'Pxe', value: this.clientData?.template.name || '' },
|
||||
{ property: 'Pxe', value: this.clientData?.template?.name || '' },
|
||||
{ property: 'Creado por', value: this.clientData?.createdBy || '' }
|
||||
];
|
||||
}
|
||||
|
@ -124,19 +147,39 @@ export class ClientMainViewComponent implements OnInit {
|
|||
if (partition.partitionNumber === 0) {
|
||||
diskData!.total = Number((partition.size / 1024).toFixed(2));
|
||||
} else {
|
||||
diskData!.used += Number(((partition.size * (partition.memoryUsage / 100)) / 1024).toFixed(2));
|
||||
diskData!.used += Number((partition.size / 1024).toFixed(2));
|
||||
diskData!.partitions.push(partition);
|
||||
}
|
||||
});
|
||||
|
||||
this.diskUsageData = Array.from(diskUsageMap.entries()).map(([diskNumber, { total, used, partitions }]) => {
|
||||
const percentage = total > 0 ? Math.round((used / total) * 100) : 0;
|
||||
return { diskNumber, total, used, percentage, partitions };
|
||||
this.chartDisk = Array.from(diskUsageMap.entries()).map(([diskNumber, { total, used, partitions }]) => {
|
||||
const partitionData = partitions.map(partition => ({
|
||||
name: `Partición ${partition.partitionNumber}`,
|
||||
value: Number((partition.size / 1024).toFixed(2))
|
||||
}));
|
||||
|
||||
const freeSpace = total - used;
|
||||
if (freeSpace > 0) {
|
||||
partitionData.push({
|
||||
name: 'Espacio libre',
|
||||
value: Number(freeSpace.toFixed(2))
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
diskNumber,
|
||||
chartData: partitionData,
|
||||
total,
|
||||
used,
|
||||
percentage: total > 0 ? Math.round((used / total) * 100) : 0
|
||||
};
|
||||
});
|
||||
|
||||
this.diskUsageData = this.chartDisk;
|
||||
this.isDiskUsageEmpty = this.diskUsageData.length === 0;
|
||||
}
|
||||
|
||||
|
||||
getStrokeOffset(partitions: any[], index: number): number {
|
||||
const totalSize = partitions.reduce((acc, part) => acc + (part.size / 1024), 0);
|
||||
|
||||
|
@ -170,15 +213,34 @@ export class ClientMainViewComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
onCommandSelect(command: any): void {
|
||||
if (command.name === 'Particionar y Formatear') {
|
||||
onCommandSelect(action: any): void {
|
||||
if (action === 'partition') {
|
||||
this.openPartitionAssistant();
|
||||
}
|
||||
|
||||
if (action === 'create-image') {
|
||||
this.openCreateImageAssistant();
|
||||
}
|
||||
|
||||
if (action === 'deploy-image') {
|
||||
this.openDeployImageAssistant();
|
||||
}
|
||||
}
|
||||
|
||||
openPartitionAssistant(): void {
|
||||
console.log(this.clientData)
|
||||
this.router.navigate([`/client/${this.clientData.uuid}/partition-assistant`]).then(r => {
|
||||
this.router.navigate([`/clients/${this.clientData.uuid}/partition-assistant`]).then(r => {
|
||||
console.log('navigated', r);
|
||||
});
|
||||
}
|
||||
|
||||
openCreateImageAssistant(): void {
|
||||
this.router.navigate([`/clients/${this.clientData.uuid}/create-image`]).then(r => {
|
||||
console.log('navigated', r);
|
||||
});
|
||||
}
|
||||
|
||||
openDeployImageAssistant(): void {
|
||||
this.router.navigate([`/clients/${this.clientData.uuid}/deploy-image`]).then(r => {
|
||||
console.log('navigated', r);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.calendar-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.lists-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.card.unidad-card {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.select-container {
|
||||
margin-top: 20px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-string {
|
||||
flex: 2;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.search-boolean {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<div class="header-container">
|
||||
<h2 class="title" i18n="@@subnetsTitle">Crear Imagen desde {{ clientName }}</h2>
|
||||
<div class="subnets-button-row">
|
||||
<button mat-flat-button color="primary" (click)="save()">Guardar y ejecutar</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<div class="select-container">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Nombre canónico</mat-label>
|
||||
<input matInput [(ngModel)]="name" placeholder="Nombre canónico" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione imagen</mat-label>
|
||||
<mat-select [(ngModel)]="selectedImage">
|
||||
<mat-option>--</mat-option>
|
||||
<mat-option *ngFor="let image of images" [value]="image['@id']">{{ image.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-radio-group
|
||||
[(ngModel)]="selectedPartition"
|
||||
>
|
||||
<mat-radio-button [value]="row">
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
{{ column.cell(image) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CreateImageComponent } from './create-image.component';
|
||||
|
||||
describe('CreateImageComponent', () => {
|
||||
let component: CreateImageComponent;
|
||||
let fixture: ComponentFixture<CreateImageComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [CreateImageComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CreateImageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,168 @@
|
|||
import {Component, EventEmitter, Output} from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {MatButton} from "@angular/material/button";
|
||||
import {MatDivider} from "@angular/material/divider";
|
||||
import {NgForOf, NgIf} from "@angular/common";
|
||||
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||
import {
|
||||
MatCell, MatCellDef,
|
||||
MatColumnDef,
|
||||
MatHeaderCell,
|
||||
MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef,
|
||||
MatTable,
|
||||
MatTableDataSource
|
||||
} from "@angular/material/table";
|
||||
import {MatChip} from "@angular/material/chips";
|
||||
import {MatCheckbox} from "@angular/material/checkbox";
|
||||
import {SelectionModel} from "@angular/cdk/collections";
|
||||
import {MatRadioButton, MatRadioGroup} from "@angular/material/radio";
|
||||
import {MatFormField, MatLabel} from "@angular/material/form-field";
|
||||
import {MatOption} from "@angular/material/autocomplete";
|
||||
import {MatSelect} from "@angular/material/select";
|
||||
import {MatInput} from "@angular/material/input";
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-image',
|
||||
templateUrl: './create-image.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatButton,
|
||||
MatDivider,
|
||||
NgForOf,
|
||||
NgIf,
|
||||
ReactiveFormsModule,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCell,
|
||||
MatHeaderCellDef,
|
||||
MatCell,
|
||||
MatCellDef,
|
||||
MatChip,
|
||||
MatHeaderRow,
|
||||
MatRow,
|
||||
MatHeaderRowDef,
|
||||
MatRowDef,
|
||||
MatCheckbox,
|
||||
MatRadioGroup,
|
||||
MatRadioButton,
|
||||
MatFormField,
|
||||
MatLabel,
|
||||
MatOption,
|
||||
MatSelect,
|
||||
MatInput,
|
||||
FormsModule
|
||||
],
|
||||
styleUrl: './create-image.component.css'
|
||||
})
|
||||
export class CreateImageComponent {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
@Output() dataChange = new EventEmitter<any>();
|
||||
|
||||
errorMessage = '';
|
||||
clientId: string | null = null;
|
||||
partitions: any[] = [];
|
||||
images: any[] = [];
|
||||
clientName: string = '';
|
||||
selectedImage: string | null = null;
|
||||
selectedPartition: any = null;
|
||||
name: string = '';
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
columns = [
|
||||
{
|
||||
columnDef: 'diskNumber',
|
||||
header: 'Disco',
|
||||
cell: (partition: any) => `${partition.diskNumber}`
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionNumber',
|
||||
header: 'Particion',
|
||||
cell: (partition: any) => `${partition.partitionNumber}`
|
||||
},
|
||||
{
|
||||
columnDef: 'size',
|
||||
header: 'Tamaño',
|
||||
cell: (partition: any) => `${partition.size} MB`
|
||||
},
|
||||
{
|
||||
columnDef: 'filesystem',
|
||||
header: 'Sistema de ficheros',
|
||||
cell: (partition: any) => `${partition.filesystem}`
|
||||
},
|
||||
{
|
||||
columnDef: 'operativeSystem',
|
||||
header: 'SO',
|
||||
cell: (partition: any) => `${partition.operativeSystem?.name}`
|
||||
}
|
||||
];
|
||||
|
||||
displayedColumns = ['select', ...this.columns.map(column => column.columnDef)];
|
||||
selection = new SelectionModel(true, []);
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.clientId = this.route.snapshot.paramMap.get('id');
|
||||
|
||||
this.loadPartitions();
|
||||
this.loadImages();
|
||||
}
|
||||
|
||||
loadPartitions() {
|
||||
const url = `${this.baseUrl}/clients/${this.clientId}`;
|
||||
this.http.get(url).subscribe(
|
||||
(response: any) => {
|
||||
if (response.partitions) {
|
||||
this.clientName = response.name;
|
||||
|
||||
this.dataSource.data = response.partitions.filter((partition: any) => {
|
||||
return partition.partitionNumber !== 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar los datos del cliente:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadImages() {
|
||||
const url = `${this.baseUrl}/images?created=true&page=1&itemsPerPage=1000`;
|
||||
this.http.get(url).subscribe(
|
||||
(response: any) => {
|
||||
this.images = response['hydra:member'];
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar las imágenes:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const payload = {
|
||||
client: `/clients/${this.clientId}`,
|
||||
name: this.name,
|
||||
image: this.selectedImage,
|
||||
partition: this.selectedPartition['@id'],
|
||||
source: 'assistant'
|
||||
};
|
||||
|
||||
|
||||
this.http.post(`${this.baseUrl}/images`, payload)
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Imagen creada exitosamente');
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error:', error);
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.option-container {
|
||||
margin: 20px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.deploy-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.select-container {
|
||||
margin-top: 20px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1 1 calc(33.33% - 16px);
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-string {
|
||||
flex: 2;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.search-boolean {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
<div class="header-container">
|
||||
<h2 class="title" i18n="@@subnetsTitle">Deploy imagen en {{ clientName }}</h2>
|
||||
<div class="subnets-button-row">
|
||||
<button mat-flat-button color="primary" (click)="save()">Guardar</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<div class="select-container">
|
||||
<div class="option-container">
|
||||
<mat-radio-group [(ngModel)]="selectedOption" aria-label="Selecciona una opcion">
|
||||
<mat-radio-button value="update-cache">Actualizar cache</mat-radio-button>
|
||||
<mat-radio-button value="deploy-image">Deploy imagen</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="deploy-container">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione imagen</mat-label>
|
||||
<mat-select [(ngModel)]="selectedImage">
|
||||
<mat-option *ngFor="let image of images" [value]="image['@id']">{{ image.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione método de deploy</mat-label>
|
||||
<mat-select [(ngModel)]="selectedMethod">
|
||||
<mat-option *ngFor="let method of deployMethods" [value]="method">{{ method }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-radio-group [(ngModel)]="selectedPartition">
|
||||
<mat-radio-button [value]="row">
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
{{ column.cell(image) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
<h3 *ngIf="isMethod('multicast')" class="input-group">Opciones multicast</h3>
|
||||
<h3 *ngIf="isMethod('torrent')" class="input-group">Opciones torrent</h3>
|
||||
<div *ngIf="isMethod('multicast')" class="input-group">
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label>Puerto</mat-label>
|
||||
<input matInput [(ngModel)]="mcastPort">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label>Dirección</mat-label>
|
||||
<input matInput [(ngModel)]="mcastIp">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label i18n="@@mcastModeLabel">Modo Multicast</mat-label>
|
||||
<mat-select [(ngModel)]="mcastMode">
|
||||
<mat-option *ngFor="let option of multicastModeOptions" [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label>Velocidad</mat-label>
|
||||
<input matInput [(ngModel)]="mcastSpeed">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label>Máximo Clientes</mat-label>
|
||||
<input matInput [(ngModel)]="mcastMaxClients">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label>Tiempo Máximo de Espera</mat-label>
|
||||
<input matInput [(ngModel)]="mcastMaxTime">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isMethod('torrent')" class="input-group">
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label i18n="@@p2pModeLabel">Modo P2P</mat-label>
|
||||
<mat-select [(ngModel)]="p2pMode">
|
||||
<mat-option *ngFor="let option of p2pModeOptions" [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label>Semilla</mat-label>
|
||||
<input matInput [(ngModel)]="p2pTime">
|
||||
</mat-form-field>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DeployImageComponent } from './deploy-image.component';
|
||||
|
||||
describe('DeployImageComponent', () => {
|
||||
let component: DeployImageComponent;
|
||||
let fixture: ComponentFixture<DeployImageComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [DeployImageComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DeployImageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,173 @@
|
|||
import {Component, EventEmitter, Output} from '@angular/core';
|
||||
import {MatTableDataSource} from "@angular/material/table";
|
||||
import {SelectionModel} from "@angular/cdk/collections";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
|
||||
@Component({
|
||||
selector: 'app-deploy-image',
|
||||
templateUrl: './deploy-image.component.html',
|
||||
styleUrl: './deploy-image.component.css'
|
||||
})
|
||||
export class DeployImageComponent {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
@Output() dataChange = new EventEmitter<any>();
|
||||
|
||||
errorMessage = '';
|
||||
clientId: string | null = null;
|
||||
partitions: any[] = [];
|
||||
images: any[] = [];
|
||||
clientName: string = '';
|
||||
selectedImage: string | null = null;
|
||||
selectedOption: string | null = null;
|
||||
selectedMethod: string | null = null;
|
||||
selectedPartition: any = null;
|
||||
mcastIp: string = '';
|
||||
mcastPort: string = '';
|
||||
mcastMode: string = '';
|
||||
mcastSpeed: Number = 0;
|
||||
mcastMaxClients: Number = 0;
|
||||
mcastMaxTime: Number = 0;
|
||||
p2pMode: string = '';
|
||||
p2pTime: Number = 0;
|
||||
name: string = '';
|
||||
|
||||
protected p2pModeOptions = [
|
||||
{ name: 'Leecher', value: 'p2p-mode-leecher' },
|
||||
{ name: 'Peer', value: 'p2p-mode-peer' },
|
||||
{ name: 'Seeder', value: 'p2p-mode-seeder' },
|
||||
];
|
||||
protected multicastModeOptions = [
|
||||
{"name": 'Half duplex', "value": "half-duplex"},
|
||||
{"name": 'Full duplex', "value": "full-duplex"},
|
||||
];
|
||||
|
||||
allMethods = [
|
||||
'multicast',
|
||||
'multicast-direct',
|
||||
'unicast',
|
||||
'unicast-direct',
|
||||
'torrent'
|
||||
];
|
||||
|
||||
updateCacheMethods = [
|
||||
'multicast',
|
||||
'unicast',
|
||||
'torrent'
|
||||
];
|
||||
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
columns = [
|
||||
{
|
||||
columnDef: 'diskNumber',
|
||||
header: 'Disco',
|
||||
cell: (partition: any) => `${partition.diskNumber}`
|
||||
},
|
||||
{
|
||||
columnDef: 'partitionNumber',
|
||||
header: 'Particion',
|
||||
cell: (partition: any) => `${partition.partitionNumber}`
|
||||
},
|
||||
{
|
||||
columnDef: 'size',
|
||||
header: 'Tamaño',
|
||||
cell: (partition: any) => `${partition.size} MB`
|
||||
},
|
||||
{
|
||||
columnDef: 'filesystem',
|
||||
header: 'Sistema de ficheros',
|
||||
cell: (partition: any) => `${partition.filesystem}`
|
||||
},
|
||||
{
|
||||
columnDef: 'operativeSystem',
|
||||
header: 'SO',
|
||||
cell: (partition: any) => `${partition.operativeSystem?.name}`
|
||||
}
|
||||
];
|
||||
|
||||
displayedColumns = ['select', ...this.columns.map(column => column.columnDef)];
|
||||
selection = new SelectionModel(true, []);
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.clientId = this.route.snapshot.paramMap.get('id');
|
||||
|
||||
this.loadPartitions();
|
||||
this.loadImages();
|
||||
}
|
||||
|
||||
get deployMethods() {
|
||||
return this.selectedOption === 'update-cache' ? this.updateCacheMethods : this.allMethods;
|
||||
}
|
||||
|
||||
isMethod(method: string): boolean {
|
||||
return this.selectedMethod === method;
|
||||
}
|
||||
|
||||
loadPartitions() {
|
||||
const url = `${this.baseUrl}/clients/${this.clientId}`;
|
||||
this.http.get(url).subscribe(
|
||||
(response: any) => {
|
||||
if (response.partitions) {
|
||||
this.clientName = response.name;
|
||||
this.dataSource.data = response.partitions.filter((partition: any) => {
|
||||
return partition.partitionNumber !== 0;
|
||||
});
|
||||
this.p2pMode = response.organizationalUnit?.networkSettings?.p2pMode;
|
||||
this.p2pTime = response.organizationalUnit?.networkSettings?.p2pTime;
|
||||
this.mcastSpeed = response.organizationalUnit?.networkSettings?.mcastSpeed;
|
||||
this.mcastMode = response.organizationalUnit?.networkSettings?.mcastMode;
|
||||
this.mcastPort = response.organizationalUnit?.networkSettings?.mcastPort;
|
||||
this.mcastIp = response.organizationalUnit?.networkSettings?.mcastIp;
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar los datos del cliente:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadImages() {
|
||||
const url = `${this.baseUrl}/images?page=1&itemsPerPage=1000`;
|
||||
this.http.get(url).subscribe(
|
||||
(response: any) => {
|
||||
this.images = response['hydra:member'];
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar las imágenes:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const payload = {
|
||||
client: `/clients/${this.clientId}`,
|
||||
method: this.selectedMethod,
|
||||
partition: this.selectedPartition['@id'],
|
||||
p2pMode: this.p2pMode,
|
||||
p2pTime: this.p2pTime,
|
||||
mcastIp: this.mcastIp,
|
||||
mcastPort: this.mcastPort,
|
||||
mcastMode: this.mcastMode,
|
||||
mcastSpeed: this.mcastSpeed,
|
||||
};
|
||||
|
||||
this.http.post(`${this.baseUrl}${this.selectedImage}/deploy-image`, payload)
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Imagen creada exitosamente');
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error:', error);
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,22 +1,5 @@
|
|||
<<<<<<< Updated upstream
|
||||
<h2 mat-dialog-title>Asistente de particionado</h2>
|
||||
|
||||
=======
|
||||
<div class="header-container">
|
||||
<h2 class="title">{{ 'partitionAssistantTitle' | translate }}</h2>
|
||||
<div class="subnets-button-row">
|
||||
<button mat-flat-button color="primary" (click)="save()">{{ 'saveButton' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<div class="partition-assistant" *ngFor="let disk of disks; let i = index">
|
||||
<div>
|
||||
<label for="disk-number-{{i}}">{{ 'diskLabel' | translate }} {{ disk.diskNumber }}:</label>
|
||||
<span class="disk-size"> {{ 'diskSizeLabel' | translate }}: {{ (disk.totalDiskSize / 1024).toFixed(2) }} GB</span>
|
||||
</div>
|
||||
>>>>>>> Stashed changes
|
||||
|
||||
<mat-dialog-content>
|
||||
<div class="partition-assistant" *ngFor="let disk of disks; let i = index">
|
||||
<div class="header">
|
||||
|
@ -34,67 +17,17 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<button mat-flat-button color="primary" (click)="addPartition(disk.diskNumber)"> + </button>
|
||||
|
||||
<table class="partition-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Partición</th>
|
||||
<th>Tipo partición</th>
|
||||
<th>Tamaño (MB)</th>
|
||||
<th>Uso (%)</th>
|
||||
<th>Formatear</th>
|
||||
<th>Eliminar</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let partition of disk.partitions; let j = index">
|
||||
<td>{{ partition.partitionNumber }}</td>
|
||||
<td>
|
||||
<select [(ngModel)]="partition.type">
|
||||
<option value="NTFS">NTFS</option>
|
||||
<option value="LINUX">LINUX</option>
|
||||
<option value="CACHE">CACHE</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
[(ngModel)]="partition.size"
|
||||
(input)="updatePartitionSize(disk.diskNumber, j, partition.size)"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
[(ngModel)]="partition.memoryUsage"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" [(ngModel)]="partition.format" />
|
||||
</td>
|
||||
<td>
|
||||
<button (click)="removePartition(disk.diskNumber, partition)" class="remove-btn">X</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<<<<<<< Updated upstream
|
||||
</mat-dialog-content>
|
||||
=======
|
||||
|
||||
<button mat-flat-button color="primary" (click)="addPartition(disk.diskNumber)"> + </button>
|
||||
|
||||
<table class="partition-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'partitionColumn' | translate }}</th>
|
||||
<th>{{ 'partitionTypeColumn' | translate }}</th>
|
||||
<th>{{ 'partitionSizeColumn' | translate }}</th>
|
||||
<th>{{ 'usageColumn' | translate }}</th>
|
||||
<th>{{ 'formatColumn' | translate }}</th>
|
||||
<th>{{ 'deleteColumn' | translate }}</th>
|
||||
<th>Partición</th>
|
||||
<th>Tipo partición</th>
|
||||
<th>Tamaño (MB)</th>
|
||||
<th>Uso (%)</th>
|
||||
<th>Formatear</th>
|
||||
<th>Eliminar</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -102,9 +35,9 @@
|
|||
<td>{{ partition.partitionNumber }}</td>
|
||||
<td>
|
||||
<select [(ngModel)]="partition.type">
|
||||
<option value="NTFS">{{ 'ntfsOption' | translate }}</option>
|
||||
<option value="LINUX">{{ 'linuxOption' | translate }}</option>
|
||||
<option value="CACHE">{{ 'cacheOption' | translate }}</option>
|
||||
<option value="NTFS">NTFS</option>
|
||||
<option value="LINUX">LINUX</option>
|
||||
<option value="CACHE">CACHE</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -124,12 +57,11 @@
|
|||
<input type="checkbox" [(ngModel)]="partition.format" />
|
||||
</td>
|
||||
<td>
|
||||
<button (click)="removePartition(disk.diskNumber, partition)" class="remove-btn">{{ 'deleteButton' | translate }}</button>
|
||||
<button (click)="removePartition(disk.diskNumber, partition)" class="remove-btn">X</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
>>>>>>> Stashed changes
|
||||
|
||||
<div *ngIf="errorMessage" class="error-message">{{ errorMessage }}</div>
|
||||
|
|
|
@ -41,7 +41,6 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
|
||||
ngOnInit() {
|
||||
this.clientId = this.route.snapshot.paramMap.get('id');
|
||||
|
||||
this.loadPartitions();
|
||||
}
|
||||
|
||||
|
@ -112,7 +111,6 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
|
||||
if (disk) {
|
||||
const remainingGB = this.getRemainingGB(disk.partitions, disk.totalDiskSize);
|
||||
console.log(remainingGB)
|
||||
if (remainingGB > 0) {
|
||||
const maxPartitionNumber =
|
||||
disk.partitions.length > 0 ? Math.max(...disk.partitions.map((p) => p.partitionNumber)) : 0;
|
||||
|
@ -142,20 +140,41 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
const remainingGB = this.getRemainingGB(disk.partitions, disk.totalDiskSize) + partition.size;
|
||||
|
||||
if (size > remainingGB) {
|
||||
this.errorMessage = `El tamaño de la partición no puede superar el espacio libre (${remainingGB.toFixed(
|
||||
2
|
||||
)} GB).`;
|
||||
this.errorMessage = `El tamaño de la partición no puede superar el espacio libre (${remainingGB.toFixed(2)} GB).`;
|
||||
} else {
|
||||
this.errorMessage = '';
|
||||
partition.size = size;
|
||||
partition.sizeBytes = size;
|
||||
|
||||
partition.percentage = (size / disk.totalDiskSize) * 100;
|
||||
this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePartitionPercentage(diskNumber: number, index: number, percentage: number) {
|
||||
const disk = this.disks.find((d) => d.diskNumber === diskNumber);
|
||||
if (disk) {
|
||||
const partition = disk.partitions[index];
|
||||
|
||||
const newSizeMB = (percentage / 100) * disk.totalDiskSize;
|
||||
|
||||
const totalPercentage = disk.partitions.reduce((sum, part) => sum + (part === partition ? percentage : part.percentage), 0);
|
||||
|
||||
if (totalPercentage > 100) {
|
||||
this.errorMessage = 'El tamaño total en porcentaje de las particiones no puede exceder el 100%';
|
||||
partition.percentage = 100 - (totalPercentage - percentage);
|
||||
} else {
|
||||
this.errorMessage = '';
|
||||
partition.percentage = percentage;
|
||||
partition.size = newSizeMB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
getRemainingGB(partitions: Partition[], totalDiskSize: number): number {
|
||||
console.log(totalDiskSize)
|
||||
const totalUsedGB = partitions.reduce((acc, partition) => acc + partition.size, 0);
|
||||
return Math.max(0, totalDiskSize - totalUsedGB);
|
||||
}
|
||||
|
@ -228,6 +247,7 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
this.http.post(this.apiUrl, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Partición creada exitosamente');
|
||||
window.location.reload();
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al crear la partición:', error);
|
||||
|
@ -239,6 +259,7 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
this.http.patch(patchUrl, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Partición actualizada exitosamente');
|
||||
window.location.reload();
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al actualizar la partición:', error);
|
||||
|
@ -249,7 +270,6 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
removePartition(diskNumber: number, partition: Partition) {
|
||||
const disk = this.disks.find((d) => d.diskNumber === diskNumber);
|
||||
|
||||
|
@ -264,6 +284,7 @@ export class PartitionAssistantComponent implements OnInit {
|
|||
this.http.delete(deleteUrl).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Partición eliminada exitosamente');
|
||||
window.location.reload();
|
||||
},
|
||||
(error) => {}
|
||||
);
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
.partition-assistant {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background-color: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.partition-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.partition-table th {
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
padding: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.partition-table td {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.partition-table select {
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
button.mat-flat-button {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
button.mat-flat-button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
interface Image {
|
||||
'@id': string;
|
||||
'@type': string;
|
||||
name: string;
|
||||
description: string;
|
||||
comments: string;
|
||||
uuid: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
interface Partition {
|
||||
diskNumber: number;
|
||||
partitionNumber: number;
|
||||
associatedImageId?: string;
|
||||
associatedOgLive?: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-restore-image',
|
||||
templateUrl: './restore-image.component.html',
|
||||
styleUrls: ['./restore-image.component.css']
|
||||
})
|
||||
export class RestoreImageComponent implements OnInit {
|
||||
@Input() data: any;
|
||||
|
||||
disks: { diskNumber: number; partitions: Partition[] }[] = [];
|
||||
availableImages: Image[] = [];
|
||||
availableOgLives: string[] = [];
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initializeDisks();
|
||||
this.fetchAvailableImages();
|
||||
this.availableOgLives = ['LiveCD1', 'LiveCD2', 'LiveCD3'];
|
||||
}
|
||||
|
||||
initializeDisks() {
|
||||
const partitionsFromData = this.data.partitions;
|
||||
const disksMap = new Map<number, Partition[]>();
|
||||
|
||||
partitionsFromData.forEach((partition: any) => {
|
||||
if (!disksMap.has(partition.diskNumber)) {
|
||||
disksMap.set(partition.diskNumber, []);
|
||||
}
|
||||
|
||||
disksMap.get(partition.diskNumber)!.push({
|
||||
diskNumber: partition.diskNumber,
|
||||
partitionNumber: partition.partitionNumber
|
||||
});
|
||||
});
|
||||
|
||||
disksMap.forEach((partitions, diskNumber) => {
|
||||
this.disks.push({ diskNumber, partitions });
|
||||
});
|
||||
}
|
||||
|
||||
fetchAvailableImages() {
|
||||
const url = 'http://127.0.0.1:8001/images?page=1&itemsPerPage=30';
|
||||
this.http.get(url).subscribe(
|
||||
(response: any) => {
|
||||
this.availableImages = response['hydra:member'];
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al obtener las imágenes:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onImageSelected(partition: Partition, event: Event) {
|
||||
const selectElement = event.target as HTMLSelectElement;
|
||||
partition.associatedImageId = selectElement.value;
|
||||
}
|
||||
|
||||
onOgLiveSelected(partition: Partition, event: Event) {
|
||||
const selectElement = event.target as HTMLSelectElement;
|
||||
partition.associatedOgLive = selectElement.value;
|
||||
}
|
||||
|
||||
saveAssociations() {
|
||||
this.disks.forEach(disk => {
|
||||
disk.partitions.forEach(partition => {
|
||||
if (partition.associatedImageId || partition.associatedOgLive) {
|
||||
console.log(
|
||||
`Guardando para disco ${partition.diskNumber}, partición ${partition.partitionNumber}, ` +
|
||||
`Imagen ID: ${partition.associatedImageId}, OgLive: ${partition.associatedOgLive}`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -62,3 +62,31 @@ button{
|
|||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.chip-busy {
|
||||
background-color: red !important;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.chip-og-live {
|
||||
background-color: yellow !important;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.chip-windows,
|
||||
.chip-windows-session,
|
||||
.chip-macos {
|
||||
background-color: blue !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chip-linux,
|
||||
.chip-linux-session {
|
||||
background-color: purple !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chip-off {
|
||||
background-color: grey !important;
|
||||
color: white;
|
||||
}
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" i18n="@@adminImagesTitle" joyrideStep="title3Step" text="En esta pantalla se podran administrar todos los clientes del sistema sin jerarquias.">{{ 'adminImagesTitle' | translate }}</h2>
|
||||
<h2 class="title" i18n="@@adminImagesTitle" joyrideStep="clientTabtitleStep" text="En esta pantalla se podran administrar todos los clientes del sistema sin jerarquias.">{{ 'adminImagesTitle' | translate }}</h2>
|
||||
<div class="images-button-row">
|
||||
<button mat-flat-button color="primary" (click)="resetFilters()" joyrideStep="resetFiltersStep" text="Reinicia los filtros aplicados para mostrar todos los clientes.">{{ 'resetFiltersButton' | translate }}</button>
|
||||
<button mat-flat-button color="primary" (click)="addClient($event)" joyrideStep="addClientStep" text="Añade un nuevo cliente a la lista.">{{ 'addClientButton' | translate }}</button>
|
||||
<button mat-flat-button color="primary" (click)="resetFilters()" joyrideStep="clientTabResetFiltersStep" text="Reinicia los filtros aplicados para mostrar todos los clientes.">{{ 'resetFiltersButton' | translate }}</button>
|
||||
<button mat-flat-button color="primary" (click)="addClient($event)" joyrideStep="clientTabaddClientStep" text="Añade un nuevo cliente a la lista.">{{ 'addClientButton' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container" joyrideStep="searchContainerStep" text="Filtra los clientes por nombre, IP, MAC o unidad organizativa.">
|
||||
<div class="search-container" joyrideStep="clientTabsearchContainerStep" text="Filtra los clientes por nombre, IP, MAC o unidad organizativa.">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label i18n="@@searchLabel">{{ 'searchClientNameLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
|
@ -48,7 +48,7 @@
|
|||
</div>
|
||||
|
||||
<div *ngIf="loading">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="Lista de clientes filtrados por tus criterios de búsqueda.">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="clientTabtableStep" text="Lista de clientes filtrados por tus criterios de búsqueda.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let client" >
|
||||
|
@ -79,7 +79,7 @@
|
|||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">{{ 'columnActions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let client" style="text-align: center;" joyrideStep="actionsStep" text="Acciones disponibles para cada cliente, como ver, editar o eliminar.">
|
||||
<td mat-cell *matCellDef="let client" style="text-align: center;" joyrideStep="clientTabactionsStep" text="Acciones disponibles para cada cliente, como ver, editar o eliminar.">
|
||||
<button *ngIf="!syncStatus" mat-icon-button color="primary" (click)="getStatus(client)"><mat-icon>sync</mat-icon></button>
|
||||
<button *ngIf="syncStatus" mat-icon-button color="primary"><mat-spinner diameter="24"></mat-spinner></button>
|
||||
<button mat-icon-button color="info" (click)="handleClientClick($event, client)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
|
||||
|
@ -94,7 +94,7 @@
|
|||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
<div class="paginator-container" joyrideStep="paginationStep" text="Navega entre las páginas de resultados utilizando el paginador.">
|
||||
<div class="paginator-container" joyrideStep="clientTabpaginationStep" text="Navega entre las páginas de resultados utilizando el paginador.">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
|
|
|
@ -144,7 +144,7 @@ export class ClientTabViewComponent {
|
|||
this.syncStatus = false;
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
this.toastService.error('Error de conexión con el cliente');
|
||||
this.syncStatus = false;
|
||||
}
|
||||
);
|
||||
|
@ -153,7 +153,7 @@ export class ClientTabViewComponent {
|
|||
|
||||
handleClientClick(event: MouseEvent, client: any): void {
|
||||
event.stopPropagation();
|
||||
this.router.navigate(['client', client.uuid], { state: { clientData: client } });
|
||||
this.router.navigate(['clients', client.uuid], { state: { clientData: client } });
|
||||
|
||||
}
|
||||
|
||||
|
@ -196,13 +196,13 @@ export class ClientTabViewComponent {
|
|||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'title3Step',
|
||||
'resetFiltersStep',
|
||||
'addClientStep',
|
||||
'searchContainerStep',
|
||||
'tableStep',
|
||||
'actionsStep',
|
||||
'paginationStep'
|
||||
'clientTabtitleStep',
|
||||
'clientTabResetFiltersStep',
|
||||
'clientTabaddClientStep',
|
||||
'clientTabsearchContainerStep',
|
||||
'clientTabtableStep',
|
||||
'clientTabactionsStep',
|
||||
'clientTabpaginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
height: 600px;
|
||||
overflow-y: auto;
|
||||
box-shadow: none !important;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.elements-card {
|
||||
|
@ -213,4 +213,4 @@ mat-card {
|
|||
|
||||
.mat-tooltip {
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminGroupsTitle' | translate }}</h2>
|
||||
<div class="groups-button-row" joyrideStep="addStep" text="{{ 'addStepText' | translate }}">
|
||||
<h2 class="title" joyrideStep="groupsTitleStepText" text="{{ 'groupsTitleStepText' | translate }}">{{ 'adminGroupsTitle' | translate }}</h2>
|
||||
<div class="groups-button-row" joyrideStep="addStep" text="{{ 'groupsAddStepText' | translate }}">
|
||||
<button mat-flat-button color="primary" (click)="addOU($event)" matTooltip="{{ 'newOrganizationalUnitTooltip' | translate }}" matTooltipShowDelay="1000">{{ 'newOrganizationalUnitButton' | translate }}</button>
|
||||
<button mat-flat-button color="primary" (click)="addClient($event)" matTooltipShowDelay="1000">{{ 'newClientButton' | translate }}</button>
|
||||
<button mat-raised-button (click)="openBottomSheet()" joyrideStep="keyStep" text="{{ 'keyStepText' | translate }}" matTooltipShowDelay="1000">{{ 'legendButton' | translate }}</button>
|
||||
|
|
|
@ -453,7 +453,7 @@ export class GroupsComponent implements OnInit {
|
|||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: ['titleStep', 'addStep', 'keyStep', 'unitStep', 'elementsStep', 'tabsStep'],
|
||||
steps: ['groupsTitleStepText', 'addStep', 'keyStep', 'unitStep', 'elementsStep', 'tabsStep'],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
|
|
|
@ -77,7 +77,15 @@
|
|||
{{ unit.description }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>{{ 'hardwareProfileError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label i18n="@@hardware-profile-label">Repositorio</mat-label>
|
||||
<mat-select formControlName="repository">
|
||||
<mat-option *ngFor="let repository of repositories" [value]="repository['@id']">
|
||||
{{ repository.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
</form>
|
||||
|
|
|
@ -16,6 +16,7 @@ export class EditClientComponent {
|
|||
clientForm!: FormGroup;
|
||||
parentUnits: any[] = [];
|
||||
hardwareProfiles: any[] = [];
|
||||
repositories: any[] = [];
|
||||
ogLives: any[] = [];
|
||||
templates: any[] = [];
|
||||
isEditMode: boolean;
|
||||
|
@ -48,6 +49,7 @@ export class EditClientComponent {
|
|||
this.loadHardwareProfiles();
|
||||
this.loadOgLives();
|
||||
this.loadPxeTemplates()
|
||||
this.loadRepositories();
|
||||
this.clientForm = this.fb.group({
|
||||
organizationalUnit: [null, Validators.required],
|
||||
name: ['', Validators.required],
|
||||
|
@ -59,6 +61,7 @@ export class EditClientComponent {
|
|||
template: null,
|
||||
hardwareProfile: null,
|
||||
ogLive: null,
|
||||
repository: null,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -99,6 +102,19 @@ export class EditClientComponent {
|
|||
);
|
||||
}
|
||||
|
||||
loadRepositories(): void {
|
||||
const url = `${this.baseUrl}/image-repositories?page=1&itemsPerPage=10000`;
|
||||
|
||||
this.http.get<any>(url).subscribe(
|
||||
response => {
|
||||
this.repositories = response['hydra:member'];
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching ogLives:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadPxeTemplates(): void {
|
||||
const url = `${this.baseUrl}/pxe-templates?page=1&itemsPerPage=10000`;
|
||||
|
||||
|
@ -127,6 +143,7 @@ export class EditClientComponent {
|
|||
serialNumber: data.serialNumber,
|
||||
hardwareProfile: data.hardwareProfile ? data.hardwareProfile['@id'] : null,
|
||||
organizationalUnit: data.organizationalUnit ? data.organizationalUnit['@id'] : null,
|
||||
repository: data.repository ? data.repository['@id'] : null,
|
||||
ogLive: data.ogLive ? data.ogLive['@id'] : null,
|
||||
template: data.template ? data.template['@id'] : null,
|
||||
});
|
||||
|
@ -150,7 +167,6 @@ export class EditClientComponent {
|
|||
|
||||
this.http.patch<any>(putUrl, formData, { headers }).subscribe(
|
||||
response => {
|
||||
console.log('PUT successful:', response);
|
||||
this.dialogRef.close();
|
||||
this.openSnackBar(false, 'Cliente actualizado exitosamente');
|
||||
|
||||
|
@ -167,7 +183,6 @@ export class EditClientComponent {
|
|||
|
||||
this.http.post<any>(postUrl, formData, { headers }).subscribe(
|
||||
response => {
|
||||
console.log('POST successful:', response);
|
||||
this.dialogRef.close();
|
||||
this.openSnackBar(false, 'Cliente creado exitosamente');
|
||||
},
|
||||
|
|
|
@ -15,6 +15,14 @@
|
|||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.partition-info-container {
|
||||
background-color: #f0f8ff; /* Un color de fondo suave */
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
margin-top: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Botones alineados al final, con margen superior */
|
||||
.dialog-actions {
|
||||
display: flex;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<h2 mat-dialog-title>{{ 'addImageTitle' | translate }}</h2>
|
||||
<h2 mat-dialog-title>{{ imageId ? 'Editar' : 'Crear' }} imagen</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<form [formGroup]="imageForm" (ngSubmit)="saveImage()" class="image-form">
|
||||
|
@ -8,9 +8,6 @@
|
|||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<<<<<<< Updated upstream
|
||||
<mat-label>Descripción</mat-label>
|
||||
=======
|
||||
<mat-label>{{ 'repositoryLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="imageRepository" required>
|
||||
<mat-option *ngFor="let imageRepository of repositories" [value]="imageRepository['@id']">
|
||||
|
@ -21,7 +18,6 @@
|
|||
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>{{ 'descriptionLabel' | translate }}</mat-label>
|
||||
>>>>>>> Stashed changes
|
||||
<input matInput formControlName="description" name="description">
|
||||
</mat-form-field>
|
||||
|
||||
|
@ -31,8 +27,8 @@
|
|||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>{{ 'softwareProfileLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="softwareProfile" required>
|
||||
<mat-label>Perfil de software</mat-label>
|
||||
<mat-select formControlName="softwareProfile">
|
||||
<mat-option *ngFor="let profile of softwareProfiles" [value]="profile['@id']">
|
||||
{{ profile.description }}
|
||||
</mat-option>
|
||||
|
@ -45,7 +41,19 @@
|
|||
>
|
||||
{{ 'remotePcLabel' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
<mat-divider *ngIf="imageId && partitionInfo"></mat-divider>
|
||||
|
||||
<div *ngIf="imageId && partitionInfo" class="partition-info-container">
|
||||
<h3>Información de Partición de origen</h3>
|
||||
<p>Sistema de archivos: {{ partitionInfo['filesystem'] }}</p>
|
||||
<p>Disco: {{ partitionInfo['numDisk'] }}</p>
|
||||
<p>Particion: {{ partitionInfo['numPartition'] }}</p>
|
||||
<p>Nombre del SO: {{ partitionInfo['osName'] }}</p>
|
||||
<p>Código de partición: {{ partitionInfo['partitionCode'] }}</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="end" class="dialog-actions">
|
||||
|
|
|
@ -15,6 +15,7 @@ export class CreateImageComponent implements OnInit {
|
|||
imageId: string | null = null;
|
||||
softwareProfiles: any[] = [];
|
||||
repositories: any[] = [];
|
||||
partitionInfo: { [key: string]: string } = {};
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
|
@ -29,7 +30,7 @@ export class CreateImageComponent implements OnInit {
|
|||
description: [''],
|
||||
comments: [''],
|
||||
remotePc: [false],
|
||||
softwareProfile: ['', Validators.required],
|
||||
softwareProfile: [''],
|
||||
imageRepository: ['', Validators.required],
|
||||
});
|
||||
}
|
||||
|
@ -50,10 +51,11 @@ export class CreateImageComponent implements OnInit {
|
|||
description: [response.description],
|
||||
comments: [response.comments],
|
||||
remotePc: [response.remotePc],
|
||||
softwareProfile: [response.softwareProfile['@id'], Validators.required],
|
||||
imageRepository: [response.repository['@id'], Validators.required],
|
||||
softwareProfile: [response.softwareProfile ? response.softwareProfile['@id'] : null, Validators.required],
|
||||
imageRepository: [response.imageRepository ? response.imageRepository['@id'] : null, Validators.required],
|
||||
});
|
||||
this.imageId = response['@id'];
|
||||
this.partitionInfo = response.partitionInfo;
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error fetching remote calendar:', err);
|
||||
|
@ -93,13 +95,13 @@ export class CreateImageComponent implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
const payload: any = {
|
||||
name: this.imageForm.value.name,
|
||||
description: this.imageForm.value.description,
|
||||
comments: this.imageForm.value.comments,
|
||||
remotePc: this.imageForm.value.remotePc,
|
||||
softwareProfile: this.imageForm.value.softwareProfile,
|
||||
imageRepository: this.imageForm.value.imageRepository,
|
||||
...(this.imageForm.value.softwareProfile ? { softwareProfile: this.imageForm.value.softwareProfile } : {}),
|
||||
};
|
||||
|
||||
if (this.imageId) {
|
||||
|
|
|
@ -1,44 +1,15 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.images-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.lists-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.imagesLists-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card.unidad-card {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid rgba(122, 122, 122, 0.555);
|
||||
}
|
||||
|
||||
.image-container h4 {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.image-name{
|
||||
cursor: pointer;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
|
@ -91,12 +62,28 @@ table {
|
|||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.example-button-row {
|
||||
.button-row {
|
||||
display: table-cell;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.example-button-row .mat-mdc-button-base {
|
||||
.button-row .mat-mdc-button-base {
|
||||
margin: 8px 8px 8px 0;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
background-color: #4caf50; /* Verde */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-failed {
|
||||
background-color: #f44336; /* Rojo */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #ff9800; /* Naranja */
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
|
||||
<div class="header-container">
|
||||
<<<<<<< Updated upstream
|
||||
<h2 class="title">Administrar imágenes</h2>
|
||||
<div class="images-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addImage()">Añadir imagen</button>
|
||||
</div>
|
||||
=======
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
|
@ -13,12 +8,10 @@
|
|||
<button mat-flat-button color="primary" (click)="addImage()">
|
||||
{{ 'addImageButton' | translate }}
|
||||
</button>
|
||||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchImageField" text="Busca una imagen por nombre. Pulsa 'enter' para iniciar la búsqueda.">
|
||||
<mat-label>Buscar nombre de imagen</mat-label>
|
||||
|
@ -28,7 +21,6 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
<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>
|
||||
|
@ -43,6 +35,20 @@
|
|||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="imagesTable" text="Esta tabla muestra las imágenes disponibles.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef === 'remotePc'">
|
||||
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef !== 'remotePc'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;" joyrideStep="actionsHeader" text="Acciones disponibles para cada imagen.">Acciones</th>
|
||||
|
@ -67,7 +73,6 @@
|
|||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
=======
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchLabel' | translate }}</mat-label>
|
||||
|
@ -115,4 +120,3 @@
|
|||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
>>>>>>> Stashed changes
|
||||
|
|
|
@ -7,6 +7,9 @@ import { DatePipe } from '@angular/common';
|
|||
import { CreateImageComponent } from './create-image/create-image.component';
|
||||
import {CreateCommandComponent} from "../commands/main-commands/create-command/create-command.component";
|
||||
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import {ServerInfoDialogComponent} from "../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component";
|
||||
import {Observable} from "rxjs";
|
||||
import {InfoImageComponent} from "../ogboot/pxe-images/info-image/info-image/info-image.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
|
||||
@Component({
|
||||
|
@ -22,6 +25,7 @@ export class ImagesComponent implements OnInit {
|
|||
page: number = 0;
|
||||
loading: boolean = false;
|
||||
filters: { [key: string]: string } = {};
|
||||
alertMessage: string | null = null;
|
||||
datePipe: DatePipe = new DatePipe('es-ES');
|
||||
columns = [
|
||||
{
|
||||
|
@ -34,11 +38,6 @@ export class ImagesComponent implements OnInit {
|
|||
header: 'Nombre de imagen',
|
||||
cell: (image: any) => `${image.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'softwareProfile',
|
||||
header: 'Perfil de software',
|
||||
cell: (image: any) => `${image.softwareProfile?.description}`
|
||||
},
|
||||
{
|
||||
columnDef: 'imageRepository',
|
||||
header: 'Repositorio',
|
||||
|
@ -49,6 +48,16 @@ export class ImagesComponent implements OnInit {
|
|||
header: 'Remote Pc',
|
||||
cell: (image: any) => `${image.remotePc}`
|
||||
},
|
||||
{
|
||||
columnDef: 'status',
|
||||
header: 'Estado',
|
||||
cell: (image: any) => `${image.status}`
|
||||
},
|
||||
{
|
||||
columnDef: 'imageFullsum',
|
||||
header: 'Fullsum',
|
||||
cell: (image: any) => `${image.imageFullsum}`
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
header: 'Fecha de creación',
|
||||
|
@ -72,7 +81,7 @@ export class ImagesComponent implements OnInit {
|
|||
|
||||
addImage(): void {
|
||||
const dialogRef = this.dialog.open(CreateImageComponent, {
|
||||
width: '600px'
|
||||
width: '800px'
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
|
@ -98,7 +107,7 @@ export class ImagesComponent implements OnInit {
|
|||
editImage(event: MouseEvent, image: any): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(CreateImageComponent, {
|
||||
width: '600px',
|
||||
width: '800px',
|
||||
data: image['@id']
|
||||
}).afterClosed().subscribe(() => this.search());
|
||||
}
|
||||
|
@ -130,6 +139,47 @@ export class ImagesComponent implements OnInit {
|
|||
this.search();
|
||||
}
|
||||
|
||||
loadImageAlert(image: any): Observable<any> {
|
||||
return this.http.get<any>(`${this.apiUrl}/server/${image.uuid}/get`, {});
|
||||
}
|
||||
|
||||
showImageInfo(event: MouseEvent, image:any) {
|
||||
event.stopPropagation();
|
||||
this.loadImageAlert(image).subscribe(
|
||||
response => {
|
||||
this.alertMessage = response.output;
|
||||
|
||||
this.dialog.open(ServerInfoDialogComponent, {
|
||||
width: '600px',
|
||||
data: {
|
||||
message: this.alertMessage
|
||||
}
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
toggleAction(image: any, action:string): void {
|
||||
switch (action) {
|
||||
case 'get-aux':
|
||||
this.http.post(`${this.baseUrl}/images/server/${image.uuid}/create-aux-files`, {}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Petición de creación de archivos auxiliares enviada');
|
||||
this.search()
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.error('Acción no soportada:', action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
|
@ -146,5 +196,4 @@ export class ImagesComponent implements OnInit {
|
|||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -241,6 +241,7 @@ export class PXEimagesComponent implements OnInit {
|
|||
});
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
console.error('Error al cargar la información del alert', error);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
<<<<<<< Updated upstream
|
||||
<h2 mat-dialog-title>{{ isEditMode ? 'Editar' : 'Añadir' }} plantilla </h2>
|
||||
=======
|
||||
<h2 mat-dialog-title>{{ isEditMode ? ('editTemplateTitle' | translate) : ('addTemplateTitle' | translate) }}</h2>
|
||||
>>>>>>> Stashed changes
|
||||
|
||||
<mat-dialog-content>
|
||||
<div class="spacing-container">
|
||||
|
@ -26,13 +22,6 @@
|
|||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button type="button" (click)="onCancel()">Cancelar</button>
|
||||
<button mat-raised-button color="primary" type="submit" (click)="onSave()" [disabled]="!templateForm.valid">
|
||||
{{ isEditMode ? 'Actualizar' : 'Crear' }}
|
||||
</button>
|
||||
=======
|
||||
<mat-dialog-actions>
|
||||
<div class="actions-container">
|
||||
<button mat-flat-button color="accent" [matMenuTriggerFor]="templateMenu">
|
||||
|
@ -50,5 +39,4 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
>>>>>>> Stashed changes
|
||||
</mat-dialog-actions>
|
||||
|
|
|
@ -220,6 +220,7 @@ export class PxeComponent {
|
|||
});
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
console.error('Error al cargar la información del alert', error);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -198,6 +198,7 @@ export class OgDhcpSubnetsComponent {
|
|||
});
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
console.error('Error al cargar la información del alert', error);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<h2 mat-dialog-title>Información de Subred</h2>
|
||||
<h2 mat-dialog-title>Información</h2>
|
||||
<mat-dialog-content>
|
||||
<pre>{{ data | json }}</pre>
|
||||
</mat-dialog-content>
|
||||
|
|
|
@ -0,0 +1,307 @@
|
|||
|
||||
.client-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.client-icon {
|
||||
flex-shrink: 0;
|
||||
margin-right: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 120px;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.row-container {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.charts-wrapper {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.charts-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between; /* Distribuye el espacio entre los gráficos */
|
||||
gap: 20px; /* Añade espacio entre los gráficos */
|
||||
}
|
||||
|
||||
.disk-usage {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
min-width: 200px; /* Ajusta este valor según el tamaño mínimo deseado para cada gráfico */
|
||||
}
|
||||
|
||||
.circular-chart {
|
||||
max-width: 150px;
|
||||
max-height: 150px;
|
||||
margin: 0 auto; /* Centra el gráfico dentro del contenedor */
|
||||
}
|
||||
|
||||
.chart {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.icon-pc {
|
||||
font-size: 25px;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.client-title h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.client-title p {
|
||||
margin: 2px 0;
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.client-info {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.repository-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.save-button{
|
||||
align-self: flex-start;
|
||||
width: 100px !important;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
margin-bottom: 30px;
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.two-column-table {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.column.property {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.column.value {
|
||||
text-align: right;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.mat-tab-group {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.mat-tab-body-wrapper {
|
||||
min-height: inherit;
|
||||
}
|
||||
|
||||
.info-section h2 {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 10px;
|
||||
color: #0056b3;
|
||||
}
|
||||
|
||||
.second-section {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.buttons-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.buttons-row button {
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.disk-usage-info{
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-left: 15px;
|
||||
}.dashboard {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.disk-usage-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-button-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.disk-usage {
|
||||
flex: 2;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.services-status {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.services-status ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.services-status li {
|
||||
margin: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-led {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.status-led.active {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.status-led.inactive {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px; /* Espacio entre botones */
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.btn:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.images-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-string {
|
||||
flex: 2;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.search-boolean {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
<mat-tab-group dynamicHeight>
|
||||
<mat-tab label="Estado servidor">
|
||||
<div class="dashboard">
|
||||
<h2>OgRepository server Status</h2>
|
||||
<div class="disk-usage-container">
|
||||
<div class="disk-usage">
|
||||
<h3>Uso de disco</h3>
|
||||
<ngx-charts-pie-chart
|
||||
[view]="view"
|
||||
[scheme]="colorScheme"
|
||||
[results]="diskUsageChartData"
|
||||
[gradient]="gradient"
|
||||
[doughnut]="isDoughnut"
|
||||
[labels]="showLabels"
|
||||
[legend]="showLegend">
|
||||
</ngx-charts-pie-chart>
|
||||
<div class="disk-usage-info">
|
||||
<p>Total: {{ diskUsage.total }}</p>
|
||||
<p>Ocupado: {{ diskUsage.used }}</p>
|
||||
<p>Disponible: {{ diskUsage.available }}</p>
|
||||
<p>Ocupado ( % ): {{ diskUsage.percentage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="services-status">
|
||||
<h3>Servicios</h3>
|
||||
<ul>
|
||||
<li *ngFor="let service of getServices()">
|
||||
<span
|
||||
class="status-led"
|
||||
[ngClass]="{
|
||||
'active': service.status === 'active',
|
||||
'inactive': service.status === 'stopped' || service.status === 'status not accesible'
|
||||
}"
|
||||
></span>
|
||||
{{ service.name }}:
|
||||
<span [ngSwitch]="service.status">
|
||||
<span *ngSwitchCase="'active'">Activo</span>
|
||||
<span *ngSwitchCase="'stopped'">Detenido</span>
|
||||
<span *ngSwitchCase="'status not accesible'">No accesible</span>
|
||||
<span *ngSwitchDefault>{{ service.status }}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Datos generales">
|
||||
<div class="dashboard">
|
||||
<div class="header-button-container">
|
||||
<button mat-flat-button color="primary" (click)="syncRepository()">Sincronizar base de datos</button>
|
||||
<button mat-flat-button color="accent" (click)="openImageInfoDialog()">Ver Información</button>
|
||||
</div>
|
||||
|
||||
<h2>Editar datos repositorio</h2>
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!loading" class="client-info form-container">
|
||||
<form [formGroup]="repositoryForm" (ngSubmit)="save()" class="repository-form">
|
||||
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>Nombre del repositorio</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>Ip</mat-label>
|
||||
<input matInput formControlName="ip" name="description">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>Comentarios</mat-label>
|
||||
<input matInput formControlName="comments" name="comments">
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-flat-button color="primary" class="save-button"(click)="save()">Guardar</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Listado de imágenes">
|
||||
<div class="dashboard">
|
||||
<h2>Imágenes</h2>
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>Buscar nombre de imagen</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="searchImages()" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image" >
|
||||
<ng-container *ngIf="column.columnDef === 'remotePc' || column.columnDef === 'created'">
|
||||
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef !== 'remotePc' && column.columnDef !== 'created'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let image" style="text-align: center;">
|
||||
<button mat-icon-button color="info" (click)="showImageInfo($event, image)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editImage($event, image)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="toggleAction(image, 'delete')">
|
||||
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum" (click)="toggleAction(image, 'get-aux')">Obtener ficheros auxiliares</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="[5, 10, 20, 40, 100]"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MainRepositoryViewComponent } from './main-repository-view.component';
|
||||
|
||||
describe('MainRepositoryViewComponent', () => {
|
||||
let component: MainRepositoryViewComponent;
|
||||
let fixture: ComponentFixture<MainRepositoryViewComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [MainRepositoryViewComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MainRepositoryViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,323 @@
|
|||
import {Component, Inject} from '@angular/core';
|
||||
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {DataService} from "../../images/data.service";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {MatTableDataSource} from "@angular/material/table";
|
||||
import {DatePipe} from "@angular/common";
|
||||
import {ServerInfoDialogComponent} from "../../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component";
|
||||
import {CreateImageComponent} from "../../images/create-image/create-image.component";
|
||||
import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import {Observable} from "rxjs";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
|
||||
@Component({
|
||||
selector: 'app-main-repository-view',
|
||||
templateUrl: './main-repository-view.component.html',
|
||||
styleUrl: './main-repository-view.component.css'
|
||||
})
|
||||
export class MainRepositoryViewComponent {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
repositoryForm: FormGroup<any>;
|
||||
repositoryId: string | null = null;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
loading: boolean = true;
|
||||
diskUsage: any = {};
|
||||
servicesStatus: any = {};
|
||||
diskUsageChartData: any[] = [];
|
||||
alertMessage: string | null = null;
|
||||
length: number = 0;
|
||||
itemsPerPage: number = 10;
|
||||
page: number = 0;
|
||||
view: [number, number] = [800, 500];
|
||||
gradient: boolean = true;
|
||||
showLegend: boolean = true;
|
||||
showLabels: boolean = true;
|
||||
isDoughnut: boolean = true;
|
||||
repositoryData: any = {};
|
||||
colorScheme: any = {
|
||||
domain: ['#FF6384', '#3f51b5']
|
||||
};
|
||||
|
||||
filters: { [key: string]: string } = {};
|
||||
datePipe: DatePipe = new DatePipe('es-ES');
|
||||
columns = [
|
||||
{
|
||||
columnDef: 'id',
|
||||
header: 'Id',
|
||||
cell: (image: any) => `${image.id}`
|
||||
},
|
||||
{
|
||||
columnDef: 'name',
|
||||
header: 'Nombre de imagen',
|
||||
cell: (image: any) => `${image.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'imageRepository',
|
||||
header: 'Repositorio',
|
||||
cell: (image: any) => `${image.imageRepository?.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'remotePc',
|
||||
header: 'Remote Pc',
|
||||
cell: (image: any) => `${image.remotePc}`
|
||||
},
|
||||
{
|
||||
columnDef: 'created',
|
||||
header: 'Creado en repositorio',
|
||||
cell: (image: any) => `${image.created}`
|
||||
},
|
||||
{
|
||||
columnDef: 'imageFullsum',
|
||||
header: 'Fullsum',
|
||||
cell: (image: any) => `${image.imageFullsum}`
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
header: 'Fecha de creación',
|
||||
cell: (image: any) => `${this.datePipe.transform(image.createdAt, 'dd/MM/yyyy hh:mm:ss')}`
|
||||
}
|
||||
];
|
||||
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
|
||||
private apiUrl = `${this.baseUrl}/images`;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
private dataService: DataService,
|
||||
private route: ActivatedRoute,
|
||||
public dialog: MatDialog,
|
||||
) {
|
||||
this.repositoryForm = this.fb.group({
|
||||
name: ['', Validators.required],
|
||||
ip: [''],
|
||||
comments: [''],
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.repositoryId = this.route.snapshot.paramMap.get('id');
|
||||
this.loading = true
|
||||
this.load()
|
||||
this.loadStatus()
|
||||
}
|
||||
|
||||
load(): void {
|
||||
const url = `${this.baseUrl}/image-repositories/${this.repositoryId}`;
|
||||
this.http.get(url).subscribe(
|
||||
(response: any) => {
|
||||
this.repositoryData = response;
|
||||
this.repositoryForm = this.fb.group({
|
||||
name: [response.name, Validators.required],
|
||||
ip: [response.ip],
|
||||
comments: [response.comments],
|
||||
});
|
||||
this.loading = false;
|
||||
// Llamar searchImages() solo cuando la data de repository esté cargada
|
||||
this.searchImages();
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar los datos del cliente:', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const payload = {
|
||||
name: this.repositoryForm.value.name,
|
||||
ip: this.repositoryForm.value.ip,
|
||||
comments: this.repositoryForm.value.comments,
|
||||
};
|
||||
|
||||
if (this.repositoryId) {
|
||||
this.http.put(`${this.baseUrl}/image-repositories/${this.repositoryId}`, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Imagen editada correctamente');
|
||||
},
|
||||
(error) => {
|
||||
this.toastService.error(error['error']['hydra:description']);
|
||||
console.error('Error al editar la imagen', error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.http.post(`${this.baseUrl}/image-repositories`, payload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Imagen añadida correctamente');
|
||||
},
|
||||
(error) => {
|
||||
this.toastService.error(error['error']['hydra:description']);
|
||||
console.error('Error al añadir la imagen', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
loadStatus(): void {
|
||||
this.http.get<any>(`${this.baseUrl}/image-repositories/server/${this.repositoryId}/status`).subscribe(data => {
|
||||
const diskData = data.output.disk;
|
||||
const servicesData = data.output.services;
|
||||
|
||||
this.diskUsage = {
|
||||
total: diskData.total,
|
||||
used: diskData.used,
|
||||
available: diskData.available,
|
||||
percentage: diskData.used_percentage
|
||||
};
|
||||
|
||||
this.diskUsageChartData = [
|
||||
{
|
||||
name: 'Usado',
|
||||
value: parseFloat(diskData.used)
|
||||
},
|
||||
{
|
||||
name: 'Disponible',
|
||||
value: parseFloat(diskData.available)
|
||||
}
|
||||
];
|
||||
|
||||
this.servicesStatus = servicesData;
|
||||
|
||||
}, error => {
|
||||
console.error('Error fetching status', error);
|
||||
});
|
||||
}
|
||||
|
||||
getServices(): { name: string, status: string }[] {
|
||||
return Object.keys(this.servicesStatus).map(key => ({
|
||||
name: key,
|
||||
status: this.servicesStatus[key]
|
||||
}));
|
||||
}
|
||||
|
||||
searchImages(): void {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.apiUrl}?repository.id=${this.repositoryData.id}&page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
|
||||
data => {
|
||||
this.dataSource.data = data['hydra:member'];
|
||||
this.length = data['hydra:totalItems'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching images', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
editImage(event: MouseEvent, image: any): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(CreateImageComponent, {
|
||||
width: '800px',
|
||||
data: image['@id']
|
||||
}).afterClosed().subscribe(() => this.searchImages());
|
||||
}
|
||||
|
||||
deleteImage(image: any): void {
|
||||
this.dialog.open(DeleteModalComponent, {
|
||||
width: '300px',
|
||||
data: { name: image.name },
|
||||
}).afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
this.http.delete(`${this.apiUrl}/server/${image.uuid}/delete`).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Imagen eliminada con éxito');
|
||||
this.searchImages();
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
console.error('Error al eliminar la imagen:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadImageAlert(image: any): Observable<any> {
|
||||
return this.http.get<any>(`${this.apiUrl}/server/${image.uuid}/get`, {});
|
||||
}
|
||||
|
||||
showImageInfo(event: MouseEvent, image:any) {
|
||||
event.stopPropagation();
|
||||
this.loadImageAlert(image).subscribe(
|
||||
response => {
|
||||
this.alertMessage = response.output;
|
||||
|
||||
this.dialog.open(ServerInfoDialogComponent, {
|
||||
width: '600px',
|
||||
data: {
|
||||
message: this.alertMessage
|
||||
}
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onPageChange(event: any): void {
|
||||
this.page = event.pageIndex;
|
||||
this.itemsPerPage = event.pageSize;
|
||||
this.length = event.length;
|
||||
this.searchImages();
|
||||
}
|
||||
|
||||
loadAlert(): Observable<any> {
|
||||
return this.http.get<any>(`${this.baseUrl}/image-repositories/server/get-collection`);
|
||||
}
|
||||
|
||||
syncRepository() {
|
||||
this.http.post(`${this.apiUrl}/sync`, {})
|
||||
.subscribe(response => {
|
||||
this.toastService.success('Sincronización completada');
|
||||
this.load()
|
||||
}, error => {
|
||||
console.error('Error al sincronizar', error);
|
||||
this.toastService.error('Error al sincronizar');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
toggleAction(image: any, action:string): void {
|
||||
switch (action) {
|
||||
case 'get-aux':
|
||||
this.http.post(`${this.baseUrl}/images/server/${image.uuid}/create-aux-files`, {}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Petición de creación de archivos auxiliares enviada');
|
||||
this.searchImages()
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'delete':
|
||||
this.deleteImage(image);
|
||||
break;
|
||||
default:
|
||||
console.error('Acción no soportada:', action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
openImageInfoDialog() {
|
||||
this.loadAlert().subscribe(
|
||||
response => {
|
||||
this.alertMessage = response.output;
|
||||
|
||||
this.dialog.open(ServerInfoDialogComponent, {
|
||||
width: '800px',
|
||||
data: {
|
||||
message: this.alertMessage
|
||||
}
|
||||
});
|
||||
},
|
||||
error => {
|
||||
console.error('Error al cargar la información del alert', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,10 +4,10 @@ import {DatePipe} from "@angular/common";
|
|||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {CreateImageComponent} from "../images/create-image/create-image.component";
|
||||
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import { CreateRepositoryComponent } from './create-repository/create-repository.component';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
import {CreateRepositoryComponent} from "./create-repository/create-repository.component";
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-repositories',
|
||||
|
@ -53,7 +53,8 @@ export class RepositoriesComponent {
|
|||
public dialog: MatDialog,
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService
|
||||
private joyrideService: JoyrideService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -87,10 +88,7 @@ export class RepositoriesComponent {
|
|||
|
||||
editRepository(event: MouseEvent, repository: any): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(CreateRepositoryComponent, {
|
||||
width: '600px',
|
||||
data: repository['@id']
|
||||
}).afterClosed().subscribe(() => this.search());
|
||||
this.router.navigate(['repository', repository.uuid]);
|
||||
}
|
||||
|
||||
deleteRepository(event: MouseEvent,command: any): void {
|
||||
|
|
|
@ -10,6 +10,10 @@ mat-toolbar {
|
|||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.trace-button .mat-icon {
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
.navbar-button-row {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
|
|
|
@ -2,22 +2,24 @@
|
|||
<span class="navbar-title" i18n="@@webConsoleTitle" matTooltip="Consola web de administración de Opengnsys" matTooltipShowDelay="1000">
|
||||
Opengnsys webconsole
|
||||
</span>
|
||||
|
||||
|
||||
<button mat-icon-button (click)="onToggleSidebar()" matTooltip="Abrir o cerrar la barra lateral" matTooltipShowDelay="1000">
|
||||
<mat-icon class="navbar-icon">menu</mat-icon>
|
||||
</button>
|
||||
|
||||
|
||||
<div class="navbar-button-row">
|
||||
<button class="admin-button" *ngIf="isSuperAdmin" mat-button [matMenuTriggerFor]="menu" i18n="@@admin"
|
||||
<button class="trace-button" routerLink="/commands-logs" mat-button i18n="@@admin"><mat-icon>notifications</mat-icon></button>
|
||||
|
||||
<button class="admin-button" *ngIf="isSuperAdmin" mat-button [matMenuTriggerFor]="menu" i18n="@@admin"
|
||||
matTooltip="Gestión de usuarios y roles de la aplicación" matTooltipShowDelay="1000">
|
||||
Administración
|
||||
</button>
|
||||
|
||||
<button class="user-button" mat-button *ngIf="!isSuperAdmin" (click)="editUser()" i18n="@@editUser"
|
||||
|
||||
<button class="user-button" mat-button *ngIf="!isSuperAdmin" (click)="editUser()" i18n="@@editUser"
|
||||
matTooltip="Editar tu información de usuario" matTooltipShowDelay="1000">
|
||||
Editar usuario
|
||||
</button>
|
||||
|
||||
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item routerLink="/users" i18n="@@usersMenuItem" matTooltip="Ver y gestionar todos los usuarios" matTooltipShowDelay="1000">
|
||||
Usuarios
|
||||
|
@ -25,9 +27,11 @@
|
|||
<button mat-menu-item routerLink="/user-groups" i18n="@@rolesMenuItem" matTooltip="Gestionar roles de usuario" matTooltipShowDelay="1000">
|
||||
Roles
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/env-vars" i18n="@@rolesMenuItem" matTooltip="Gestionar variables de entorno" matTooltipShowDelay="1000">
|
||||
Variables de entorno
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
<button mat-flat-button color="warn" routerLink="/auth/login" i18n="@@logout"
|
||||
<button mat-flat-button color="warn" routerLink="/auth/login" i18n="@@logout"
|
||||
matTooltip="Cerrar sesión y salir de la aplicación" matTooltipShowDelay="1000">
|
||||
Salir
|
||||
</button>
|
||||
|
|
|
@ -41,12 +41,6 @@
|
|||
<span i18n="@@gallery">Tareas</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/commands-logs" matTooltip="Revisar trazas de ejecución de comandos" matTooltipShowDelay="1000">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">notifications</mat-icon>
|
||||
<span i18n="@@gallery">Trazas</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
|
||||
<mat-list-item (click)="toggleOgDhcpSub()" matTooltip="Configurar y administrar DHCP" matTooltipShowDelay="1000">
|
||||
|
|
|
@ -29,7 +29,6 @@ export class SidebarComponent {
|
|||
this.showSoftwareSub = !this.showSoftwareSub;
|
||||
}
|
||||
|
||||
|
||||
constructor(public dialog: MatDialog) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
"labelOrganizationalUnit": "Organizational Unit",
|
||||
"buttonCancel": "Cancel",
|
||||
"buttonAdd": "Add",
|
||||
"addButton": "Add",
|
||||
"addClientDialogTitle": "Add Client",
|
||||
"dialogTitleEditUser": "Edit User",
|
||||
"labelCurrentPassword": "Current password",
|
||||
"labelNewPassword": "New password",
|
||||
|
@ -36,13 +38,15 @@
|
|||
"checkboxOrgOperator": "Organizational Unit Operator",
|
||||
"checkboxOrgMinimal": "Minimal Organizational Unit",
|
||||
"checkboxUserRole": "User",
|
||||
"titleStepText": "On this screen, you can manage the calendars of remote teams connected to the UDS service.",
|
||||
"groupsTitleStepText": "On this screen, you can manage the main organizational units (Faculties, Classrooms, Classroom Groups, and clients).",
|
||||
"titleStepText": "On this screen, you can manage the calendars of remote teams connected to the UDS service",
|
||||
"groupsAddStepText": "Click to add a new organizational unit or client.",
|
||||
"adminCalendarsTitle": "Manage calendars",
|
||||
"addButtonStepText": "Click here to add a new calendar.",
|
||||
"addCalendar": "Add calendar",
|
||||
"searchStepText": "Use this search bar to filter existing calendars.",
|
||||
"searchCalendarLabel": "Search calendar name",
|
||||
"tableStepText": "Here you can see the existing calendars with their features and configurations.",
|
||||
"tableStepText": "Here are the existing calendars with their characteristics and settings.",
|
||||
"actionsStepText": "Access the available actions for each calendar here.",
|
||||
"editCalendar": "Edit calendar",
|
||||
"remoteAvailability": "Remote availability?",
|
||||
|
@ -52,7 +56,7 @@
|
|||
"endTime": "End time",
|
||||
"endTimePlaceholder": "Select end time",
|
||||
"reasonLabel": "Reason",
|
||||
"reasonPlaceholder": "Reason for exception",
|
||||
"reasonPlaceholder": "Reason for the exception",
|
||||
"startDate": "Start date",
|
||||
"endDate": "End date",
|
||||
"buttonSave": "Save",
|
||||
|
@ -60,7 +64,7 @@
|
|||
"addCommandGroupStepText": "Click to add a new command group.",
|
||||
"addCommandGroup": "Add Command Group",
|
||||
"searchGroupNameLabel": "Search group name",
|
||||
"loadingStepText": "Wait while command groups are loading.",
|
||||
"loadingStepText": "Wait while the command groups are loading.",
|
||||
"viewCommands": "View commands",
|
||||
"paginationStepText": "Navigate between command group pages using the paginator.",
|
||||
"commandGroupDetailsTitle": "Command Group Details",
|
||||
|
@ -91,12 +95,12 @@
|
|||
"statusColumn": "Status",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"adminCommandsTitle": "Command and Procedure Traces",
|
||||
"resetFiltersStepText": "Click to reset filters and see all traces.",
|
||||
"adminCommandsTitle": "Command and procedure traces",
|
||||
"resetFiltersStepText": "Click to reset the applied filters and see all traces.",
|
||||
"resetFilters": "Reset filters",
|
||||
"clientSelectStepText": "Select a client to view associated traces.",
|
||||
"clientSelectStepText": "Select a client to see the associated traces.",
|
||||
"selectClientPlaceholder": "Select a client",
|
||||
"commandSelectStepText": "Select a command to view specific traces for that command.",
|
||||
"commandSelectStepText": "Select a command to see the specific traces of that command.",
|
||||
"selectCommandPlaceholder": "Select a command",
|
||||
"taskDetailsTitle": "Task Details",
|
||||
"taskId": "Task ID",
|
||||
|
@ -112,15 +116,15 @@
|
|||
"informationSectionTitle": "Information",
|
||||
"informationLabel": "Information",
|
||||
"notesPlaceholder": "Enter your notes here",
|
||||
"commandSelectionSectionTitle": "Command Selection",
|
||||
"commandSelectionSectionTitle": "Command selection",
|
||||
"selectCommandsLabel": "Select Commands",
|
||||
"requiredFieldError": "This field is required",
|
||||
"executionDateTimeSectionTitle": "Execution Date and Time",
|
||||
"executionDateTimeSectionTitle": "Execution date and time",
|
||||
"executionDateLabel": "Execution Date",
|
||||
"selectDatePlaceholder": "Select a date",
|
||||
"executionTimeLabel": "Execution Time",
|
||||
"selectTimePlaceholder": "Select a time",
|
||||
"destinationSelectionSectionTitle": "Select Destination",
|
||||
"destinationSelectionSectionTitle": "Select destination",
|
||||
"selectOrganizationalUnitLabel": "Select Organizational Unit",
|
||||
"selectClassroomLabel": "Select Classroom",
|
||||
"selectAllClients": "Select all",
|
||||
|
@ -144,17 +148,18 @@
|
|||
"commandScriptPlaceholder": "Command script",
|
||||
"readOnlyLabel": "Read only",
|
||||
"enabledLabel": "Enabled",
|
||||
"cancelButton": "Cancel",
|
||||
"generalTabLabel": "General",
|
||||
"tabsStepText": "Use the tabs to access different options for viewing and searching organizational units and clients.",
|
||||
"adminGroupsTitle": "Manage Groups",
|
||||
"newOrganizationalUnitTooltip": "Open modal to create organizational units of any type (Center, Classroom, Group of classrooms, or Group of clients)",
|
||||
"tabsStepText": "Use the tabs to access different viewing and search options for organizational units and clients.",
|
||||
"adminGroupsTitle": "Manage groups",
|
||||
"newOrganizationalUnitTooltip": "Open modal to create organizational units of any type (Center, Classroom, Classroom Group, or Client Group)",
|
||||
"newOrganizationalUnitButton": "New Organizational Unit",
|
||||
"newClientButton": "New Client",
|
||||
"keyStepText": "The legend will show you the types of organizational units and their corresponding icons.",
|
||||
"keyStepText": "The legend will show you the types of organizational units and their corresponding icons",
|
||||
"legendButton": "Legend",
|
||||
"unitStepText": "This section shows the organizational units of type 'Center'.",
|
||||
"unitStepText": "This is the section where 'Center' type organizational units will be displayed",
|
||||
"organizationalUnitTitle": "Centers",
|
||||
"elementsStepText": "This section allows you to view internal units of the selected center and navigate through them.",
|
||||
"elementsStepText": "This is the section to view internal units of the selected center and navigate through them.",
|
||||
"internalElementsTitle": "Internal elements",
|
||||
"noInternalElementsMessage": "No internal elements",
|
||||
"viewTreeTooltip": "View unit as a tree",
|
||||
|
@ -167,17 +172,17 @@
|
|||
"deleteElementTooltip": "Delete this element",
|
||||
"deleteElementMenu": "Delete element",
|
||||
"executeCommandTooltip": "Execute command on this element",
|
||||
"advancedSearchTabLabel": "Advanced Search",
|
||||
"advancedSearchTabLabel": "Advanced search",
|
||||
"clientsTabLabel": "Clients",
|
||||
"organizationalUnitsTabLabel": "Organizational Units",
|
||||
"viewTreeTitle": "View Organizational Unit Tree",
|
||||
"organizationalUnitsTabLabel": "Organizational units",
|
||||
"viewTreeTitle": "View organizational unit tree",
|
||||
"toggleNodeAriaLabel": "Toggle node",
|
||||
"closeButton": "Close",
|
||||
"orgUnitPropertiesTitle": "Organizational Unit Properties",
|
||||
"generalDataTab": "General Data",
|
||||
"orgUnitPropertiesTitle": "Organizational unit properties",
|
||||
"generalDataTab": "General data",
|
||||
"propertyHeader": "Property",
|
||||
"valueHeader": "Value",
|
||||
"classroomNetworkPropertiesTab": "Classroom and Network Properties",
|
||||
"classroomNetworkPropertiesTab": "Classroom and network properties",
|
||||
"editOrgUnitTitle": "Edit Organizational Unit",
|
||||
"generalStepLabel": "General",
|
||||
"typeLabel": "Type",
|
||||
|
@ -192,12 +197,12 @@
|
|||
"associatedCalendarLabel": "Associated Calendar",
|
||||
"backButton": "Back",
|
||||
"additionalInfoStepLabel": "Additional Information",
|
||||
"networkSettingsStepLabel": "Network Configuration",
|
||||
"proxyUrlLabel": "Proxy Server URL",
|
||||
"dnsIpLabel": "DNS Server IP",
|
||||
"netmaskLabel": "Network Mask",
|
||||
"networkSettingsStepLabel": "Network Settings",
|
||||
"proxyUrlLabel": "Proxy server URL",
|
||||
"dnsIpLabel": "DNS server IP",
|
||||
"netmaskLabel": "Netmask",
|
||||
"routerLabel": "Router",
|
||||
"ntpIpLabel": "NTP Server IP",
|
||||
"ntpIpLabel": "NTP server IP",
|
||||
"p2pModeLabel": "P2P Mode",
|
||||
"p2pTimeLabel": "P2P Time",
|
||||
"mcastIpLabel": "Multicast IP",
|
||||
|
@ -210,14 +215,14 @@
|
|||
"validationToggle": "Validation",
|
||||
"submitButton": "Add",
|
||||
"addOrgUnitTitle": "Add Organizational Unit",
|
||||
"createOrgUnitparentLabel": "Parent Organizational Unit",
|
||||
"createOrgUnitparentLabel": "Parent organizational unit",
|
||||
"noParentOption": "--",
|
||||
"nextServerLabel": "NextServer",
|
||||
"bootFileNameLabel": "bootFileName",
|
||||
"orgUnitTitle": "Organizational Unit",
|
||||
"classroomGroupsTitle": "Classroom Groups",
|
||||
"orgUnitTitle": "Organizational unit",
|
||||
"classroomGroupsTitle": "Classroom groups",
|
||||
"classroomTitle": "Classroom",
|
||||
"clientGroupsTitle": "Client Groups",
|
||||
"clientGroupsTitle": "Client groups",
|
||||
"clientTitle": "Client",
|
||||
"executeCommandOrGroupTitle": "Execute Command or Command Group",
|
||||
"selectCommandLabel": "Select Command",
|
||||
|
@ -227,46 +232,47 @@
|
|||
"organizationalUnitLabel": "Parent",
|
||||
"ogLiveLabel": "OgLive",
|
||||
"serialNumberLabel": "Serial Number",
|
||||
"netifaceLabel": "Network Interface",
|
||||
"netDriverLabel": "Network Driver",
|
||||
"netifaceLabel": "Network interface",
|
||||
"netDriverLabel": "Network driver",
|
||||
"macLabel": "MAC",
|
||||
"macError": "Invalid MAC format. Valid example: 00:11:22:33:44:55",
|
||||
"ipLabel": "IP Address",
|
||||
"ipError": "Invalid IP address format. Valid example: 127.0.0.1",
|
||||
"templateLabel": "PXE Template",
|
||||
"digitalBoard": "Digital Board",
|
||||
"digitalBoard": "Digital board",
|
||||
"projectorAlt": "Projector",
|
||||
"clientAlt": "Client",
|
||||
"saveDispositionButton": "Save Disposition",
|
||||
"saveDispositionButton": "Save disposition",
|
||||
"actionsModalTitle": "Actions",
|
||||
"adminOuTitle": "Manage Organizational Units",
|
||||
"resetFiltersButton": "Reset Filters",
|
||||
"adminOuTitle": "Manage organizational units",
|
||||
"resetFiltersButton": "Reset filters",
|
||||
"addOUButton": "Add OU",
|
||||
"searchLabelOu": "Search OU name",
|
||||
"typeLabel": "Type",
|
||||
"macHint": "Example: 00:11:22:33:44:55",
|
||||
"ipHint": "Example: 123.1.1.1",
|
||||
"allOption": "All",
|
||||
"centerOption": "Center",
|
||||
"classroomsGroupOption": "Classroom Groups",
|
||||
"classroomOption": "Classroom",
|
||||
"clientsGroupOption": "PC Groups",
|
||||
"roomMapOption": "Classroom Layout",
|
||||
"clientDetailsTitle": "Client Details",
|
||||
"roomMapOption": "Classroom map",
|
||||
"clientDetailsTitle": "Client details",
|
||||
"commandsButton": "Commands",
|
||||
"networkPropertiesTab": "Network Properties",
|
||||
"networkPropertiesTab": "Network properties",
|
||||
"disksPartitionsTitle": "Disks/Partitions",
|
||||
"diskTitle": "Disk",
|
||||
"diskUsedLabel": "Used",
|
||||
"diskTotalLabel": "Total",
|
||||
"diskImageAssistantTitle": "Disk Image Assistant",
|
||||
"diskImageAssistantTitle": "Disk image assistant",
|
||||
"partitionColumn": "Partition",
|
||||
"isoImageColumn": "ISO Image",
|
||||
"ogliveColumn": "OgLive",
|
||||
"selectImageOption": "Select Image",
|
||||
"selectImageOption": "Select image",
|
||||
"selectOgLiveOption": "Select OgLive",
|
||||
"saveAssociationsButton": "Save Associations",
|
||||
"partitionAssistantTitle": "Partitioning Assistant",
|
||||
"partitionAssistantTitle": "Partition assistant",
|
||||
"diskSizeLabel": "Size",
|
||||
"partitionTypeColumn": "Partition Type",
|
||||
"partitionTypeColumn": "Partition type",
|
||||
"partitionSizeColumn": "Size (MB)",
|
||||
"usageColumn": "Usage (%)",
|
||||
"formatColumn": "Format",
|
||||
|
@ -274,63 +280,65 @@
|
|||
"linuxOption": "LINUX",
|
||||
"cacheOption": "CACHE",
|
||||
"deleteButton": "Delete",
|
||||
"searchTitle": "Advanced Search",
|
||||
"searchTitle": "Advanced search",
|
||||
"selectFilterLabel": "Select filter",
|
||||
"gridViewButton": "Grid",
|
||||
"listViewButton": "List",
|
||||
"selectOptionLabel": "Select an option",
|
||||
"namePlaceholder": "Organizational Unit",
|
||||
"namePlaceholder": "Organizational unit",
|
||||
"selectAllButton": "Select/Deselect All",
|
||||
"saveFiltersButton": "Save Filters",
|
||||
"sendFiltersButton": "Send Action",
|
||||
"addPxeButton": "Add PXE file",
|
||||
"internalUnits": "Internal Units",
|
||||
"internalUnits": "Internal units",
|
||||
"noResultsMessage": "No results to display.",
|
||||
"imagesTitle": "Manage Images",
|
||||
"addImageButton": "Add Image",
|
||||
"imagesTitle": "Manage images",
|
||||
"addImageButton": "Add image",
|
||||
"searchNameDescription": "Search images by name to quickly find a specific image.",
|
||||
"searchDefaultDescription": "Filter images to show only default or non-default images.",
|
||||
"searchDefaultLabel": "Default Image",
|
||||
"searchDefaultLabel": "Default image",
|
||||
"searchInstalledDescription": "Filter images to show only those installed on the OgBoot server.",
|
||||
"searchInstalledLabel": "Installed on OgBoot Server",
|
||||
"searchInstalledLabel": "Installed on OgBoot server",
|
||||
"tableDescription": "Here is the list of available images to manage.",
|
||||
"actionsColumnHeader": "Actions",
|
||||
"viewIcon": "visibility",
|
||||
"editIcon": "edit",
|
||||
"installOption": "Install",
|
||||
"uninstallOption": "Uninstall",
|
||||
"setDefaultOption": "Set as Default Image",
|
||||
"setDefaultOption": "Set as default image",
|
||||
"paginationDescription": "Navigate between image pages using the paginator.",
|
||||
"detailsTitle": "Details of {{ name }}",
|
||||
"editTemplateTitle": "Edit Template",
|
||||
"addTemplateTitle": "Add Template",
|
||||
"editTemplateTitle": "Edit template",
|
||||
"addTemplateTitle": "Add template",
|
||||
"templateNameLabel": "Template Name",
|
||||
"templateNamePlaceholder": "Enter the template name",
|
||||
"templateContentPlaceholder": "Enter the template content",
|
||||
"loadTemplateModelButton": "Load Template Model",
|
||||
"diskModel": "Disk Boot",
|
||||
"loadTemplateModelButton": "Load model template",
|
||||
"diskModel": "Disk boot",
|
||||
"createButton": "Create",
|
||||
"manageClientsTitle": "Manage Clients",
|
||||
"manageClientsTitle": "Manage clients",
|
||||
"syncIcon": "sync",
|
||||
"addClientsTitle": "Add clients to {{ subnetName }}",
|
||||
"selectedClientsTitle": "Selected Clients:",
|
||||
"selectedClientsTitle": "Selected clients:",
|
||||
"editClientTitle": "Edit Client",
|
||||
"addClientTitle": "Add Client",
|
||||
"advancedNetbootTitle": "Advanced Netboot",
|
||||
"advancedNetbootTitle": "Advanced netboot",
|
||||
"selectUnitLabel": "Select Organizational Unit",
|
||||
"loadingUnitsOption": "Loading units...",
|
||||
"selectClassLabel": "Select classroom",
|
||||
"applyToAllLabel": "Select template to apply to all clients",
|
||||
"saveButtonLabel": "Save",
|
||||
"tableDescription": "Manage Netboot templates for each client in this table.",
|
||||
"idColumnHeader": "Id",
|
||||
"nameColumnHeader": "Name",
|
||||
"templateColumnHeader": "Template",
|
||||
"pxeImageTitle": "OgBoot Server Information",
|
||||
"pxeImageTitle": "Information on ogBoot server",
|
||||
"serverInfoDescription": "Access information and synchronization options on the OgBoot server.",
|
||||
"syncDatabaseButton": "Sync Database",
|
||||
"syncDatabaseButton": "Sync database",
|
||||
"viewInfoButton": "View Information",
|
||||
"adminImagesDescription": "From here you can manage the images configured on the OgBoot server.",
|
||||
"searchNameDescription": "Search images by name to quickly find a specific image.",
|
||||
"actionsDescription": "Manage each image with options to view, edit, delete, and more."
|
||||
"actionsDescription": "Manage each image with options to view, edit, delete, and more.",
|
||||
"addClientButton": "Add client",
|
||||
"searchClientNameLabel": "Search client name",
|
||||
"searchIPLabel": "Search IP",
|
||||
"searchMACLabel": "Search MAC"
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
"labelOrganizationalUnit": "Unidad organizativa",
|
||||
"buttonCancel": "Cancelar",
|
||||
"buttonAdd": "Añadir",
|
||||
"addButton": "Añadir",
|
||||
"addClientDialogTitle": "Añadir Cliente",
|
||||
"dialogTitleEditUser": "Editar Usuario",
|
||||
"labelCurrentPassword": "Contraseña actual",
|
||||
"labelNewPassword": "Nueva contraseña",
|
||||
|
@ -36,7 +38,9 @@
|
|||
"checkboxOrgOperator": "Operador de Unidad Organizativa",
|
||||
"checkboxOrgMinimal": "Unidad Organizativa Mínima",
|
||||
"checkboxUserRole": "Usuario",
|
||||
"groupsTitleStepText": "En esta pantalla, puedes gestionar las principales unidades organizativas (Facultades, Aulas, Grupos de Aulas y clientes).",
|
||||
"titleStepText": "En esta pantalla, puedes gestionar los calendarios de los equipos remotos conectados con el servicio UDS",
|
||||
"groupsAddStepText": "Haz clic para añadir una nueva unidad organizativa o un cliente.",
|
||||
"adminCalendarsTitle": "Administrar calendarios",
|
||||
"addButtonStepText": "Haz clic aquí para añadir un nuevo calendario.",
|
||||
"addCalendar": "Añadir calendario",
|
||||
|
@ -144,6 +148,7 @@
|
|||
"commandScriptPlaceholder": "Script del comando",
|
||||
"readOnlyLabel": "Solo lectura",
|
||||
"enabledLabel": "Habilitado",
|
||||
"cancelButton": "cancelar",
|
||||
"generalTabLabel": "General",
|
||||
"tabsStepText": "Utiliza las pestañás para acceder a las diferentes opciones de visualización y busqueda de unidades organizativas y clientes.",
|
||||
"adminGroupsTitle": "Administrar grupos",
|
||||
|
@ -243,7 +248,8 @@
|
|||
"resetFiltersButton": "Reiniciar filtros",
|
||||
"addOUButton": "Añadir OU",
|
||||
"searchLabelOu": "Buscar nombre de OU",
|
||||
"typeLabel": "Tipo",
|
||||
"macHint": "Ejemplo: 00:11:22:33:44:55",
|
||||
"ipHint": "Ejemplo: 123.1.1.1",
|
||||
"allOption": "Todos",
|
||||
"centerOption": "Centro",
|
||||
"classroomsGroupOption": "Grupos de aulas",
|
||||
|
@ -322,7 +328,6 @@
|
|||
"selectClassLabel": "Selecciona aula",
|
||||
"applyToAllLabel": "Seleccione plantilla para aplicar a todos los clientes",
|
||||
"saveButtonLabel": "Guardar",
|
||||
"tableDescription": "Administra las plantillas de Netboot para cada cliente en esta tabla.",
|
||||
"idColumnHeader": "Id",
|
||||
"nameColumnHeader": "Nombre",
|
||||
"templateColumnHeader": "Plantilla",
|
||||
|
@ -331,6 +336,9 @@
|
|||
"syncDatabaseButton": "Sincronizar base de datos",
|
||||
"viewInfoButton": "Ver Información",
|
||||
"adminImagesDescription": "Desde aquí puedes gestionar las imágenes configuradas en el servidor OgBoot.",
|
||||
"searchNameDescription": "Busca imágenes por nombre para encontrar rápidamente una imagen específica.",
|
||||
"actionsDescription": "Administra cada imagen con opciones para ver, editar, eliminar y más."
|
||||
"actionsDescription": "Administra cada imagen con opciones para ver, editar, eliminar y más.",
|
||||
"addClientButton": "Añadir cliente",
|
||||
"searchClientNameLabel": "Buscar nombre de cliente",
|
||||
"searchIPLabel": "Buscar IP",
|
||||
"searchMACLabel": "Buscar MAC"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue