ogRepo new endpoints. Export/Import image
testing/ogGui-multibranch/pipeline/head This commit looks good
Details
testing/ogGui-multibranch/pipeline/head This commit looks good
Details
parent
b2bf6b8c96
commit
bdbb16d3fd
|
@ -124,6 +124,8 @@ import { MatSortModule } from '@angular/material/sort';
|
|||
import { MenusComponent } from './components/menus/menus.component';
|
||||
import { CreateMenuComponent } from './components/menus/create-menu/create-menu.component';
|
||||
import { CreateMultipleClientComponent } from './components/groups/shared/clients/create-multiple-client/create-multiple-client.component';
|
||||
import { ExportImageComponent } from './components/images/export-image/export-image.component';
|
||||
import {ImportImageComponent} from "./components/repositories/import-image/import-image.component";
|
||||
export function HttpLoaderFactory(http: HttpClient) {
|
||||
return new TranslateHttpLoader(http, './locale/', '.json');
|
||||
}
|
||||
|
@ -206,6 +208,8 @@ export function HttpLoaderFactory(http: HttpClient) {
|
|||
MenusComponent,
|
||||
CreateMenuComponent,
|
||||
CreateMultipleClientComponent,
|
||||
ExportImageComponent,
|
||||
ImportImageComponent,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [BrowserModule,
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
mat-dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<h2 mat-dialog-title>Exportar imagen {{data.image?.name}}</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione repositorio destino</mat-label>
|
||||
<mat-select [(value)]="selectedRepository">
|
||||
<mat-option *ngFor="let repository of repositories" [value]="repository['@id']">{{ repository.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="close()">Cancelar</button>
|
||||
<button mat-button (click)="save()">Continuar</button>
|
||||
</mat-dialog-actions>
|
|
@ -0,0 +1,58 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ExportImageComponent } from './export-image.component';
|
||||
import {FormBuilder, ReactiveFormsModule} from "@angular/forms";
|
||||
import {ToastrModule, ToastrService} from "ngx-toastr";
|
||||
import {provideHttpClient} from "@angular/common/http";
|
||||
import {provideHttpClientTesting} from "@angular/common/http/testing";
|
||||
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
|
||||
import {MatFormFieldModule} from "@angular/material/form-field";
|
||||
import {MatInputModule} from "@angular/material/input";
|
||||
import {MatButtonModule} from "@angular/material/button";
|
||||
import {MatSelectModule} from "@angular/material/select";
|
||||
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
|
||||
import {TranslateModule} from "@ngx-translate/core";
|
||||
|
||||
describe('ExportImageComponent', () => {
|
||||
let component: ExportImageComponent;
|
||||
let fixture: ComponentFixture<ExportImageComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ExportImageComponent],
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatButtonModule,
|
||||
MatSelectModule,
|
||||
BrowserAnimationsModule,
|
||||
ToastrModule.forRoot(),
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
FormBuilder,
|
||||
ToastrService,
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
{
|
||||
provide: MatDialogRef,
|
||||
useValue: {}
|
||||
},
|
||||
{
|
||||
provide: MAT_DIALOG_DATA,
|
||||
useValue: {}
|
||||
}]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ExportImageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: 'app-export-image',
|
||||
templateUrl: './export-image.component.html',
|
||||
styleUrl: './export-image.component.css'
|
||||
})
|
||||
export class ExportImageComponent implements OnInit {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
loading: boolean = true;
|
||||
repositories: any[] = [];
|
||||
selectedRepository: string = '';
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
public dialogRef: MatDialogRef<ExportImageComponent>,
|
||||
private toastService: ToastrService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { image: any }
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading = true;
|
||||
this.loadRepositories();
|
||||
}
|
||||
|
||||
loadRepositories() {
|
||||
this.http.get<any>(`${this.baseUrl}/image-repositories?page=1&itemsPerPage=50`).subscribe(
|
||||
response => {
|
||||
this.repositories = response['hydra:member'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => console.error('Error fetching organizational units:', error)
|
||||
);
|
||||
}
|
||||
|
||||
save() {
|
||||
this.http.post<any>(`${this.baseUrl}${this.selectedRepository}/export-image`, {
|
||||
images: [this.data.image['@id']]
|
||||
}).subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Imagen exportada correctamente');
|
||||
this.dialogRef.close();
|
||||
},
|
||||
error: error => {
|
||||
console.error('Error al exportar imagen:', error);
|
||||
this.toastService.error('Error al exportar imagen');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
|
@ -91,3 +91,9 @@ table {
|
|||
color: white;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title">{{ 'imagesTitle' | translate }}</h2>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="groupsTitleStepText" text="{{ 'groupsTitleStepText' | translate }}">
|
||||
{{ 'imagesTitle' | translate }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="images-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addImage()">
|
||||
{{ 'addImageButton' | translate }}
|
||||
|
@ -62,7 +66,7 @@
|
|||
<button mat-menu-item (click)="toggleAction(image, 'get-aux')">Obtener ficheros auxiliares</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'" (click)="toggleAction(image, 'delete-trash')">Eliminar imagen temporalmente</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'trash'" (click)="toggleAction(image, 'recover')">Recuperar imagen de la papelera</button>
|
||||
|
||||
<button mat-menu-item (click)="toggleAction(image, 'export')">Exportar imagen</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
|
|
@ -11,6 +11,7 @@ import {ServerInfoDialogComponent} from "../ogdhcp/og-dhcp-subnets/server-info-d
|
|||
import {Observable} from "rxjs";
|
||||
import {InfoImageComponent} from "../ogboot/pxe-images/info-image/info-image/info-image.component";
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
import {ExportImageComponent} from "./export-image/export-image.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-images',
|
||||
|
@ -216,6 +217,14 @@ export class ImagesComponent implements OnInit {
|
|||
}
|
||||
});
|
||||
break;
|
||||
case 'export':
|
||||
this.dialog.open(ExportImageComponent, {
|
||||
width: '600px',
|
||||
data: {
|
||||
image: image
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.error('Acción no soportada:', action);
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
mat-dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<h2 mat-dialog-title>Importar imagenes a {{data.repository?.name}}</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione imagenes a importar</mat-label>
|
||||
<mat-select [(value)]="selectedClients" multiple>
|
||||
<mat-option *ngFor="let image of images" [value]="image['@id']">{{ image.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="close()">Cancelar</button>
|
||||
<button mat-button (click)="save()">Continuar</button>
|
||||
</mat-dialog-actions>
|
|
@ -0,0 +1,59 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ImportImageComponent } from './import-image.component';
|
||||
import {FormBuilder, ReactiveFormsModule} from "@angular/forms";
|
||||
import {ToastrModule, ToastrService} from "ngx-toastr";
|
||||
import {DataService} from "../../calendar/data.service";
|
||||
import {provideHttpClient} from "@angular/common/http";
|
||||
import {provideHttpClientTesting} from "@angular/common/http/testing";
|
||||
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
|
||||
import {MatFormFieldModule} from "@angular/material/form-field";
|
||||
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
|
||||
import {TranslateModule} from "@ngx-translate/core";
|
||||
import {MatButtonModule} from "@angular/material/button";
|
||||
import {MatInputModule} from "@angular/material/input";
|
||||
import {MatSelectModule} from "@angular/material/select";
|
||||
|
||||
describe('ImportImageComponent', () => {
|
||||
let component: ImportImageComponent;
|
||||
let fixture: ComponentFixture<ImportImageComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ImportImageComponent],
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatButtonModule,
|
||||
MatSelectModule,
|
||||
BrowserAnimationsModule,
|
||||
ToastrModule.forRoot(),
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
FormBuilder,
|
||||
ToastrService,
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
{
|
||||
provide: MatDialogRef,
|
||||
useValue: {}
|
||||
},
|
||||
{
|
||||
provide: MAT_DIALOG_DATA,
|
||||
useValue: {}
|
||||
}]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ImportImageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: 'app-import-image',
|
||||
templateUrl: './import-image.component.html',
|
||||
styleUrl: './import-image.component.css'
|
||||
})
|
||||
export class ImportImageComponent implements OnInit{
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
loading: boolean = true;
|
||||
images: any[] = [];
|
||||
selectedClients: any[] = [];
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
public dialogRef: MatDialogRef<ImportImageComponent>,
|
||||
private toastService: ToastrService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { repository: any }
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading = true;
|
||||
this.loadImages();
|
||||
}
|
||||
|
||||
loadImages() {
|
||||
this.http.get<any>(`${this.baseUrl}/images?page=1&itemsPerPage=50`).subscribe(
|
||||
response => {
|
||||
this.images = response['hydra:member'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => console.error('Error fetching organizational units:', error)
|
||||
);
|
||||
}
|
||||
|
||||
save() {
|
||||
this.http.post<any>(`${this.baseUrl}${this.data.repository['@id']}/import-image`, {
|
||||
images: this.selectedClients
|
||||
}).subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Peticion de importacion de imagen enviada correctamente');
|
||||
this.dialogRef.close();
|
||||
},
|
||||
error: error => {
|
||||
this.toastService.error('Error al importar imagenes');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
|
@ -135,6 +135,10 @@
|
|||
min-height: 400px;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.mat-tab-body-wrapper {
|
||||
min-height: inherit;
|
||||
}
|
||||
|
@ -210,13 +214,6 @@ p {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.status-led {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.status-led.active {
|
||||
background-color: green;
|
||||
|
@ -304,4 +301,85 @@ table {
|
|||
}
|
||||
|
||||
|
||||
.dashboard {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.top-row .card {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.status-led {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.cpu-usage-bar {
|
||||
background: lightgray;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cpu-bar {
|
||||
height: 100%;
|
||||
background: green;
|
||||
}
|
||||
|
||||
.cpu-bar.high {
|
||||
background: red;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.top-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-row .card {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,51 +1,91 @@
|
|||
<mat-tab-group dynamicHeight>
|
||||
<mat-tab-group class="main-container" 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>
|
||||
<h2>OgRepository Server Status</h2>
|
||||
|
||||
<div class="row top-row">
|
||||
<div class="card">
|
||||
<h3>Uso de Disco</h3>
|
||||
<ngx-charts-pie-chart
|
||||
[view]="view"
|
||||
[scheme]="colorScheme"
|
||||
[results]="diskUsageChartData"
|
||||
[gradient]="gradient"
|
||||
[doughnut]="isDoughnut"
|
||||
[labels]="showLabels"
|
||||
[legend]="showLegend">
|
||||
[labels]="showLabels" >
|
||||
</ngx-charts-pie-chart>
|
||||
<div class="disk-usage-info">
|
||||
<div class="info">
|
||||
<p>Total: {{ diskUsage.total }}</p>
|
||||
<p>Ocupado: {{ diskUsage.used }}</p>
|
||||
<p>Disponible: {{ diskUsage.available }}</p>
|
||||
<p>Ocupado ( % ): {{ diskUsage.percentage }}</p>
|
||||
<p>Ocupado (%): {{ diskUsage.percentage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="services-status">
|
||||
<div class="card">
|
||||
<h3>Uso de RAM</h3>
|
||||
<ngx-charts-pie-chart
|
||||
[view]="view"
|
||||
[scheme]="colorScheme"
|
||||
[results]="ramUsageChartData"
|
||||
[gradient]="gradient"
|
||||
[doughnut]="isDoughnut"
|
||||
[labels]="showLabels">
|
||||
</ngx-charts-pie-chart>
|
||||
<div class="info">
|
||||
<p>Total: {{ ramUsage.total }}</p>
|
||||
<p>Ocupado: {{ ramUsage.used }}</p>
|
||||
<p>Disponible: {{ ramUsage.available }}</p>
|
||||
<p>Ocupado (%): {{ ramUsage.percentage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row bottom-row">
|
||||
<div class="card">
|
||||
<h3>Uso de CPU</h3>
|
||||
<div class="cpu-usage-bar">
|
||||
<div class="cpu-bar" [style.width]="cpuUsage.percentage" [ngClass]="{'high': cpuUsage.percentage > '80%'}"></div>
|
||||
</div>
|
||||
<p>Usado: {{ cpuUsage.percentage }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<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>
|
||||
<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>
|
||||
<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 class="card">
|
||||
<h3>Procesos</h3>
|
||||
<ul>
|
||||
<li *ngFor="let process of getProcesses()">
|
||||
<span class="status-led" [ngClass]="{
|
||||
'active': process.status === 'running',
|
||||
'inactive': process.status === 'stopped'
|
||||
}"></span>
|
||||
{{ process.name }}: {{ process.status }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
|
||||
<mat-tab label="Datos generales">
|
||||
<div class="dashboard">
|
||||
<div class="header-button-container">
|
||||
|
@ -83,59 +123,6 @@
|
|||
</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>
|
||||
<app-images />
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Component, Inject} from '@angular/core';
|
||||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
@ -17,7 +17,7 @@ import {MatDialog} from "@angular/material/dialog";
|
|||
templateUrl: './main-repository-view.component.html',
|
||||
styleUrl: './main-repository-view.component.css'
|
||||
})
|
||||
export class MainRepositoryViewComponent {
|
||||
export class MainRepositoryViewComponent implements OnInit {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
repositoryForm: FormGroup<any>;
|
||||
repositoryId: string | null = null;
|
||||
|
@ -25,16 +25,20 @@ export class MainRepositoryViewComponent {
|
|||
loading: boolean = true;
|
||||
diskUsage: any = {};
|
||||
servicesStatus: any = {};
|
||||
processesStatus: any = {};
|
||||
diskUsageChartData: any[] = [];
|
||||
ramUsageChartData: any[] = [];
|
||||
ramUsage: any = {};
|
||||
cpuUsage: 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;
|
||||
status: boolean = false;
|
||||
repositoryData: any = {};
|
||||
colorScheme: any = {
|
||||
domain: ['#FF6384', '#3f51b5']
|
||||
|
@ -115,7 +119,6 @@ export class MainRepositoryViewComponent {
|
|||
comments: [response.comments],
|
||||
});
|
||||
this.loading = false;
|
||||
// Llamar searchImages() solo cuando la data de repository esté cargada
|
||||
this.searchImages();
|
||||
},
|
||||
(error) => {
|
||||
|
@ -157,28 +160,33 @@ export class MainRepositoryViewComponent {
|
|||
|
||||
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;
|
||||
if (!data.success) {
|
||||
console.error('Error: No se pudo obtener los datos del servidor');
|
||||
this.status = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.diskUsage = {
|
||||
total: diskData.total,
|
||||
used: diskData.used,
|
||||
available: diskData.available,
|
||||
percentage: diskData.used_percentage
|
||||
};
|
||||
this.status = true;
|
||||
|
||||
const { disk, services, ram, cpu, processes } = data.output;
|
||||
|
||||
this.diskUsage = { ...disk };
|
||||
this.diskUsageChartData = [
|
||||
{
|
||||
name: 'Usado',
|
||||
value: parseFloat(diskData.used)
|
||||
},
|
||||
{
|
||||
name: 'Disponible',
|
||||
value: parseFloat(diskData.available)
|
||||
}
|
||||
{ name: 'Usado', value: parseFloat(disk.used.replace('GB', '')) },
|
||||
{ name: 'Disponible', value: parseFloat(disk.available.replace('GB', '')) }
|
||||
];
|
||||
|
||||
this.servicesStatus = servicesData;
|
||||
this.ramUsage = { ...ram };
|
||||
this.ramUsageChartData = [
|
||||
{ name: 'Usado', value: parseFloat(ram.used.replace('GB', '')) },
|
||||
{ name: 'Disponible', value: parseFloat(ram.available.replace('GB', '')) }
|
||||
];
|
||||
|
||||
this.cpuUsage = { percentage: cpu.used_percentage };
|
||||
|
||||
this.servicesStatus = Object.entries(services).map(([name, status]) => ({ name, status }));
|
||||
|
||||
this.processesStatus = Object.entries(processes).map(([name, status]) => ({ name, status }));
|
||||
|
||||
}, error => {
|
||||
console.error('Error fetching status', error);
|
||||
|
@ -186,10 +194,17 @@ export class MainRepositoryViewComponent {
|
|||
}
|
||||
|
||||
getServices(): { name: string, status: string }[] {
|
||||
return Object.keys(this.servicesStatus).map(key => ({
|
||||
name: key,
|
||||
status: this.servicesStatus[key]
|
||||
}));
|
||||
if (!this.status) {
|
||||
return [];
|
||||
}
|
||||
return this.servicesStatus ? this.servicesStatus : [];
|
||||
}
|
||||
|
||||
getProcesses(): { name: string, status: string }[] {
|
||||
if (!this.status) {
|
||||
return [];
|
||||
}
|
||||
return this.processesStatus ? this.processesStatus : [];
|
||||
}
|
||||
|
||||
searchImages(): void {
|
||||
|
@ -207,64 +222,6 @@ export class MainRepositoryViewComponent {
|
|||
);
|
||||
}
|
||||
|
||||
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`);
|
||||
}
|
||||
|
@ -280,29 +237,6 @@ export class MainRepositoryViewComponent {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
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 => {
|
||||
|
|
|
@ -100,3 +100,10 @@ table {
|
|||
margin: 8px 8px 8px 0;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" joyrideStep="titleStep" text="Desde esta pantalla podrás ver y administrar los respositioros exitentes.">Administrar repositorios</h2>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="groupsTitleStepText" text="{{ 'groupsTitleStepText' | translate }}">
|
||||
{{ 'repositoryTitle' | translate }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="images-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addImage()" joyrideStep="addStep" text="Utiliza este botón para añadir un nuevo repositorio.">Añadir repositorio</button>
|
||||
</div>
|
||||
|
@ -11,14 +15,20 @@
|
|||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>Buscar nombre de imagen</mat-label>
|
||||
<mat-label>Buscar nombre de repositorio</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>Buscar IP de repositorio</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['ip']" (keyup.enter)="search()" 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">
|
||||
<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 repository" >
|
||||
|
@ -30,9 +40,10 @@
|
|||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let client" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="editRepository($event, client)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="deleteRepository($event, client)">
|
||||
<td mat-cell *matCellDef="let repository" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="importImage($event, repository)" i18n="@@editImage"> <mat-icon>download</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editRepository($event, repository)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="deleteRepository($event, repository)">
|
||||
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
|
|
|
@ -8,6 +8,7 @@ import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delet
|
|||
import { JoyrideService } from 'ngx-joyride';
|
||||
import {CreateRepositoryComponent} from "./create-repository/create-repository.component";
|
||||
import { Router } from '@angular/router';
|
||||
import {ImportImageComponent} from "./import-image/import-image.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-repositories',
|
||||
|
@ -91,6 +92,16 @@ export class RepositoriesComponent {
|
|||
this.router.navigate(['repository', repository.uuid]);
|
||||
}
|
||||
|
||||
importImage(event: MouseEvent, repository: any): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(ImportImageComponent, {
|
||||
width: '600px',
|
||||
data: { repository }
|
||||
}).afterClosed().subscribe(() => {
|
||||
this.search();
|
||||
});
|
||||
}
|
||||
|
||||
deleteRepository(event: MouseEvent,command: any): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(DeleteModalComponent, {
|
||||
|
|
|
@ -275,6 +275,7 @@
|
|||
"ogliveColumn": "OgLive",
|
||||
"selectImageOption": "Select image",
|
||||
"selectOgLiveOption": "Select OgLive",
|
||||
"repositoryTitle": "Admin Repository",
|
||||
"saveAssociationsButton": "Save Associations",
|
||||
"partitionAssistantTitle": "Partition assistant",
|
||||
"diskSizeLabel": "Size",
|
||||
|
|
|
@ -300,6 +300,7 @@
|
|||
"internalUnits": "Unidades internas",
|
||||
"noResultsMessage": "No hay resultados para mostrar.",
|
||||
"imagesTitle": "Administrar imágenes",
|
||||
"repositoryTitle": "Administrar repositorios",
|
||||
"addImageButton": "Añadir imagen",
|
||||
"searchNameDescription": "Busca imágenes por nombre para encontrar rápidamente una imagen específica.",
|
||||
"searchDefaultDescription": "Filtra las imágenes para mostrar solo las imágenes por defecto o no por defecto.",
|
||||
|
|
Loading…
Reference in New Issue