Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
commit
822984baca
|
@ -24,6 +24,9 @@ import { StatusComponent } from "./components/ogdhcp/og-dhcp-subnets/status/stat
|
||||||
import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component';
|
import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component';
|
||||||
import { ImagesComponent } from './components/images/images.component';
|
import { ImagesComponent } from './components/images/images.component';
|
||||||
import { RestoreImageComponent } from './components/groups/components/client-main-view/restore-image/restore-image.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";
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
|
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
|
||||||
{
|
{
|
||||||
|
@ -49,7 +52,10 @@ const routes: Routes = [
|
||||||
{ path: 'calendars', component: CalendarComponent },
|
{ path: 'calendars', component: CalendarComponent },
|
||||||
{ path: 'client/:id', component: ClientMainViewComponent },
|
{ path: 'client/:id', component: ClientMainViewComponent },
|
||||||
{ path: 'images', component: ImagesComponent },
|
{ path: 'images', component: ImagesComponent },
|
||||||
{ path: 'restore-image', component: RestoreImageComponent}
|
{ path: 'restore-image', component: RestoreImageComponent},
|
||||||
|
{ path: 'software', component: SoftwareComponent },
|
||||||
|
{ path: 'software-profiles', component: SoftwareProfileComponent },
|
||||||
|
{ path: 'operative-systems', component: OperativeSystemComponent },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -107,6 +107,12 @@ import { ImagesComponent } from './components/images/images.component';
|
||||||
import { CreateImageComponent } from './components/images/create-image/create-image.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 { 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 { 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';
|
||||||
|
import { CreateSoftwareProfileComponent } from './components/software-profile/create-software-profile/create-software-profile.component';
|
||||||
|
import { OperativeSystemComponent } from './components/operative-system/operative-system.component';
|
||||||
|
import { CreateOperativeSystemComponent } from './components/operative-system/create-operative-system/create-operative-system.component';
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
|
@ -171,6 +177,12 @@ import { RestoreImageComponent } from './components/groups/components/client-mai
|
||||||
CreateImageComponent,
|
CreateImageComponent,
|
||||||
PartitionAssistantComponent,
|
PartitionAssistantComponent,
|
||||||
RestoreImageComponent,
|
RestoreImageComponent,
|
||||||
|
SoftwareComponent,
|
||||||
|
CreateSoftwareComponent,
|
||||||
|
SoftwareProfileComponent,
|
||||||
|
CreateSoftwareProfileComponent,
|
||||||
|
OperativeSystemComponent,
|
||||||
|
CreateOperativeSystemComponent,
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
imports: [BrowserModule,
|
imports: [BrowserModule,
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {MatDialog} from "@angular/material/dialog";
|
||||||
import {HttpClient} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
import {DataService} from "./data.service";
|
import {DataService} from "./data.service";
|
||||||
import {ToastrService} from "ngx-toastr";
|
import {ToastrService} from "ngx-toastr";
|
||||||
import {InfoImageComponent} from "../ogboot/pxe-images/info-image/info-image/info-image.component";
|
|
||||||
import {PageEvent} from "@angular/material/paginator";
|
import {PageEvent} from "@angular/material/paginator";
|
||||||
import {CreateCalendarComponent} from "./create-calendar/create-calendar.component";
|
import {CreateCalendarComponent} from "./create-calendar/create-calendar.component";
|
||||||
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||||
|
|
|
@ -32,12 +32,6 @@
|
||||||
{{ service.name }}: {{ service.status }}
|
{{ service.name }}: {{ service.status }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="button-container">
|
|
||||||
<button mat-flat-button color="primary" class="btn" routerLink="/images">Oglive</button>
|
|
||||||
<button mat-flat-button color="primary" class="btn" routerLink="/pxe">Plantillas PXE</button>
|
|
||||||
<button mat-flat-button color="primary" class="btn" routerLink="/pxe-boot-file">Arranque PXE</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -63,4 +57,3 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,7 @@ export class OgbootStatusComponent implements OnInit {
|
||||||
installedOglives: any[] = [];
|
installedOglives: any[] = [];
|
||||||
diskUsageChartData: any[] = [];
|
diskUsageChartData: any[] = [];
|
||||||
|
|
||||||
view: [number, number] = [1300, 500];
|
view: [number, number] = [1100, 500];
|
||||||
|
|
||||||
// Opciones de la gráfica
|
// Opciones de la gráfica
|
||||||
gradient: boolean = true;
|
gradient: boolean = true;
|
||||||
|
|
|
@ -37,7 +37,7 @@ export class CreatePXEImageComponent implements OnInit {
|
||||||
this.http.get(`${this.baseUrl}/og-lives/server/get-isos?page=1&itemsPerPage=30`)
|
this.http.get(`${this.baseUrl}/og-lives/server/get-isos?page=1&itemsPerPage=30`)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (response: any) => {
|
next: (response: any) => {
|
||||||
this.downloads = response.data.downloads;
|
this.downloads = response.message.downloads.downloads;
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
console.error('Error fetching downloads:', error);
|
console.error('Error fetching downloads:', error);
|
||||||
|
@ -53,7 +53,7 @@ export class CreatePXEImageComponent implements OnInit {
|
||||||
submitForm(): void {
|
submitForm(): void {
|
||||||
const payload = {
|
const payload = {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
downloadUrl: this.selectedDownload.URL
|
downloadUrl: this.selectedDownload.filename
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.isEditMode && this.imageId) {
|
if (this.isEditMode && this.imageId) {
|
||||||
|
|
|
@ -93,12 +93,12 @@ table {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.example-button-row {
|
.button-row {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.example-button-row .mat-mdc-button-base {
|
.button-row .mat-mdc-button-base {
|
||||||
margin: 8px 8px 8px 0;
|
margin: 8px 8px 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
<mat-accordion class="example-headers-align">
|
<mat-accordion class="example-headers-align">
|
||||||
<mat-expansion-panel hideToggle>
|
<mat-expansion-panel hideToggle>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title> Sincronización ogBoot </mat-panel-title>
|
<mat-panel-title> Información en servidor ogBoot </mat-panel-title>
|
||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
<p *ngIf="alertMessage">Oglives creados en servidor ogBoot: {{ alertMessage }}</p>
|
|
||||||
<p *ngIf="alertMessage">Oglives creados en servidor ogCore (base de datos): {{ length }}</p>
|
|
||||||
|
|
||||||
<div class="example-button-row">
|
<div class="button-row">
|
||||||
<button mat-flat-button color="primary" (click)="syncOgBoot()"> Sincronizar OgCore</button>
|
<button mat-flat-button color="primary" (click)="syncOgBoot()"> Sincronizar base de datos</button>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()">Ver Información</button>
|
||||||
</div>
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {ToastrService} from "ngx-toastr";
|
||||||
import { DatePipe } from "@angular/common";
|
import { DatePipe } from "@angular/common";
|
||||||
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
|
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
|
||||||
import {DataService} from "./data.service";
|
import {DataService} from "./data.service";
|
||||||
|
import {ServerInfoDialogComponent} from "../../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pxe-images',
|
selector: 'app-pxe-images',
|
||||||
|
@ -193,12 +194,23 @@ export class PXEimagesComponent implements OnInit {
|
||||||
this.http.get(`${this.apiUrl}/server/get-collection`)
|
this.http.get(`${this.apiUrl}/server/get-collection`)
|
||||||
.subscribe(response => {
|
.subscribe(response => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.alertMessage = response.installed_ogLives.length
|
this.alertMessage = response.message
|
||||||
}, error => {
|
}, error => {
|
||||||
console.error('Error al cargar la información del alert', error);
|
console.error('Error al cargar la información del alert', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openSubnetInfoDialog() {
|
||||||
|
this.loadAlert()
|
||||||
|
this.dialog.open(ServerInfoDialogComponent, {
|
||||||
|
width: '600px',
|
||||||
|
data: {
|
||||||
|
alertMessage: this.alertMessage,
|
||||||
|
length: this.length
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
syncOgBoot(): void {
|
syncOgBoot(): void {
|
||||||
this.http.post(`${this.apiUrl}/sync`, {})
|
this.http.post(`${this.apiUrl}/sync`, {})
|
||||||
.subscribe(response => {
|
.subscribe(response => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="dashboard">
|
<div class="dashboard">
|
||||||
<h2>OgBoot server Status</h2>
|
<h2>OgDhcp server Status</h2>
|
||||||
|
|
||||||
<div class="disk-usage-container">
|
<div class="disk-usage-container">
|
||||||
<div class="disk-usage">
|
<div class="disk-usage">
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.form-container {
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.additional-form {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 15px 0;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-fields {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px; /* Espacio entre los campos */
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-field {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start; /* Alinea el contenido al inicio */
|
||||||
|
justify-content: space-between; /* Espacio entre los textos y los íconos */
|
||||||
|
width: 100%; /* Asegúrate de que el contenido ocupe todo el ancho */
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-content {
|
||||||
|
flex-grow: 1; /* Permite que este contenedor ocupe el espacio disponible */
|
||||||
|
margin-right: 16px; /* Espaciado a la derecha para separar de los íconos */
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; /* Alinea los íconos verticalmente */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-icon {
|
||||||
|
margin-left: 8px; /* Espaciado entre los íconos */
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<h2 mat-dialog-title>{{ operativeSystemId ? 'Editar' : 'Crear' }} sistema operativo</h2>
|
||||||
|
<mat-dialog-content class="form-container">
|
||||||
|
<form [formGroup]="formGroup" (ngSubmit)="onSubmit()" class="command-form">
|
||||||
|
<mat-form-field appearance="fill" class="full-width">
|
||||||
|
<mat-label>Nombre</mat-label>
|
||||||
|
<input matInput formControlName="name" placeholder="Nombre del software" required>
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
<button mat-button (click)="onCancel($event)">Cancelar</button>
|
||||||
|
<button mat-button (click)="onSubmit()" cdkFocusInitial> Guardar </button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CreateOperativeSystemComponent } from './create-operative-system.component';
|
||||||
|
|
||||||
|
describe('CreateOperativeSystemComponent', () => {
|
||||||
|
let component: CreateOperativeSystemComponent;
|
||||||
|
let fixture: ComponentFixture<CreateOperativeSystemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [CreateOperativeSystemComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CreateOperativeSystemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,90 @@
|
||||||
|
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 "../../software/data.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-create-operative-system',
|
||||||
|
templateUrl: './create-operative-system.component.html',
|
||||||
|
styleUrl: './create-operative-system.component.css'
|
||||||
|
})
|
||||||
|
export class CreateOperativeSystemComponent {
|
||||||
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||||
|
formGroup: FormGroup<any>;
|
||||||
|
private apiUrl = `${this.baseUrl}/software`;
|
||||||
|
operativeSystemId: string | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private http: HttpClient,
|
||||||
|
public dialogRef: MatDialogRef<CreateOperativeSystemComponent>,
|
||||||
|
private toastService: ToastrService,
|
||||||
|
private dataService: DataService,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: any
|
||||||
|
) {
|
||||||
|
this.formGroup = this.fb.group({
|
||||||
|
name: ['', Validators.required],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.data) {
|
||||||
|
this.load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load(): void {
|
||||||
|
this.dataService.getSoftware(this.data).subscribe({
|
||||||
|
next: (response) => {
|
||||||
|
console.log(response);
|
||||||
|
this.formGroup = this.fb.group({
|
||||||
|
name: [response.name, Validators.required]
|
||||||
|
});
|
||||||
|
this.operativeSystemId = response['@id'];
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Error fetching software:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(event: Event): void {
|
||||||
|
event.preventDefault();
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.formGroup.valid) {
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: this.formGroup.value.name
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.operativeSystemId) {
|
||||||
|
this.http.put(`${this.baseUrl}${this.operativeSystemId}`, payload).subscribe(
|
||||||
|
(response) => {
|
||||||
|
this.toastService.success('Sistema operativo editado correctamente');
|
||||||
|
this.dialogRef.close();
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.toastService.error(error['error']['hydra:description']);
|
||||||
|
console.error('Error al editar el sistema operativo', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.http.post(`${this.baseUrl}/operative-systems`, payload).subscribe(
|
||||||
|
(response) => {
|
||||||
|
this.toastService.success('Sistema operativo añadido correctamente');
|
||||||
|
this.dialogRef.close();
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.toastService.error(error['error']['hydra:description']);
|
||||||
|
console.error('Error al añadir sistema operativo', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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,44 @@
|
||||||
|
<div class="header-container">
|
||||||
|
<h2 class="title" i18n="@@adminImagesTitle">Administrar sistemas operativos</h2>
|
||||||
|
<div class="calendar-button-row">
|
||||||
|
<button mat-flat-button color="primary" (click)="addSoftware()">Añadir sistema operativo</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<mat-divider class="divider"></mat-divider>
|
||||||
|
<div class="search-container">
|
||||||
|
<mat-form-field appearance="fill" class="search-string">
|
||||||
|
<mat-label i18n="@@searchLabel">Buscar nombre de sistema operativo</mat-label>
|
||||||
|
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||||
|
<mat-icon matSuffix>search</mat-icon>
|
||||||
|
<mat-hint i18n="@@searchHint">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>
|
||||||
|
{{ 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 calendar" style="text-align: center;">
|
||||||
|
<button mat-icon-button color="primary" (click)="editSoftware(calendar)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||||
|
<button mat-icon-button color="warn" (click)="deleteSoftware(calendar)" i18n="@@buttonDelete"><mat-icon>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]="pageSizeOptions"
|
||||||
|
(page)="onPageChange($event)">
|
||||||
|
</mat-paginator>
|
||||||
|
</div>
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { OperativeSystemComponent } from './operative-system.component';
|
||||||
|
|
||||||
|
describe('OperativeSystemComponent', () => {
|
||||||
|
let component: OperativeSystemComponent;
|
||||||
|
let fixture: ComponentFixture<OperativeSystemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [OperativeSystemComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(OperativeSystemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,127 @@
|
||||||
|
import {Component, signal} 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 {DataService} from "../software/data.service";
|
||||||
|
import {ToastrService} from "ngx-toastr";
|
||||||
|
import {CreateSoftwareComponent} from "../software/create-software/create-software.component";
|
||||||
|
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||||
|
import {PageEvent} from "@angular/material/paginator";
|
||||||
|
import {CreateOperativeSystemComponent} from "./create-operative-system/create-operative-system.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-operative-system',
|
||||||
|
templateUrl: './operative-system.component.html',
|
||||||
|
styleUrl: './operative-system.component.css'
|
||||||
|
})
|
||||||
|
export class OperativeSystemComponent {
|
||||||
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||||
|
images: { downloadUrl: string; name: string; uuid: string }[] = [];
|
||||||
|
dataSource = new MatTableDataSource<any>();
|
||||||
|
length: number = 0;
|
||||||
|
itemsPerPage: number = 10;
|
||||||
|
page: number = 0;
|
||||||
|
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
|
||||||
|
loading:boolean = false;
|
||||||
|
filters: { [key: string]: string } = {};
|
||||||
|
alertMessage: string | null = null;
|
||||||
|
readonly panelOpenState = signal(false);
|
||||||
|
datePipe: DatePipe = new DatePipe('es-ES');
|
||||||
|
columns = [
|
||||||
|
{
|
||||||
|
columnDef: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: (operativeSystem: any) => `${operativeSystem.id}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnDef: 'name',
|
||||||
|
header: 'Nombre',
|
||||||
|
cell: (operativeSystem: any) => `${operativeSystem.name}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnDef: 'createdAt',
|
||||||
|
header: 'Fecha de creación',
|
||||||
|
cell: (operativeSystem: any) => `${this.datePipe.transform(operativeSystem.createdAt, 'dd/MM/yyyy hh:mm:ss')}`,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
|
||||||
|
|
||||||
|
private apiUrl = `${this.baseUrl}/operative-systems`;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialog: MatDialog,
|
||||||
|
private http: HttpClient,
|
||||||
|
private dataService: DataService,
|
||||||
|
private toastService: ToastrService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
|
||||||
|
addSoftware(): void {
|
||||||
|
const dialogRef = this.dialog.open(CreateOperativeSystemComponent, {
|
||||||
|
width: '600px'
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
this.search();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
search(): void {
|
||||||
|
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'];
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching commands', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
editSoftware(calendar: any): void {
|
||||||
|
const dialogRef = this.dialog.open(CreateOperativeSystemComponent, {
|
||||||
|
width: '600px',
|
||||||
|
data: calendar['@id']
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
this.search();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSoftware(operativeSystem: any): void {
|
||||||
|
const dialogRef = this.dialog.open(DeleteModalComponent, {
|
||||||
|
width: '400px',
|
||||||
|
data: { name: operativeSystem.name }
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
const apiUrl = `${this.baseUrl}${operativeSystem['@id']}`;
|
||||||
|
|
||||||
|
this.http.delete(apiUrl).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.search();
|
||||||
|
this.toastService.success('Operative System deleted');
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
this.toastService.error('Error deleting operative system');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('calendar deletion cancelled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageChange(event: PageEvent) {
|
||||||
|
this.page = event.pageIndex;
|
||||||
|
this.itemsPerPage = event.pageSize;
|
||||||
|
this.length = event.length;
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.form-container {
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.additional-form {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 15px 0;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-fields {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px; /* Espacio entre los campos */
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-field {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start; /* Alinea el contenido al inicio */
|
||||||
|
justify-content: space-between; /* Espacio entre los textos y los íconos */
|
||||||
|
width: 100%; /* Asegúrate de que el contenido ocupe todo el ancho */
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-content {
|
||||||
|
flex-grow: 1; /* Permite que este contenedor ocupe el espacio disponible */
|
||||||
|
margin-right: 16px; /* Espaciado a la derecha para separar de los íconos */
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; /* Alinea los íconos verticalmente */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-icon {
|
||||||
|
margin-left: 8px; /* Espaciado entre los íconos */
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
<h2 mat-dialog-title>{{ softwareProfileId ? 'Editar' : 'Crear' }} perfil de software</h2>
|
||||||
|
|
||||||
|
<mat-dialog-content class="form-container">
|
||||||
|
<mat-tab-group>
|
||||||
|
<!-- Primer tab: formulario de comandos -->
|
||||||
|
<mat-tab label="Formulario">
|
||||||
|
<form [formGroup]="formGroup" (ngSubmit)="onSubmit()" class="form-group">
|
||||||
|
<mat-form-field appearance="fill" class="full-width" >
|
||||||
|
<mat-label>Descripción</mat-label>
|
||||||
|
<input matInput formControlName="description" placeholder="Descripción del comando" required>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="fill" class="full-width">
|
||||||
|
<mat-label>Comentarios</mat-label>
|
||||||
|
<input matInput formControlName="comments" placeholder="Comentarios del comando">
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="fill" class="full-width">
|
||||||
|
<mat-label>Seleccionar unidad organizativa</mat-label>
|
||||||
|
<mat-select formControlName="organizationalUnit" required>
|
||||||
|
<mat-option *ngFor="let ou of organizationalUnits" [value]="ou['@id']">
|
||||||
|
{{ ou.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="fill" class="full-width">
|
||||||
|
<mat-label>Seleccionar sistema operativo</mat-label>
|
||||||
|
<mat-select formControlName="operativeSystem" >
|
||||||
|
<mat-option *ngFor="let op of operativeSystems" [value]="op['@id']">
|
||||||
|
{{ op.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
</mat-tab>
|
||||||
|
|
||||||
|
<mat-tab label="Seleccionar Software">
|
||||||
|
<div class="form-group">
|
||||||
|
<mat-form-field appearance="fill" class="full-width">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
matInput
|
||||||
|
[formControl]="softwareControl"
|
||||||
|
[matAutocomplete]="clientAuto"
|
||||||
|
placeholder="Seleccione software para añadir">
|
||||||
|
|
||||||
|
<mat-autocomplete
|
||||||
|
#clientAuto="matAutocomplete"
|
||||||
|
[displayWith]="displayFnClient"
|
||||||
|
(optionSelected)="onOptionClientSelected($event.option.value)">
|
||||||
|
|
||||||
|
<mat-option *ngFor="let software of filteredSoftware | async" [value]="software">
|
||||||
|
{{ software.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-autocomplete>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div class="software-list">
|
||||||
|
<mat-list>
|
||||||
|
<mat-list-item *ngFor="let software of selectedSoftwares">
|
||||||
|
{{ software.name }}
|
||||||
|
<button mat-icon-button (click)="removeSoftware(software)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
<button mat-button (click)="onCancel($event)">Cancelar</button>
|
||||||
|
<button mat-button (click)="onSubmit()" cdkFocusInitial>Guardar</button>
|
||||||
|
</mat-dialog-actions>
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CreateSoftwareProfileComponent } from './create-software-profile.component';
|
||||||
|
|
||||||
|
describe('CreateSoftwareProfileComponent', () => {
|
||||||
|
let component: CreateSoftwareProfileComponent;
|
||||||
|
let fixture: ComponentFixture<CreateSoftwareProfileComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [CreateSoftwareProfileComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CreateSoftwareProfileComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,182 @@
|
||||||
|
import {Component, Inject} from '@angular/core';
|
||||||
|
import {FormBuilder, FormControl, 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 as SoftwareService} from "../../software/data.service";
|
||||||
|
import {DataService} from "../data.service";
|
||||||
|
import {Observable, startWith} from "rxjs";
|
||||||
|
import { debounceTime, switchMap, map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-create-software-profile',
|
||||||
|
templateUrl: './create-software-profile.component.html',
|
||||||
|
styleUrl: './create-software-profile.component.css'
|
||||||
|
})
|
||||||
|
export class CreateSoftwareProfileComponent {
|
||||||
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||||
|
formGroup: FormGroup<any>;
|
||||||
|
private apiUrl = `${this.baseUrl}/software-profiles`;
|
||||||
|
softwareCollection: any[] = [];
|
||||||
|
organizationalUnits: any[] = [];
|
||||||
|
operativeSystems: any[] = [];
|
||||||
|
softwareProfileId: string | null = null;
|
||||||
|
selectedSoftware: any;
|
||||||
|
selectedSoftwares: any[] = [];
|
||||||
|
softwareControl = new FormControl();
|
||||||
|
filteredSoftware!: Observable<any[]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private http: HttpClient,
|
||||||
|
public dialogRef: MatDialogRef<CreateSoftwareProfileComponent>,
|
||||||
|
private toastService: ToastrService,
|
||||||
|
private softwareDataService: SoftwareService,
|
||||||
|
private dataService: DataService,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: any
|
||||||
|
) {
|
||||||
|
this.formGroup = this.fb.group({
|
||||||
|
description: [''],
|
||||||
|
comments: [''],
|
||||||
|
organizationalUnit: [null, Validators.required],
|
||||||
|
operativeSystem: [null]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.data) {
|
||||||
|
this.load()
|
||||||
|
}
|
||||||
|
this.loadSoftware();
|
||||||
|
this.loadOrganizationalUnits()
|
||||||
|
this.loadOperativeSystems()
|
||||||
|
|
||||||
|
this.filteredSoftware = this.softwareControl.valueChanges.pipe(
|
||||||
|
startWith(''),
|
||||||
|
debounceTime(300),
|
||||||
|
switchMap(value => this._filterSoftware(value))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSoftware() {
|
||||||
|
this.http.get<any>( `${this.baseUrl}/software?&page=1&itemsPerPage=10`).subscribe(
|
||||||
|
response => {
|
||||||
|
this.softwareCollection = response['hydra:member'];
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error('Error fetching parent units:', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadOrganizationalUnits() {
|
||||||
|
this.http.get<any>( `${this.baseUrl}/organizational-units?&page=1&itemsPerPage=10000`).subscribe(
|
||||||
|
response => {
|
||||||
|
this.organizationalUnits = response['hydra:member'];
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error('Error fetching parent units:', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadOperativeSystems() {
|
||||||
|
this.http.get<any>( `${this.baseUrl}/operative-systems?&page=1&itemsPerPage=10000`).subscribe(
|
||||||
|
response => {
|
||||||
|
this.operativeSystems = response['hydra:member'];
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error('Error fetching parent units:', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterSoftware(value: string): Observable<any[]> {
|
||||||
|
|
||||||
|
return this.softwareDataService.getSoftwareCollection({ 'name': value}).pipe(
|
||||||
|
map(response => response || [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
displayFnClient(client: any): string {
|
||||||
|
return client && client.name ? client.name : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
onOptionClientSelected(software: any) {
|
||||||
|
if (!this.selectedSoftwares.find(s => s.id === software.id)) {
|
||||||
|
this.selectedSoftwares.push(software);
|
||||||
|
}
|
||||||
|
this.softwareControl.setValue('');
|
||||||
|
}
|
||||||
|
|
||||||
|
load(): void {
|
||||||
|
console.log(this.data);
|
||||||
|
this.dataService.getSoftwareProfile(this.data).subscribe({
|
||||||
|
next: (response) => {
|
||||||
|
this.formGroup = this.fb.group({
|
||||||
|
description: [response.description],
|
||||||
|
comments: [response.comments],
|
||||||
|
organizationalUnit: [response.organizationalUnit ? response.organizationalUnit['@id'] : null],
|
||||||
|
operativeSystem: [response.operativeSystem ? response.operativeSystem['@id'] : null],
|
||||||
|
});
|
||||||
|
this.softwareProfileId = response['@id'];
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Error fetching software:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addSoftware() {
|
||||||
|
const software = this.softwareCollection.find(s => s.id === this.selectedSoftware);
|
||||||
|
if (software && !this.selectedSoftwares.includes(software)) {
|
||||||
|
this.selectedSoftwares.push(software);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSoftware(software: any) {
|
||||||
|
this.selectedSoftwares = this.selectedSoftwares.filter(s => s.id !== software.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(event: Event): void {
|
||||||
|
event.preventDefault();
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.formGroup.valid) {
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
description: this.formGroup.value.description,
|
||||||
|
comments: this.formGroup.value.comments,
|
||||||
|
softwareCollection: this.selectedSoftwares.map(s => s['@id']),
|
||||||
|
organizationalUnit: this.formGroup.value.organizationalUnit,
|
||||||
|
operativeSystem: this.formGroup.value.operativeSystem
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.softwareProfileId) {
|
||||||
|
this.http.put(`${this.baseUrl}${this.softwareProfileId}`, payload).subscribe(
|
||||||
|
(response) => {
|
||||||
|
this.toastService.success('Software editado correctamente');
|
||||||
|
this.dialogRef.close();
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.toastService.error(error['error']['hydra:description']);
|
||||||
|
console.error('Error al editar el comando', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.http.post(`${this.baseUrl}/software-profiles`, payload).subscribe(
|
||||||
|
(response) => {
|
||||||
|
this.toastService.success('Software añadido correctamente');
|
||||||
|
this.dialogRef.close();
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.toastService.error(error['error']['hydra:description']);
|
||||||
|
console.error('Error al añadir comando', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {HttpClient, HttpParams} from '@angular/common/http';
|
||||||
|
import { Observable, throwError } from 'rxjs';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class DataService {
|
||||||
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||||
|
private apiUrl = `${this.baseUrl}/software-profiles`;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
getSoftwareProfiles(filters: { [key: string]: string }): Observable<any[]> {
|
||||||
|
const params = new HttpParams({ fromObject: filters });
|
||||||
|
|
||||||
|
return this.http.get<any>(this.apiUrl, { params }).pipe(
|
||||||
|
map(response => {
|
||||||
|
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
|
||||||
|
return response['hydra:member'];
|
||||||
|
} else {
|
||||||
|
throw new Error('Unexpected response format');
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(error => {
|
||||||
|
console.error('Error fetching remote calendars', error);
|
||||||
|
return throwError(error);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSoftwareProfile(id: string): Observable<any> {
|
||||||
|
console.log(id)
|
||||||
|
return this.http.get<any>(`${this.baseUrl}${id}`).pipe(
|
||||||
|
map(response => {
|
||||||
|
if (response.description) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
throw new Error('Unexpected response format');
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(error => {
|
||||||
|
console.error('Error fetching calendar', error);
|
||||||
|
return throwError(error);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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,50 @@
|
||||||
|
<div class="header-container">
|
||||||
|
<h2 class="title" i18n="@@adminImagesTitle">Administrar perfiles software</h2>
|
||||||
|
<div class="calendar-button-row">
|
||||||
|
<button mat-flat-button color="primary" (click)="addSoftware()">Añadir perfil software</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<mat-divider class="divider"></mat-divider>
|
||||||
|
<div class="search-container">
|
||||||
|
<mat-form-field appearance="fill" class="search-string">
|
||||||
|
<mat-label i18n="@@searchLabel">Buscar nombre de perfil</mat-label>
|
||||||
|
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['description']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||||
|
<mat-icon matSuffix>search</mat-icon>
|
||||||
|
<mat-hint i18n="@@searchHint">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 === 'isDefault' || column.columnDef === 'installed'">
|
||||||
|
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||||
|
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||||
|
</mat-icon>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'">
|
||||||
|
{{ 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 calendar" style="text-align: center;">
|
||||||
|
<button mat-icon-button color="primary" (click)="editSoftware(calendar)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||||
|
<button mat-icon-button color="warn" (click)="deleteSoftware(calendar)" i18n="@@buttonDelete"><mat-icon>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]="pageSizeOptions"
|
||||||
|
(page)="onPageChange($event)">
|
||||||
|
</mat-paginator>
|
||||||
|
</div>
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SoftwareProfileComponent } from './software-profile.component';
|
||||||
|
|
||||||
|
describe('SoftwareProfileComponent', () => {
|
||||||
|
let component: SoftwareProfileComponent;
|
||||||
|
let fixture: ComponentFixture<SoftwareProfileComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [SoftwareProfileComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SoftwareProfileComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,132 @@
|
||||||
|
import {Component, signal} 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 {DataService} from "../software/data.service";
|
||||||
|
import {ToastrService} from "ngx-toastr";
|
||||||
|
import {CreateSoftwareComponent} from "../software/create-software/create-software.component";
|
||||||
|
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||||
|
import {PageEvent} from "@angular/material/paginator";
|
||||||
|
import {CreateSoftwareProfileComponent} from "./create-software-profile/create-software-profile.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-software-profile',
|
||||||
|
templateUrl: './software-profile.component.html',
|
||||||
|
styleUrl: './software-profile.component.css'
|
||||||
|
})
|
||||||
|
export class SoftwareProfileComponent {
|
||||||
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||||
|
images: { downloadUrl: string; name: string; uuid: string }[] = [];
|
||||||
|
dataSource = new MatTableDataSource<any>();
|
||||||
|
length: number = 0;
|
||||||
|
itemsPerPage: number = 10;
|
||||||
|
page: number = 0;
|
||||||
|
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
|
||||||
|
loading:boolean = false;
|
||||||
|
filters: { [key: string]: string } = {};
|
||||||
|
alertMessage: string | null = null;
|
||||||
|
readonly panelOpenState = signal(false);
|
||||||
|
datePipe: DatePipe = new DatePipe('es-ES');
|
||||||
|
columns = [
|
||||||
|
{
|
||||||
|
columnDef: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: (software: any) => `${software.id}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnDef: 'description',
|
||||||
|
header: 'Descripción',
|
||||||
|
cell: (software: any) => `${software.description}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnDef: 'operativeSystem',
|
||||||
|
header: 'Sistema Operativo',
|
||||||
|
cell: (software: any) => `${software.operativeSystem?.name}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnDef: 'createdAt',
|
||||||
|
header: 'Fecha de creación',
|
||||||
|
cell: (software: any) => `${this.datePipe.transform(software.createdAt, 'dd/MM/yyyy hh:mm:ss')}`,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
|
||||||
|
|
||||||
|
private apiUrl = `${this.baseUrl}/software-profiles`;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialog: MatDialog,
|
||||||
|
private http: HttpClient,
|
||||||
|
private dataService: DataService,
|
||||||
|
private toastService: ToastrService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
|
||||||
|
addSoftware(): void {
|
||||||
|
const dialogRef = this.dialog.open(CreateSoftwareProfileComponent, {
|
||||||
|
width: '600px'
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
this.search();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
search(): void {
|
||||||
|
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'];
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching commands', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
editSoftware(softwareProfile: any): void {
|
||||||
|
const dialogRef = this.dialog.open(CreateSoftwareProfileComponent, {
|
||||||
|
width: '600px',
|
||||||
|
data: softwareProfile['@id']
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
this.search();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSoftware(calendar: any): void {
|
||||||
|
const dialogRef = this.dialog.open(DeleteModalComponent, {
|
||||||
|
width: '400px',
|
||||||
|
data: { name: calendar.name }
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
const apiUrl = `${this.baseUrl}${calendar['@id']}`;
|
||||||
|
|
||||||
|
this.http.delete(apiUrl).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.search();
|
||||||
|
this.toastService.success('Software deleted successfully');
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
this.toastService.error('Error deleting calendar');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('calendar deletion cancelled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageChange(event: PageEvent) {
|
||||||
|
this.page = event.pageIndex;
|
||||||
|
this.itemsPerPage = event.pageSize;
|
||||||
|
this.length = event.length;
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.form-container {
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.additional-form {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 15px 0;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-fields {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px; /* Espacio entre los campos */
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-field {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start; /* Alinea el contenido al inicio */
|
||||||
|
justify-content: space-between; /* Espacio entre los textos y los íconos */
|
||||||
|
width: 100%; /* Asegúrate de que el contenido ocupe todo el ancho */
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-content {
|
||||||
|
flex-grow: 1; /* Permite que este contenedor ocupe el espacio disponible */
|
||||||
|
margin-right: 16px; /* Espaciado a la derecha para separar de los íconos */
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; /* Alinea los íconos verticalmente */
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-icon {
|
||||||
|
margin-left: 8px; /* Espaciado entre los íconos */
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<h2 mat-dialog-title>{{ softwareId ? 'Editar' : 'Crear' }} software</h2>
|
||||||
|
<mat-dialog-content class="form-container">
|
||||||
|
<form [formGroup]="formGroup" (ngSubmit)="onSubmit()" class="command-form">
|
||||||
|
<mat-form-field appearance="fill" class="full-width">
|
||||||
|
<mat-label>Nombre</mat-label>
|
||||||
|
<input matInput formControlName="name" placeholder="Nombre del software" required>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="fill" class="full-width">
|
||||||
|
<mat-label>Descripción</mat-label>
|
||||||
|
<input matInput formControlName="description" placeholder="Descripción del software">
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="fill" class="full-width">
|
||||||
|
<mat-label>Tipo</mat-label>
|
||||||
|
<mat-select formControlName="type">
|
||||||
|
<mat-option value="application" i18n="@@organizationalUnitsOption">Aplicacion</mat-option>
|
||||||
|
<mat-option value="operative-system" i18n="@@clientsOption">Sistema operativo</mat-option>
|
||||||
|
<mat-option value="file" i18n="@@clientsOption">Fichero</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
<button mat-button (click)="onCancel($event)">Cancelar</button>
|
||||||
|
<button mat-button (click)="onSubmit()" cdkFocusInitial> Guardar </button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CreateSoftwareComponent } from './create-software.component';
|
||||||
|
|
||||||
|
describe('CreateSoftwareComponent', () => {
|
||||||
|
let component: CreateSoftwareComponent;
|
||||||
|
let fixture: ComponentFixture<CreateSoftwareComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [CreateSoftwareComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CreateSoftwareComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,96 @@
|
||||||
|
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 "../data.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-create-software',
|
||||||
|
templateUrl: './create-software.component.html',
|
||||||
|
styleUrl: './create-software.component.css'
|
||||||
|
})
|
||||||
|
export class CreateSoftwareComponent {
|
||||||
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||||
|
formGroup: FormGroup<any>;
|
||||||
|
private apiUrl = `${this.baseUrl}/software`;
|
||||||
|
softwareId: string | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private http: HttpClient,
|
||||||
|
public dialogRef: MatDialogRef<CreateSoftwareComponent>,
|
||||||
|
private toastService: ToastrService,
|
||||||
|
private dataService: DataService,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: any
|
||||||
|
) {
|
||||||
|
this.formGroup = this.fb.group({
|
||||||
|
name: ['', Validators.required],
|
||||||
|
description: [''],
|
||||||
|
type: [''],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.data) {
|
||||||
|
this.load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load(): void {
|
||||||
|
this.dataService.getSoftware(this.data).subscribe({
|
||||||
|
next: (response) => {
|
||||||
|
console.log(response);
|
||||||
|
this.formGroup = this.fb.group({
|
||||||
|
name: [response.name, Validators.required],
|
||||||
|
description: [response.description],
|
||||||
|
type: [response.type],
|
||||||
|
});
|
||||||
|
this.softwareId = response['@id'];
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Error fetching software:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(event: Event): void {
|
||||||
|
event.preventDefault();
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.formGroup.valid) {
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: this.formGroup.value.name,
|
||||||
|
description: this.formGroup.value.description,
|
||||||
|
type: this.formGroup.value.type,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.softwareId) {
|
||||||
|
this.http.put(`${this.baseUrl}${this.softwareId}`, payload).subscribe(
|
||||||
|
(response) => {
|
||||||
|
this.toastService.success('Software editado correctamente');
|
||||||
|
this.dialogRef.close();
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.toastService.error(error['error']['hydra:description']);
|
||||||
|
console.error('Error al editar el comando', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.http.post(`${this.baseUrl}/software`, payload).subscribe(
|
||||||
|
(response) => {
|
||||||
|
this.toastService.success('Software añadido correctamente');
|
||||||
|
this.dialogRef.close();
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.toastService.error(error['error']['hydra:description']);
|
||||||
|
console.error('Error al añadir comando', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {HttpClient, HttpParams} from '@angular/common/http';
|
||||||
|
import { Observable, throwError } from 'rxjs';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class DataService {
|
||||||
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||||
|
private apiUrl = `${this.baseUrl}/software`;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
getSoftwareCollection(filters: { [key: string]: string }): Observable<any[]> {
|
||||||
|
const params = new HttpParams({ fromObject: filters });
|
||||||
|
|
||||||
|
return this.http.get<any>(this.apiUrl, { params }).pipe(
|
||||||
|
map(response => {
|
||||||
|
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
|
||||||
|
return response['hydra:member'];
|
||||||
|
} else {
|
||||||
|
throw new Error('Unexpected response format');
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(error => {
|
||||||
|
console.error('Error fetching remote calendars', error);
|
||||||
|
return throwError(error);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSoftware(id: string): Observable<any> {
|
||||||
|
return this.http.get<any>(`${this.baseUrl}${id}`).pipe(
|
||||||
|
map(response => {
|
||||||
|
if (response.name) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
throw new Error('Unexpected response format');
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(error => {
|
||||||
|
console.error('Error fetching calendar', error);
|
||||||
|
return throwError(error);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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,58 @@
|
||||||
|
<div class="header-container">
|
||||||
|
<h2 class="title" i18n="@@adminImagesTitle">Administrar Software</h2>
|
||||||
|
<div class="calendar-button-row">
|
||||||
|
<button mat-flat-button color="primary" (click)="addSoftware()">Añadir software</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<mat-divider class="divider"></mat-divider>
|
||||||
|
<div class="search-container">
|
||||||
|
<mat-form-field appearance="fill" class="search-string">
|
||||||
|
<mat-label i18n="@@searchLabel">Buscar nombre de software</mat-label>
|
||||||
|
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||||
|
<mat-icon matSuffix>search</mat-icon>
|
||||||
|
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="fill" class="search-boolean">
|
||||||
|
<mat-label i18n="@@searchLabel">Buscar por tipo</mat-label>
|
||||||
|
<mat-select [(ngModel)]="filters['type']" (selectionChange)="search()" placeholder="Seleccionar opción">
|
||||||
|
<mat-option [value]="'application'">Aplicación</mat-option>
|
||||||
|
<mat-option [value]="'operative-system'">Sistema operativo</mat-option>
|
||||||
|
<mat-option [value]="'file'">Sistema de ficheros</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</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 === 'isDefault' || column.columnDef === 'installed'">
|
||||||
|
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||||
|
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||||
|
</mat-icon>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'">
|
||||||
|
{{ 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 calendar" style="text-align: center;">
|
||||||
|
<button mat-icon-button color="primary" (click)="editSoftware(calendar)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||||
|
<button mat-icon-button color="warn" (click)="deleteSoftware(calendar)" i18n="@@buttonDelete"><mat-icon>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]="pageSizeOptions"
|
||||||
|
(page)="onPageChange($event)">
|
||||||
|
</mat-paginator>
|
||||||
|
</div>
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SoftwareComponent } from './software.component';
|
||||||
|
|
||||||
|
describe('SoftwareComponent', () => {
|
||||||
|
let component: SoftwareComponent;
|
||||||
|
let fixture: ComponentFixture<SoftwareComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [SoftwareComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SoftwareComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,137 @@
|
||||||
|
import {Component, signal} 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 {DataService} from "./data.service";
|
||||||
|
import {ToastrService} from "ngx-toastr";
|
||||||
|
import {CreateCalendarComponent} from "../calendar/create-calendar/create-calendar.component";
|
||||||
|
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||||
|
import {PageEvent} from "@angular/material/paginator";
|
||||||
|
import {CreateSoftwareComponent} from "./create-software/create-software.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-software',
|
||||||
|
templateUrl: './software.component.html',
|
||||||
|
styleUrl: './software.component.css'
|
||||||
|
})
|
||||||
|
export class SoftwareComponent {
|
||||||
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||||
|
images: { downloadUrl: string; name: string; uuid: string }[] = [];
|
||||||
|
dataSource = new MatTableDataSource<any>();
|
||||||
|
length: number = 0;
|
||||||
|
itemsPerPage: number = 10;
|
||||||
|
page: number = 0;
|
||||||
|
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
|
||||||
|
loading:boolean = false;
|
||||||
|
filters: { [key: string]: string } = {};
|
||||||
|
alertMessage: string | null = null;
|
||||||
|
readonly panelOpenState = signal(false);
|
||||||
|
datePipe: DatePipe = new DatePipe('es-ES');
|
||||||
|
columns = [
|
||||||
|
{
|
||||||
|
columnDef: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: (software: any) => `${software.id}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnDef: 'name',
|
||||||
|
header: 'Nombre',
|
||||||
|
cell: (software: any) => `${software.name}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnDef: 'description',
|
||||||
|
header: 'Descripción',
|
||||||
|
cell: (software: any) => `${software.description}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnDef: 'type',
|
||||||
|
header: 'Tipo',
|
||||||
|
cell: (software: any) => `${software.type}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnDef: 'createdAt',
|
||||||
|
header: 'Fecha de creación',
|
||||||
|
cell: (software: any) => `${this.datePipe.transform(software.createdAt, 'dd/MM/yyyy hh:mm:ss')}`,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
|
||||||
|
|
||||||
|
private apiUrl = `${this.baseUrl}/software`;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialog: MatDialog,
|
||||||
|
private http: HttpClient,
|
||||||
|
private dataService: DataService,
|
||||||
|
private toastService: ToastrService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
|
||||||
|
addSoftware(): void {
|
||||||
|
const dialogRef = this.dialog.open(CreateSoftwareComponent, {
|
||||||
|
width: '600px'
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
this.search();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
search(): void {
|
||||||
|
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'];
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching commands', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
editSoftware(calendar: any): void {
|
||||||
|
const dialogRef = this.dialog.open(CreateSoftwareComponent, {
|
||||||
|
width: '600px',
|
||||||
|
data: calendar['@id']
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
this.search();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSoftware(calendar: any): void {
|
||||||
|
const dialogRef = this.dialog.open(DeleteModalComponent, {
|
||||||
|
width: '400px',
|
||||||
|
data: { name: calendar.name }
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
const apiUrl = `${this.baseUrl}${calendar['@id']}`;
|
||||||
|
|
||||||
|
this.http.delete(apiUrl).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.search();
|
||||||
|
this.toastService.success('Software deleted successfully');
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
this.toastService.error('Error deleting calendar');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('calendar deletion cancelled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageChange(event: PageEvent) {
|
||||||
|
this.page = event.pageIndex;
|
||||||
|
this.itemsPerPage = event.pageSize;
|
||||||
|
this.length = event.length;
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
}
|
|
@ -119,13 +119,35 @@
|
||||||
</span>
|
</span>
|
||||||
</mat-list-item>
|
</mat-list-item>
|
||||||
|
|
||||||
<mat-list-item class="disabled">
|
<mat-list-item (click)="toggleSoftwareSub()">
|
||||||
<span class="entry">
|
<span class="entry">
|
||||||
<mat-icon class="icon">settings_input_component</mat-icon>
|
<mat-icon class="icon">terminal</mat-icon>
|
||||||
<span i18n="@@components">Componentes</span>
|
<span i18n="@@images">Software</span>
|
||||||
</span>
|
</span>
|
||||||
</mat-list-item>
|
</mat-list-item>
|
||||||
|
|
||||||
|
<!-- Submenu items ogdhcp -->
|
||||||
|
<mat-nav-list *ngIf="showSoftwareSub" style="padding-left: 20px;">
|
||||||
|
<mat-list-item routerLink="/software">
|
||||||
|
<span class="entry">
|
||||||
|
<mat-icon class="icon">list</mat-icon>
|
||||||
|
<span i18n="@@gallery">Listado </span>
|
||||||
|
</span>
|
||||||
|
</mat-list-item>
|
||||||
|
<mat-list-item routerLink="/software-profiles">
|
||||||
|
<span class="entry">
|
||||||
|
<mat-icon class="icon">folder_shared</mat-icon>
|
||||||
|
<span i18n="@@gallery">Perfiles</span>
|
||||||
|
</span>
|
||||||
|
</mat-list-item>
|
||||||
|
<mat-list-item routerLink="/operative-systems">
|
||||||
|
<span class="entry">
|
||||||
|
<mat-icon class="icon">terminal</mat-icon>
|
||||||
|
<span i18n="@@gallery">S. Operativos</span>
|
||||||
|
</span>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-nav-list>
|
||||||
|
|
||||||
<mat-list-item class="disabled">
|
<mat-list-item class="disabled">
|
||||||
<span class="entry">
|
<span class="entry">
|
||||||
<mat-icon class="icon">warehouse</mat-icon>
|
<mat-icon class="icon">warehouse</mat-icon>
|
||||||
|
|
|
@ -14,6 +14,7 @@ export class SidebarComponent {
|
||||||
showOgBootSub: boolean = false;
|
showOgBootSub: boolean = false;
|
||||||
showOgDhcpSub: boolean = false;
|
showOgDhcpSub: boolean = false;
|
||||||
showCommandSub: boolean = false;
|
showCommandSub: boolean = false;
|
||||||
|
showSoftwareSub: boolean = false;
|
||||||
|
|
||||||
toggleOgBootSub() {
|
toggleOgBootSub() {
|
||||||
this.showOgBootSub = !this.showOgBootSub;
|
this.showOgBootSub = !this.showOgBootSub;
|
||||||
|
@ -24,6 +25,9 @@ export class SidebarComponent {
|
||||||
toggleCommandSub() {
|
toggleCommandSub() {
|
||||||
this.showCommandSub = !this.showCommandSub;
|
this.showCommandSub = !this.showCommandSub;
|
||||||
}
|
}
|
||||||
|
toggleSoftwareSub() {
|
||||||
|
this.showSoftwareSub = !this.showSoftwareSub;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
constructor(public dialog: MatDialog) {}
|
constructor(public dialog: MatDialog) {}
|
||||||
|
|
Loading…
Reference in New Issue