refs #1079. Added admin Repository
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details

oggui/translations
Manuel Aranda Rosales 2024-10-29 16:50:17 +01:00
parent 83dc01aa1c
commit ffebe422ba
10 changed files with 481 additions and 0 deletions

View File

@ -30,6 +30,7 @@ import {OperativeSystemComponent} from "./components/operative-system/operative-
import {
PartitionAssistantComponent
} from "./components/groups/components/client-main-view/partition-assistant/partition-assistant.component";
import {RepositoriesComponent} from "./components/repositories/repositories.component";
const routes: Routes = [
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
{
@ -56,6 +57,7 @@ const routes: Routes = [
{ path: 'client/:id', component: ClientMainViewComponent },
{ path: 'client/:id/partition-assistant', component: PartitionAssistantComponent },
{ path: 'images', component: ImagesComponent },
{ path: 'repositories', component: RepositoriesComponent },
{ path: 'restore-image', component: RestoreImageComponent},
{ path: 'software', component: SoftwareComponent },
{ path: 'software-profiles', component: SoftwareProfileComponent },

View File

@ -116,6 +116,8 @@ import { CreateOperativeSystemComponent } from './components/operative-system/cr
import { ShowTemplateContentComponent } from './components/ogboot/pxe/show-template-content/show-template-content.component';
import { AddClientsToPxeComponent } from './components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component';
import { ClientsComponent } from './components/ogboot/pxe/clients/clients.component';
import { RepositoriesComponent } from './components/repositories/repositories.component';
import { CreateRepositoryComponent } from './components/repositories/create-repository/create-repository.component';
@NgModule({
declarations: [
AppComponent,
@ -189,6 +191,8 @@ import { ClientsComponent } from './components/ogboot/pxe/clients/clients.compon
ShowTemplateContentComponent,
AddClientsToPxeComponent,
ClientsComponent,
RepositoriesComponent,
CreateRepositoryComponent,
],
bootstrap: [AppComponent],
imports: [BrowserModule,

View File

@ -0,0 +1,43 @@
.dialog-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.repository-form {
width: 100%;
display: flex;
flex-direction: column;
}
.form-field {
width: 100%;
margin-bottom: 16px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
margin-top: 24px;
}
button {
margin-left: 8px;
}
@media (max-width: 600px) {
.form-field {
width: 100%;
}
.dialog-actions {
flex-direction: column;
align-items: stretch;
}
button {
width: 100%;
margin-left: 0;
margin-bottom: 8px;
}
}

View File

@ -0,0 +1,25 @@
<h2 mat-dialog-title> {{ repositoryId ? 'Editar' : 'Añadir' }} repositorio</h2>
<mat-dialog-content class="dialog-content">
<form [formGroup]="imageForm" (ngSubmit)="save()" class="repository-form">
<mat-form-field appearance="fill" class="form-field">
<mat-label>Nombre del repositorio</mat-label>
<input matInput formControlName="name" required>
</mat-form-field>
<mat-form-field appearance="fill" class="form-field">
<mat-label>Ip</mat-label>
<input matInput formControlName="ip" name="description">
</mat-form-field>
<mat-form-field appearance="fill" class="form-field">
<mat-label>Comentarios</mat-label>
<input matInput formControlName="comments" name="comments">
</mat-form-field>
</form>
</mat-dialog-content>
<mat-dialog-actions align="end" class="dialog-actions">
<button mat-button (click)="close()">Cancelar</button>
<button mat-button color="primary" (click)="save()">Guardar</button>
</mat-dialog-actions>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateRepositoryComponent } from './create-repository.component';
describe('CreateRepositoryComponent', () => {
let component: CreateRepositoryComponent;
let fixture: ComponentFixture<CreateRepositoryComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CreateRepositoryComponent]
})
.compileComponents();
fixture = TestBed.createComponent(CreateRepositoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,91 @@
import {Component, Inject} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {HttpClient} from "@angular/common/http";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {ToastrService} from "ngx-toastr";
import {DataService} from "../../images/data.service";
@Component({
selector: 'app-create-repository',
templateUrl: './create-repository.component.html',
styleUrl: './create-repository.component.css'
})
export class CreateRepositoryComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
imageForm: FormGroup<any>;
repositoryId: string | null = null;
softwareProfiles: any[] = [];
constructor(
private fb: FormBuilder,
private http: HttpClient,
public dialogRef: MatDialogRef<CreateRepositoryComponent>,
private toastService: ToastrService,
private dataService: DataService,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.imageForm = this.fb.group({
name: ['', Validators.required],
ip: [''],
comments: [''],
});
}
ngOnInit() {
if (this.data) {
this.load()
}
}
load(): void {
this.dataService.getImage(this.data).subscribe({
next: (response) => {
this.imageForm = this.fb.group({
name: [response.name, Validators.required],
ip: [response.ip],
comments: [response.comments],
});
this.repositoryId = response['@id'];
},
error: (err) => {
console.error('Error fetching remote calendar:', err);
}
});
}
save(): void {
const payload = {
name: this.imageForm.value.name,
ip: this.imageForm.value.ip,
comments: this.imageForm.value.comments,
};
if (this.repositoryId) {
this.http.put(`${this.baseUrl}${this.repositoryId}`, payload).subscribe(
(response) => {
this.toastService.success('Imagen editada correctamente');
this.dialogRef.close();
},
(error) => {
this.toastService.error(error['error']['hydra:description']);
console.error('Error al editar la imagen', error);
}
);
} else {
this.http.post(`${this.baseUrl}/image-repositories`, payload).subscribe(
(response) => {
this.toastService.success('Imagen añadida correctamente');
this.dialogRef.close();
},
(error) => {
this.toastService.error(error['error']['hydra:description']);
console.error('Error al añadir la imagen', error);
}
);
}
}
close(): void {
this.dialogRef.close();
}
}

View File

@ -0,0 +1,102 @@
.title {
font-size: 24px;
}
.images-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
}
.divider {
margin: 20px 0;
}
.lists-container {
padding: 16px;
}
.imagesLists-container {
flex: 1;
}
.card.unidad-card {
height: 100%;
box-sizing: border-box;
}
.image-container {
display: flex;
align-items: center;
margin-bottom: 16px;
border-bottom: 1px solid rgba(122, 122, 122, 0.555);
}
.image-container h4 {
margin: 0;
flex: 1;
}
.image-name{
cursor: pointer;
}
table {
width: 100%;
margin-top: 50px;
}
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 5px;
box-sizing: border-box;
}
.search-string {
flex: 2;
padding: 5px;
}
.search-boolean {
flex: 1;
padding: 5px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
}
.paginator-container {
display: flex;
justify-content: end;
margin-bottom: 30px;
}
.example-headers-align .mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field {
margin-left: 8px;
}
.example-button-row {
display: table-cell;
max-width: 600px;
}
.example-button-row .mat-mdc-button-base {
margin: 8px 8px 8px 0;
}

View File

@ -0,0 +1,48 @@
<div class="header-container">
<h2 class="title">Administrar repositorios</h2>
<div class="images-button-row">
<button mat-flat-button color="primary" (click)="addImage()">Añadir repositorio</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill" class="search-string">
<mat-label>Buscar nombre de imagen</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
</mat-form-field>
</div>
<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" >
<ng-container>
{{ column.cell(repository) }}
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let client" style="text-align: center;">
<button 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)">
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<div class="paginator-container">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="[5, 10, 20, 40, 100]"
(page)="onPageChange($event)">
</mat-paginator>
</div>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RepositoriesComponent } from './repositories.component';
describe('RepositoriesComponent', () => {
let component: RepositoriesComponent;
let fixture: ComponentFixture<RepositoriesComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [RepositoriesComponent]
})
.compileComponents();
fixture = TestBed.createComponent(RepositoriesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,120 @@
import { Component } from '@angular/core';
import {MatTableDataSource} from "@angular/material/table";
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";
@Component({
selector: 'app-repositories',
templateUrl: './repositories.component.html',
styleUrl: './repositories.component.css'
})
export class RepositoriesComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
dataSource = new MatTableDataSource<any>();
length: number = 0;
itemsPerPage: number = 10;
page: number = 0;
loading: boolean = false;
filters: { [key: string]: string } = {};
datePipe: DatePipe = new DatePipe('es-ES');
columns = [
{
columnDef: 'id',
header: 'Id',
cell: (repository: any) => `${repository.id}`
},
{
columnDef: 'name',
header: 'Nombre de repositorio',
cell: (repository: any) => `${repository.name}`
},
{
columnDef: 'ip',
header: 'Ip',
cell: (repository: any) => `${repository.ip}`
},
{
columnDef: 'createdAt',
header: 'Fecha de creación',
cell: (repository: any) => `${this.datePipe.transform(repository.createdAt, 'dd/MM/yyyy hh:mm:ss')}`
}
];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
private apiUrl = `${this.baseUrl}/image-repositories`;
constructor(
public dialog: MatDialog,
private http: HttpClient,
private toastService: ToastrService
) {}
ngOnInit(): void {
this.search();
}
addImage(): void {
const dialogRef = this.dialog.open(CreateRepositoryComponent, {
width: '600px'
});
dialogRef.afterClosed().subscribe(() => {
this.search();
});
}
search(): void {
this.loading = true;
this.http.get<any>(`${this.apiUrl}?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;
}
);
}
editRepository(event: MouseEvent, repository: any): void {
event.stopPropagation();
this.dialog.open(CreateRepositoryComponent, {
width: '600px',
data: repository['@id']
}).afterClosed().subscribe(() => this.search());
}
deleteRepository(event: MouseEvent,command: any): void {
event.stopPropagation();
this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: command.name },
}).afterClosed().subscribe((result) => {
if (result) {
this.http.delete(`${this.apiUrl}/${command.uuid}`).subscribe({
next: () => {
this.toastService.success('Imagen eliminada con éxito');
this.search();
},
error: (error) => {
console.error('Error al eliminar la imagen:', error);
}
});
}
});
}
onPageChange(event: any): void {
this.page = event.pageIndex;
this.itemsPerPage = event.pageSize;
this.length = event.length;
this.search();
}
}