From 4dfc45b1253273adfa0fb0d912d5593b571115c4 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Fri, 15 Nov 2024 12:57:45 +0100 Subject: [PATCH] refs #1091. CreateImage logic --- ogWebconsole/package-lock.json | 4 +- ogWebconsole/src/app/app-routing.module.ts | 18 +- ogWebconsole/src/app/app.module.ts | 6 +- .../create-calendar-rule.component.html | 2 +- .../task-logs/task-logs.component.css | 17 +- .../task-logs/task-logs.component.html | 35 +- .../task-logs/task-logs.component.ts | 13 +- .../advanced-search.component.css | 28 ++ .../advanced-search.component.html | 13 +- .../client-main-view.component.html | 121 +++---- .../client-main-view.component.ts | 83 ++++- .../create-image/create-image.component.css | 76 +++++ .../create-image/create-image.component.html | 46 +++ .../create-image.component.spec.ts | 23 ++ .../create-image/create-image.component.ts | 168 +++++++++ .../deploy-image/deploy-image.component.css | 84 +++++ .../deploy-image/deploy-image.component.html | 109 ++++++ .../deploy-image.component.spec.ts | 23 ++ .../deploy-image/deploy-image.component.ts | 161 +++++++++ .../partition-assistant.component.html | 61 ++-- .../partition-assistant.component.ts | 35 +- .../restore-image/restore-image.component.css | 71 ---- .../restore-image.component.html | 39 --- .../restore-image/restore-image.component.ts | 95 ------ .../client-tab-view.component.css | 28 ++ .../client-tab-view.component.html | 96 +++--- .../edit-client/edit-client.component.html | 10 +- .../edit-client/edit-client.component.ts | 19 +- .../create-image/create-image.component.css | 8 + .../create-image/create-image.component.html | 16 +- .../create-image/create-image.component.ts | 12 +- .../components/images/images.component.css | 59 ++-- .../components/images/images.component.html | 37 +- .../app/components/images/images.component.ts | 65 +++- .../main-repository-view.component.css | 307 +++++++++++++++++ .../main-repository-view.component.html | 141 ++++++++ .../main-repository-view.component.spec.ts | 23 ++ .../main-repository-view.component.ts | 323 ++++++++++++++++++ .../app/layout/header/header.component.css | 4 + .../app/layout/header/header.component.html | 1 + .../app/layout/sidebar/sidebar.component.html | 6 - 41 files changed, 2035 insertions(+), 451 deletions(-) create mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.css create mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html create mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.spec.ts create mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts create mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.css create mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html create mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts create mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts delete mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.css delete mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.html delete mode 100644 ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.ts create mode 100644 ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.css create mode 100644 ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html create mode 100644 ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.spec.ts create mode 100644 ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts diff --git a/ogWebconsole/package-lock.json b/ogWebconsole/package-lock.json index 05f3952..a0f4544 100644 --- a/ogWebconsole/package-lock.json +++ b/ogWebconsole/package-lock.json @@ -1,12 +1,12 @@ { "name": "og-webconsole", - "version": "0.0.0", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "og-webconsole", - "version": "0.0.0", + "version": "0.5.0", "dependencies": { "@angular/animations": "^18.0.0", "@angular/cdk": "~18.0.0", diff --git a/ogWebconsole/src/app/app-routing.module.ts b/ogWebconsole/src/app/app-routing.module.ts index bca74a7..b015f6a 100644 --- a/ogWebconsole/src/app/app-routing.module.ts +++ b/ogWebconsole/src/app/app-routing.module.ts @@ -23,7 +23,6 @@ import { TaskLogsComponent } from './components/commands/commands-task/task-logs import { StatusComponent } from "./components/ogdhcp/og-dhcp-subnets/status/status.component"; import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component'; import { ImagesComponent } from './components/images/images.component'; -import { RestoreImageComponent } from './components/groups/components/client-main-view/restore-image/restore-image.component'; import {SoftwareComponent} from "./components/software/software.component"; import {SoftwareProfileComponent} from "./components/software-profile/software-profile.component"; import {OperativeSystemComponent} from "./components/operative-system/operative-system.component"; @@ -31,6 +30,15 @@ import { PartitionAssistantComponent } from "./components/groups/components/client-main-view/partition-assistant/partition-assistant.component"; import {RepositoriesComponent} from "./components/repositories/repositories.component"; +import { + CreateImageComponent +} from "./components/groups/components/client-main-view/create-image/create-image.component"; +import { + DeployImageComponent +} from "./components/groups/components/client-main-view/deploy-image/deploy-image.component"; +import { + MainRepositoryViewComponent +} from "./components/repositories/main-repository-view/main-repository-view.component"; const routes: Routes = [ { path: '', redirectTo: 'auth/login', pathMatch: 'full' }, { @@ -54,11 +62,13 @@ const routes: Routes = [ { path: 'commands-task', component: CommandsTaskComponent }, { path: 'commands-logs', component: TaskLogsComponent }, { path: 'calendars', component: CalendarComponent }, - { path: 'client/:id', component: ClientMainViewComponent }, - { path: 'client/:id/partition-assistant', component: PartitionAssistantComponent }, + { path: 'clients/:id', component: ClientMainViewComponent }, + { path: 'clients/:id/partition-assistant', component: PartitionAssistantComponent }, + { path: 'clients/:id/create-image', component: CreateImageComponent }, + { path: 'clients/:id/deploy-image', component: DeployImageComponent }, { path: 'images', component: ImagesComponent }, { path: 'repositories', component: RepositoriesComponent }, - { path: 'restore-image', component: RestoreImageComponent}, + { path: 'repository/:id', component: MainRepositoryViewComponent }, { path: 'software', component: SoftwareComponent }, { path: 'software-profiles', component: SoftwareProfileComponent }, { path: 'operative-systems', component: OperativeSystemComponent }, diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 55da408..090451b 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -106,7 +106,6 @@ import { ClientMainViewComponent } from './components/groups/components/client-m import { ImagesComponent } from './components/images/images.component'; import { CreateImageComponent } from './components/images/create-image/create-image.component'; import { PartitionAssistantComponent } from './components/groups/components/client-main-view/partition-assistant/partition-assistant.component'; -import { RestoreImageComponent } from './components/groups/components/client-main-view/restore-image/restore-image.component'; import { SoftwareComponent } from './components/software/software.component'; import { CreateSoftwareComponent } from './components/software/create-software/create-software.component'; import { SoftwareProfileComponent } from './components/software-profile/software-profile.component'; @@ -119,6 +118,8 @@ import { ClientsComponent } from './components/ogboot/pxe/clients/clients.compon import { RepositoriesComponent } from './components/repositories/repositories.component'; import { CreateRepositoryComponent } from './components/repositories/create-repository/create-repository.component'; import { ExecuteCommandComponent } from './components/commands/main-commands/execute-command/execute-command.component'; +import { DeployImageComponent } from './components/groups/components/client-main-view/deploy-image/deploy-image.component'; +import { MainRepositoryViewComponent } from './components/repositories/main-repository-view/main-repository-view.component'; @NgModule({ declarations: [ AppComponent, @@ -182,7 +183,6 @@ import { ExecuteCommandComponent } from './components/commands/main-commands/exe ImagesComponent, CreateImageComponent, PartitionAssistantComponent, - RestoreImageComponent, SoftwareComponent, CreateSoftwareComponent, SoftwareProfileComponent, @@ -195,6 +195,8 @@ import { ExecuteCommandComponent } from './components/commands/main-commands/exe RepositoriesComponent, CreateRepositoryComponent, ExecuteCommandComponent, + DeployImageComponent, + MainRepositoryViewComponent, ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.html b/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.html index 9fe6871..a2fd016 100644 --- a/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.html +++ b/ogWebconsole/src/app/components/calendar/create-calendar-rule/create-calendar-rule.component.html @@ -1,6 +1,6 @@

{{ isEditMode ? 'Editar' : 'Añadir' }} calendario

- ¿Disponibilidad remoto? + ¿Remote PC?
Selecciona los días de la semana diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.css b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.css index 0a5a10b..952c78a 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.css +++ b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.css @@ -70,13 +70,18 @@ table { margin-bottom: 30px; } -.mat-chip-readonly-true { - background-color: #4CAF50 !important; - color: white !important; +.chip-failed { + background-color: #f15d5d !important; + color: white; } -.mat-chip-readonly-false { - background-color: #F44336 !important; - color: white !important; +.chip-success { + background-color: #2ea22e !important; + color: white; +} + +.chip-pending { + background-color: orange !important; + color: black; } diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html index 766cf69..d2aa159 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html +++ b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.html @@ -1,5 +1,5 @@
-

Trazas de comandos y procedimientos

+

Trazas

@@ -24,20 +24,46 @@ + + + Estado + + Fallido + Pendiente de ejecutar + Completado con éxito + +
-
-
@@ -46,6 +72,7 @@
{{ column.header }} - {{ column.cell(trace) }} + + + + {{ + trace.status === 'failed' ? 'Fallido' : + trace.status === 'success' ? 'Finalizado con éxito' : + trace.status === 'pending' ? 'Pendiente de ejecutar' : + trace.status + }} + + + + + {{ column.cell(trace) }} + +
+
`${trace.status}` }, + { + columnDef: 'jobId', + header: 'Hilo de trabajo', + cell: (trace: any) => `${trace.jobId}` + }, { columnDef: 'executedAt', header: 'Programación de ejecución', cell: (trace: any) => `${this.datePipe.transform(trace.executedAt, 'dd/MM/yyyy hh:mm:ss')}`, }, { - columnDef: 'createdAt', - header: 'Fecha de creación', - cell: (trace: any) => `${this.datePipe.transform(trace.createdAt, 'dd/MM/yyyy hh:mm:ss')}`, - } + columnDef: 'finishedAt', + header: 'Finalización', + cell: (trace: any) => `${this.datePipe.transform(trace.finishedAt, 'dd/MM/yyyy hh:mm:ss')}`, + }, ]; displayedColumns = [...this.columns.map(column => column.columnDef)]; diff --git a/ogWebconsole/src/app/components/groups/components/advanced-search/advanced-search.component.css b/ogWebconsole/src/app/components/groups/components/advanced-search/advanced-search.component.css index fbf7901..25efc7a 100644 --- a/ogWebconsole/src/app/components/groups/components/advanced-search/advanced-search.component.css +++ b/ogWebconsole/src/app/components/groups/components/advanced-search/advanced-search.component.css @@ -293,3 +293,31 @@ mat-card { .result-list { height: auto; } + +.chip-busy { + background-color: red !important; + color: black; +} + +.chip-og-live { + background-color: yellow !important; + color: black; +} + +.chip-windows, +.chip-windows-session, +.chip-macos { + background-color: blue !important; + color: white; +} + +.chip-linux, +.chip-linux-session { + background-color: purple !important; + color: white; +} + +.chip-off { + background-color: grey !important; + color: white; +} diff --git a/ogWebconsole/src/app/components/groups/components/advanced-search/advanced-search.component.html b/ogWebconsole/src/app/components/groups/components/advanced-search/advanced-search.component.html index 7fd0e2c..1f16945 100644 --- a/ogWebconsole/src/app/components/groups/components/advanced-search/advanced-search.component.html +++ b/ogWebconsole/src/app/components/groups/components/advanced-search/advanced-search.component.html @@ -56,7 +56,18 @@

{{ result.type !== 'client' ? result.type : '' }}

{{ result.ip }}

{{ result.mac }}

-

{{ result.status }}

+ + + {{ result.status }} + +

Unidades internas: {{ result.children.length }}

Clientes: {{ result.clients.length }}

diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html index 188b776..cc9526d 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.html @@ -4,90 +4,77 @@
-
- -
- -
+ + +
+ +
-
-
- - +
+
{{ clientData?.property }}
{{ clientData?.value }}
- -
{{ clientData?.property }}
{{ clientData?.value }}
-
- -
- -
-

Discos/Particiones

-
- -
- - - - - - - -
{{ column.header }} - - {{ column.cell(image) }} - - - - {{ (image.size / 1024).toFixed(2) }} GB - - -
-
- -
- -
-
-

Disco {{ disk.diskNumber }}

-
- - - - - - - - {{ (disk.used / disk.total * 100).toFixed(0) }}% - -
-

Usado: {{ disk.used }} GB ({{ disk.percentage }}%)

-

Total: {{ disk.total }} GB

-
-
-
+
+
+ +
+

Discos/Particiones

+
-
+
+ + + + + + + +
{{ column.header }} + + {{ column.cell(image) }} + + + + {{ (image.size / 1024).toFixed(2) }} GB + + +
+
+ +
+ +
+
+ + + +

Disco {{ disk.diskNumber }}

+

Usado: {{ disk.used }} GB ({{ disk.percentage }}%)

+

Total: {{ disk.total }} GB

+
+
+
+
+ + + diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts index 3e4c70f..ccf6560 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.ts @@ -31,6 +31,24 @@ export class ClientMainViewComponent implements OnInit { diskUsageData: any[] = []; partitions: any[] = []; commands: any[] = []; + chartDisk: any[] = []; + view: [number, number] = [600, 300]; + showLegend: boolean = true; + + arrayCommands: any[] = [ + {name: 'Enceder', slug: 'power-on'}, + {name: 'Apagar', slug: 'power-off'}, + {name: 'Reiniciar', slug: 'reboot'}, + {name: 'Iniciar Sesión', slug: 'login'}, + {name: 'Crear Image', slug: 'create-image'}, + {name: 'Deploy Image', slug: 'deploy-image'}, + {name: 'Eliminar Imagen Cache', slug: 'delete-image-cache'}, + {name: 'Particionar y Formatear', slug: 'partition'}, + {name: 'Inventario Software', slug: 'software-inventory'}, + {name: 'Inventario Hardware', slug: 'hardware-inventory'}, + {name: 'Ejecutar script', slug: 'run-script'}, + ]; + datePipe: DatePipe = new DatePipe('es-ES'); columns = [ { @@ -58,6 +76,11 @@ export class ClientMainViewComponent implements OnInit { header: 'Uso', cell: (partition: any) => `${partition.memoryUsage} %` }, + { + columnDef: 'operativeSystem', + header: 'SO', + cell: (partition: any) => `${partition.operativeSystem?.name}` + }, ]; displayedColumns = [...this.columns.map(column => column.columnDef)]; isDiskUsageEmpty: boolean = true; @@ -75,6 +98,7 @@ export class ClientMainViewComponent implements OnInit { ngOnInit() { this.clientData = history.state.clientData; + console.log(this.clientData) this.loadPartitions() this.updateGeneralData(); this.updateNetworkData(); @@ -100,11 +124,11 @@ export class ClientMainViewComponent implements OnInit { this.networkData = [ { property: 'Remote Pc', value: this.clientData.remotePc || '' }, { property: 'Subred', value: this.clientData?.subnet || '' }, - { property: 'OGlive', value: '' }, + { property: 'OGlive', value: this.clientData?.ogLive?.name || '' }, { property: 'Autoexec', value: '' }, - { property: 'Repositorio', value: '' }, + { property: 'Repositorio', value: this.clientData?.repository?.name || '' }, { property: 'Validación', value: this.clientData?.organizationalUnit?.networkSettings?.validation || '' }, - { property: 'Pxe', value: this.clientData?.template.name || '' }, + { property: 'Pxe', value: this.clientData?.template?.name || '' }, { property: 'Creado por', value: this.clientData?.createdBy || '' } ]; } @@ -124,19 +148,39 @@ export class ClientMainViewComponent implements OnInit { if (partition.partitionNumber === 0) { diskData!.total = Number((partition.size / 1024).toFixed(2)); } else { - diskData!.used += Number(((partition.size * (partition.memoryUsage / 100)) / 1024).toFixed(2)); + diskData!.used += Number((partition.size / 1024).toFixed(2)); diskData!.partitions.push(partition); } }); - this.diskUsageData = Array.from(diskUsageMap.entries()).map(([diskNumber, { total, used, partitions }]) => { - const percentage = total > 0 ? Math.round((used / total) * 100) : 0; - return { diskNumber, total, used, percentage, partitions }; + this.chartDisk = Array.from(diskUsageMap.entries()).map(([diskNumber, { total, used, partitions }]) => { + const partitionData = partitions.map(partition => ({ + name: `Partición ${partition.partitionNumber}`, + value: Number((partition.size / 1024).toFixed(2)) + })); + + const freeSpace = total - used; + if (freeSpace > 0) { + partitionData.push({ + name: 'Espacio libre', + value: Number(freeSpace.toFixed(2)) + }); + } + + return { + diskNumber, + chartData: partitionData, + total, + used, + percentage: total > 0 ? Math.round((used / total) * 100) : 0 + }; }); + this.diskUsageData = this.chartDisk; this.isDiskUsageEmpty = this.diskUsageData.length === 0; } + getStrokeOffset(partitions: any[], index: number): number { const totalSize = partitions.reduce((acc, part) => acc + (part.size / 1024), 0); @@ -170,16 +214,35 @@ export class ClientMainViewComponent implements OnInit { }); } - onCommandSelect(command: any): void { - if (command.name === 'Particionar y Formatear') { + onCommandSelect(action: any): void { + if (action === 'partition') { this.openPartitionAssistant(); } + + if (action === 'create-image') { + this.openCreateImageAssistant(); + } + + if (action === 'deploy-image') { + this.openDeployImageAssistant(); + } } openPartitionAssistant(): void { - console.log(this.clientData) this.router.navigate([`/client/${this.clientData.uuid}/partition-assistant`]).then(r => { console.log('navigated', r); }); } + + openCreateImageAssistant(): void { + this.router.navigate([`/client/${this.clientData.uuid}/create-image`]).then(r => { + console.log('navigated', r); + }); + } + + openDeployImageAssistant(): void { + this.router.navigate([`/client/${this.clientData.uuid}/deploy-image`]).then(r => { + console.log('navigated', r); + }); + } } diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.css new file mode 100644 index 0000000..fc11805 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.css @@ -0,0 +1,76 @@ +.title { + font-size: 24px; +} + +.calendar-button-row { + display: flex; + justify-content: flex-start; + margin-top: 16px; +} + +.divider { + margin: 20px 0; +} + +.lists-container { + padding: 16px; +} + +.card.unidad-card { + height: 100%; + box-sizing: border-box; +} + +table { + width: 100%; + margin-top: 50px; +} + +.search-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 0 5px; + box-sizing: border-box; +} + +.select-container { + margin-top: 20px; + align-items: center; + width: 100%; + padding: 0 5px; + box-sizing: border-box; +} + +.full-width { + width: 100%; + margin-bottom: 16px; +} + +.search-string { + flex: 2; + padding: 5px; +} + +.search-boolean { + flex: 1; + padding: 5px; +} + +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +.paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html new file mode 100644 index 0000000..85216c9 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html @@ -0,0 +1,46 @@ +
+

Crear Imagen desde {{ clientName }}

+
+ +
+
+ + +
+ + Nombre canónico + + + + + Seleccione imagen + + -- + {{ image.name }} + + +
+ + + + + + + + + + + + + + +
Seleccionar partición + + + + + {{ column.header }} + {{ column.cell(image) }} +
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.spec.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.spec.ts new file mode 100644 index 0000000..3949532 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateImageComponent } from './create-image.component'; + +describe('CreateImageComponent', () => { + let component: CreateImageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CreateImageComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CreateImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts new file mode 100644 index 0000000..1c26c50 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts @@ -0,0 +1,168 @@ +import {Component, EventEmitter, Output} from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {ToastrService} from "ngx-toastr"; +import {ActivatedRoute} from "@angular/router"; +import {MatButton} from "@angular/material/button"; +import {MatDivider} from "@angular/material/divider"; +import {NgForOf, NgIf} from "@angular/common"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import { + MatCell, MatCellDef, + MatColumnDef, + MatHeaderCell, + MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef, + MatTable, + MatTableDataSource +} from "@angular/material/table"; +import {MatChip} from "@angular/material/chips"; +import {MatCheckbox} from "@angular/material/checkbox"; +import {SelectionModel} from "@angular/cdk/collections"; +import {MatRadioButton, MatRadioGroup} from "@angular/material/radio"; +import {MatFormField, MatLabel} from "@angular/material/form-field"; +import {MatOption} from "@angular/material/autocomplete"; +import {MatSelect} from "@angular/material/select"; +import {MatInput} from "@angular/material/input"; + +@Component({ + selector: 'app-create-image', + templateUrl: './create-image.component.html', + standalone: true, + imports: [ + MatButton, + MatDivider, + NgForOf, + NgIf, + ReactiveFormsModule, + MatTable, + MatColumnDef, + MatHeaderCell, + MatHeaderCellDef, + MatCell, + MatCellDef, + MatChip, + MatHeaderRow, + MatRow, + MatHeaderRowDef, + MatRowDef, + MatCheckbox, + MatRadioGroup, + MatRadioButton, + MatFormField, + MatLabel, + MatOption, + MatSelect, + MatInput, + FormsModule + ], + styleUrl: './create-image.component.css' +}) +export class CreateImageComponent { + baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; + @Output() dataChange = new EventEmitter(); + + errorMessage = ''; + clientId: string | null = null; + partitions: any[] = []; + images: any[] = []; + clientName: string = ''; + selectedImage: string | null = null; + selectedPartition: any = null; + name: string = ''; + dataSource = new MatTableDataSource(); + columns = [ + { + columnDef: 'diskNumber', + header: 'Disco', + cell: (partition: any) => `${partition.diskNumber}` + }, + { + columnDef: 'partitionNumber', + header: 'Particion', + cell: (partition: any) => `${partition.partitionNumber}` + }, + { + columnDef: 'size', + header: 'Tamaño', + cell: (partition: any) => `${partition.size} MB` + }, + { + columnDef: 'filesystem', + header: 'Sistema de ficheros', + cell: (partition: any) => `${partition.filesystem}` + }, + { + columnDef: 'operativeSystem', + header: 'SO', + cell: (partition: any) => `${partition.operativeSystem?.name}` + } + ]; + + displayedColumns = ['select', ...this.columns.map(column => column.columnDef)]; + selection = new SelectionModel(true, []); + + constructor( + private http: HttpClient, + private toastService: ToastrService, + private route: ActivatedRoute + ) {} + + ngOnInit() { + this.clientId = this.route.snapshot.paramMap.get('id'); + + this.loadPartitions(); + this.loadImages(); + } + + loadPartitions() { + const url = `${this.baseUrl}/clients/${this.clientId}`; + this.http.get(url).subscribe( + (response: any) => { + if (response.partitions) { + this.clientName = response.name; + + this.dataSource.data = response.partitions.filter((partition: any) => { + return partition.partitionNumber !== 0; + }); + } + }, + (error) => { + console.error('Error al cargar los datos del cliente:', error); + } + ); + } + + loadImages() { + const url = `${this.baseUrl}/images?created=true&page=1&itemsPerPage=1000`; + this.http.get(url).subscribe( + (response: any) => { + this.images = response['hydra:member']; + }, + (error) => { + console.error('Error al cargar las imágenes:', error); + } + ); + } + + save(): void { + const payload = { + client: `/clients/${this.clientId}`, + name: this.name, + image: this.selectedImage, + partition: this.selectedPartition['@id'], + input: 'assistant' + }; + + + this.http.post(`${this.baseUrl}/images`, payload) + .subscribe({ + next: (response) => { + this.toastService.success('Imagen creada exitosamente'); + }, + error: (error) => { + console.error('Error:', error); + this.toastService.error(error.error['hydra:description']); + } + } + ); + } +} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.css new file mode 100644 index 0000000..6f2e1d9 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.css @@ -0,0 +1,84 @@ + +.divider { + margin: 20px 0; +} + +table { + width: 100%; + margin-top: 50px; +} + +.search-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 0 5px; + box-sizing: border-box; +} + +.option-container { + margin: 20px 0; + width: 100%; +} + +.deploy-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 5px; + gap: 10px; +} + +.select-container { + margin-top: 20px; + align-items: center; + width: 100%; + padding: 0 5px; + box-sizing: border-box; +} + +.input-group { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin-top: 20px; +} + +.input-field { + flex: 1 1 calc(33.33% - 16px); + min-width: 250px; +} + +.full-width { + width: 100%; + margin-bottom: 16px; +} + +.search-string { + flex: 2; + padding: 5px; +} + +.search-boolean { + flex: 1; + padding: 5px; +} + +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +.paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html new file mode 100644 index 0000000..347e2e2 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html @@ -0,0 +1,109 @@ +
+

Deploy imagen en {{ clientName }}

+
+ +
+
+ + +
+
+ + Actualizar cache + Deploy imagen + +
+ +
+ + Seleccione imagen + + {{ image.name }} + + + + + Seleccione método de deploy + + {{ method }} + + +
+
+ + + + + + + + + + + + + + +
Seleccionar partición + + + + + {{ column.header }} + {{ column.cell(image) }} +
+ + +

Opciones multicast

+

Opciones torrent

+
+ + Puerto + + + + + Dirección + + + + + Modo Multicast + + + {{ option.name }} + + + + + + Velocidad + + + + + Máximo Clientes + + + + + Tiempo Máximo de Espera + + +
+ +
+ + Modo P2P + + + {{ option.name }} + + + + + + Semilla + + +
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts new file mode 100644 index 0000000..b6b9991 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DeployImageComponent } from './deploy-image.component'; + +describe('DeployImageComponent', () => { + let component: DeployImageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DeployImageComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(DeployImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts new file mode 100644 index 0000000..836df18 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts @@ -0,0 +1,161 @@ +import {Component, EventEmitter, Output} from '@angular/core'; +import {MatTableDataSource} from "@angular/material/table"; +import {SelectionModel} from "@angular/cdk/collections"; +import {HttpClient} from "@angular/common/http"; +import {ToastrService} from "ngx-toastr"; +import {ActivatedRoute} from "@angular/router"; + +@Component({ + selector: 'app-deploy-image', + templateUrl: './deploy-image.component.html', + styleUrl: './deploy-image.component.css' +}) +export class DeployImageComponent { + baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; + @Output() dataChange = new EventEmitter(); + + errorMessage = ''; + clientId: string | null = null; + partitions: any[] = []; + images: any[] = []; + clientName: string = ''; + selectedImage: string | null = null; + selectedOption: string | null = null; + selectedMethod: string | null = null; + selectedPartition: any = null; + mcastIp: string = ''; + mcastPort: string = ''; + mcastMode: string = ''; + mcastSpeed: string = ''; + mcastMaxClients: string = ''; + mcastMaxTime: string = ''; + p2pMode: string = ''; + p2pTime: string = ''; + name: string = ''; + + protected p2pModeOptions = [ + { name: 'Leecher', value: 'p2p-mode-leecher' }, + { name: 'Peer', value: 'p2p-mode-peer' }, + { name: 'Seeder', value: 'p2p-mode-seeder' }, + ]; + protected multicastModeOptions = [ + {"name": 'Half duplex', "value": "half-duplex"}, + {"name": 'Full duplex', "value": "full-duplex"}, + ]; + + allMethods = [ + 'multicast', + 'multicast-direct', + 'unicast', + 'unicast-direct', + 'torrent' + ]; + + updateCacheMethods = [ + 'multicast', + 'unicast', + 'torrent' + ]; + + dataSource = new MatTableDataSource(); + columns = [ + { + columnDef: 'diskNumber', + header: 'Disco', + cell: (partition: any) => `${partition.diskNumber}` + }, + { + columnDef: 'partitionNumber', + header: 'Particion', + cell: (partition: any) => `${partition.partitionNumber}` + }, + { + columnDef: 'size', + header: 'Tamaño', + cell: (partition: any) => `${partition.size} MB` + }, + { + columnDef: 'filesystem', + header: 'Sistema de ficheros', + cell: (partition: any) => `${partition.filesystem}` + }, + { + columnDef: 'operativeSystem', + header: 'SO', + cell: (partition: any) => `${partition.operativeSystem?.name}` + } + ]; + + displayedColumns = ['select', ...this.columns.map(column => column.columnDef)]; + selection = new SelectionModel(true, []); + + constructor( + private http: HttpClient, + private toastService: ToastrService, + private route: ActivatedRoute + ) {} + + ngOnInit() { + this.clientId = this.route.snapshot.paramMap.get('id'); + + this.loadPartitions(); + this.loadImages(); + } + + get deployMethods() { + return this.selectedOption === 'update-cache' ? this.updateCacheMethods : this.allMethods; + } + + isMethod(method: string): boolean { + return this.selectedMethod === method; + } + + loadPartitions() { + const url = `${this.baseUrl}/clients/${this.clientId}`; + this.http.get(url).subscribe( + (response: any) => { + if (response.partitions) { + this.clientName = response.name; + this.dataSource.data = response?.partitions; + } + }, + (error) => { + console.error('Error al cargar los datos del cliente:', error); + } + ); + } + + loadImages() { + const url = `${this.baseUrl}/images?page=1&itemsPerPage=1000`; + this.http.get(url).subscribe( + (response: any) => { + this.images = response['hydra:member']; + }, + (error) => { + console.error('Error al cargar las imágenes:', error); + } + ); + } + + save(): void { + const payload = { + client: `/clients/${this.clientId}`, + name: this.name, + image: this.selectedImage, + partition: this.selectedPartition['@id'] + }; + + + this.http.post(`${this.baseUrl}/images`, payload) + .subscribe({ + next: (response) => { + this.toastService.success('Imagen creada exitosamente'); + }, + error: (error) => { + console.error('Error:', error); + this.toastService.error(error.error['hydra:description']); + } + } + ); + } +} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html index 09e52c4..76c25e2 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html @@ -30,41 +30,42 @@ Partición Tipo partición Tamaño (MB) - Uso (%) + Tamaño (%) Formatear Eliminar - - {{ partition.partitionNumber }} - - - - - - - - - - - - - - - - + + {{ partition.partitionNumber }} + + + + + + + + + + + + + + + +
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts index 196cc0a..ab0803c 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts @@ -41,7 +41,6 @@ export class PartitionAssistantComponent implements OnInit { ngOnInit() { this.clientId = this.route.snapshot.paramMap.get('id'); - this.loadPartitions(); } @@ -112,7 +111,6 @@ export class PartitionAssistantComponent implements OnInit { if (disk) { const remainingGB = this.getRemainingGB(disk.partitions, disk.totalDiskSize); - console.log(remainingGB) if (remainingGB > 0) { const maxPartitionNumber = disk.partitions.length > 0 ? Math.max(...disk.partitions.map((p) => p.partitionNumber)) : 0; @@ -142,20 +140,41 @@ export class PartitionAssistantComponent implements OnInit { const remainingGB = this.getRemainingGB(disk.partitions, disk.totalDiskSize) + partition.size; if (size > remainingGB) { - this.errorMessage = `El tamaño de la partición no puede superar el espacio libre (${remainingGB.toFixed( - 2 - )} GB).`; + this.errorMessage = `El tamaño de la partición no puede superar el espacio libre (${remainingGB.toFixed(2)} GB).`; } else { this.errorMessage = ''; partition.size = size; partition.sizeBytes = size; + + partition.percentage = (size / disk.totalDiskSize) * 100; this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); } } } + updatePartitionPercentage(diskNumber: number, index: number, percentage: number) { + const disk = this.disks.find((d) => d.diskNumber === diskNumber); + if (disk) { + const partition = disk.partitions[index]; + + const newSizeMB = (percentage / 100) * disk.totalDiskSize; + + const totalPercentage = disk.partitions.reduce((sum, part) => sum + (part === partition ? percentage : part.percentage), 0); + + if (totalPercentage > 100) { + this.errorMessage = 'El tamaño total en porcentaje de las particiones no puede exceder el 100%'; + partition.percentage = 100 - (totalPercentage - percentage); + } else { + this.errorMessage = ''; + partition.percentage = percentage; + partition.size = newSizeMB; + } + } + } + + + getRemainingGB(partitions: Partition[], totalDiskSize: number): number { - console.log(totalDiskSize) const totalUsedGB = partitions.reduce((acc, partition) => acc + partition.size, 0); return Math.max(0, totalDiskSize - totalUsedGB); } @@ -228,6 +247,7 @@ export class PartitionAssistantComponent implements OnInit { this.http.post(this.apiUrl, payload).subscribe( (response) => { this.toastService.success('Partición creada exitosamente'); + window.location.reload(); }, (error) => { console.error('Error al crear la partición:', error); @@ -239,6 +259,7 @@ export class PartitionAssistantComponent implements OnInit { this.http.patch(patchUrl, payload).subscribe( (response) => { this.toastService.success('Partición actualizada exitosamente'); + window.location.reload(); }, (error) => { console.error('Error al actualizar la partición:', error); @@ -249,7 +270,6 @@ export class PartitionAssistantComponent implements OnInit { }); } - removePartition(diskNumber: number, partition: Partition) { const disk = this.disks.find((d) => d.diskNumber === diskNumber); @@ -264,6 +284,7 @@ export class PartitionAssistantComponent implements OnInit { this.http.delete(deleteUrl).subscribe( (response) => { this.toastService.success('Partición eliminada exitosamente'); + window.location.reload(); }, (error) => {} ); diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.css deleted file mode 100644 index ad6b310..0000000 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.css +++ /dev/null @@ -1,71 +0,0 @@ -.partition-assistant { - font-family: 'Roboto', sans-serif; - background-color: #f9f9f9; - padding: 20px; - border-radius: 10px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - margin: 20px auto; - } - - .header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 15px; - padding: 10px; - background-color: #fff; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - - .partition-table { - width: 100%; - border-collapse: collapse; - background-color: #fff; - border-radius: 8px; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); - overflow: hidden; - margin-bottom: 20px; - } - - .partition-table th { - background-color: #f5f5f5; - color: #333; - padding: 12px; - font-weight: 600; - } - - .partition-table td { - padding: 10px; - text-align: center; - border-bottom: 1px solid #eee; - } - - .partition-table select { - padding: 5px; - border-radius: 4px; - border: 1px solid #ccc; - width: 100%; - } - - .actions { - display: flex; - justify-content: flex-end; - padding-top: 10px; - } - - button.mat-flat-button { - background-color: #28a745; - color: white; - padding: 10px 20px; - border-radius: 4px; - font-size: 1rem; - font-weight: 500; - cursor: pointer; - transition: background-color 0.3s ease; - } - - button.mat-flat-button:hover { - background-color: #218838; - } - \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.html deleted file mode 100644 index 4301955..0000000 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.html +++ /dev/null @@ -1,39 +0,0 @@ -

Asistente de imagenes en disco

-
-
- -
- - - - - - - - - - - - - - - - -
ParticiónImagen ISOOgLive
{{ partition.partitionNumber }} - - - -
-
- -
- -
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.ts deleted file mode 100644 index 65d424d..0000000 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/restore-image/restore-image.component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; - -interface Image { - '@id': string; - '@type': string; - name: string; - description: string; - comments: string; - uuid: string; - id: number; -} - -interface Partition { - diskNumber: number; - partitionNumber: number; - associatedImageId?: string; - associatedOgLive?: string; -} - -@Component({ - selector: 'app-restore-image', - templateUrl: './restore-image.component.html', - styleUrls: ['./restore-image.component.css'] -}) -export class RestoreImageComponent implements OnInit { - @Input() data: any; - - disks: { diskNumber: number; partitions: Partition[] }[] = []; - availableImages: Image[] = []; - availableOgLives: string[] = []; - - constructor(private http: HttpClient) {} - - ngOnInit(): void { - this.initializeDisks(); - this.fetchAvailableImages(); - this.availableOgLives = ['LiveCD1', 'LiveCD2', 'LiveCD3']; - } - - initializeDisks() { - const partitionsFromData = this.data.partitions; - const disksMap = new Map(); - - partitionsFromData.forEach((partition: any) => { - if (!disksMap.has(partition.diskNumber)) { - disksMap.set(partition.diskNumber, []); - } - - disksMap.get(partition.diskNumber)!.push({ - diskNumber: partition.diskNumber, - partitionNumber: partition.partitionNumber - }); - }); - - disksMap.forEach((partitions, diskNumber) => { - this.disks.push({ diskNumber, partitions }); - }); - } - - fetchAvailableImages() { - const url = 'http://127.0.0.1:8001/images?page=1&itemsPerPage=30'; - this.http.get(url).subscribe( - (response: any) => { - this.availableImages = response['hydra:member']; - }, - (error) => { - console.error('Error al obtener las imágenes:', error); - } - ); - } - - onImageSelected(partition: Partition, event: Event) { - const selectElement = event.target as HTMLSelectElement; - partition.associatedImageId = selectElement.value; - } - - onOgLiveSelected(partition: Partition, event: Event) { - const selectElement = event.target as HTMLSelectElement; - partition.associatedOgLive = selectElement.value; - } - - saveAssociations() { - this.disks.forEach(disk => { - disk.partitions.forEach(partition => { - if (partition.associatedImageId || partition.associatedOgLive) { - console.log( - `Guardando para disco ${partition.diskNumber}, partición ${partition.partitionNumber}, ` + - `Imagen ID: ${partition.associatedImageId}, OgLive: ${partition.associatedOgLive}` - ); - } - }); - }); - } -} diff --git a/ogWebconsole/src/app/components/groups/components/client-tab-view/client-tab-view.component.css b/ogWebconsole/src/app/components/groups/components/client-tab-view/client-tab-view.component.css index 53aec3e..a57a176 100644 --- a/ogWebconsole/src/app/components/groups/components/client-tab-view/client-tab-view.component.css +++ b/ogWebconsole/src/app/components/groups/components/client-tab-view/client-tab-view.component.css @@ -62,3 +62,31 @@ button{ color: #666; line-height: 1.5; } + +.chip-busy { + background-color: red !important; + color: black; +} + +.chip-og-live { + background-color: yellow !important; + color: black; +} + +.chip-windows, +.chip-windows-session, +.chip-macos { + background-color: blue !important; + color: white; +} + +.chip-linux, +.chip-linux-session { + background-color: purple !important; + color: white; +} + +.chip-off { + background-color: grey !important; + color: white; +} diff --git a/ogWebconsole/src/app/components/groups/components/client-tab-view/client-tab-view.component.html b/ogWebconsole/src/app/components/groups/components/client-tab-view/client-tab-view.component.html index 63d3f09..52375fd 100644 --- a/ogWebconsole/src/app/components/groups/components/client-tab-view/client-tab-view.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-tab-view/client-tab-view.component.html @@ -41,49 +41,59 @@
- - - - + + + - - + + + {{ client.status }} + + - - -
{{ column.header }} - -
-
{{ client.name }}
-
{{ client.ip }}
-
{{ client.mac }}
-
-
- - - {{ client.status }} - - - - {{ column.cell(client) }} - -
{{ column.header }} + +
+
{{ client.name }}
+
{{ client.ip }}
+
{{ client.mac }}
+
+
- -
Acciones - - - - - -
-
- - -
+ + {{ column.cell(client) }} + + + + + + + Acciones + + + + + + + + + + + + +
+ + +
diff --git a/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.html b/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.html index 30ec146..b73a666 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.html +++ b/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.html @@ -77,7 +77,15 @@ {{ unit.description }} - Formato de URL inválido. + + + + Repositorio + + + {{ repository.name }} + + diff --git a/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.ts b/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.ts index 7298966..6aa2807 100644 --- a/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/clients/edit-client/edit-client.component.ts @@ -16,6 +16,7 @@ export class EditClientComponent { clientForm!: FormGroup; parentUnits: any[] = []; hardwareProfiles: any[] = []; + repositories: any[] = []; ogLives: any[] = []; templates: any[] = []; isEditMode: boolean; @@ -48,6 +49,7 @@ export class EditClientComponent { this.loadHardwareProfiles(); this.loadOgLives(); this.loadPxeTemplates() + this.loadRepositories(); this.clientForm = this.fb.group({ organizationalUnit: [null, Validators.required], name: ['', Validators.required], @@ -59,6 +61,7 @@ export class EditClientComponent { template: null, hardwareProfile: null, ogLive: null, + repository: null, }); } @@ -99,6 +102,19 @@ export class EditClientComponent { ); } + loadRepositories(): void { + const url = `${this.baseUrl}/image-repositories?page=1&itemsPerPage=10000`; + + this.http.get(url).subscribe( + response => { + this.repositories = response['hydra:member']; + }, + error => { + console.error('Error fetching ogLives:', error); + } + ); + } + loadPxeTemplates(): void { const url = `${this.baseUrl}/pxe-templates?page=1&itemsPerPage=10000`; @@ -127,6 +143,7 @@ export class EditClientComponent { serialNumber: data.serialNumber, hardwareProfile: data.hardwareProfile ? data.hardwareProfile['@id'] : null, organizationalUnit: data.organizationalUnit ? data.organizationalUnit['@id'] : null, + repository: data.repository ? data.repository['@id'] : null, ogLive: data.ogLive ? data.ogLive['@id'] : null, template: data.template ? data.template['@id'] : null, }); @@ -150,7 +167,6 @@ export class EditClientComponent { this.http.patch(putUrl, formData, { headers }).subscribe( response => { - console.log('PUT successful:', response); this.dialogRef.close(); this.openSnackBar(false, 'Cliente actualizado exitosamente'); @@ -167,7 +183,6 @@ export class EditClientComponent { this.http.post(postUrl, formData, { headers }).subscribe( response => { - console.log('POST successful:', response); this.dialogRef.close(); this.openSnackBar(false, 'Cliente creado exitosamente'); }, diff --git a/ogWebconsole/src/app/components/images/create-image/create-image.component.css b/ogWebconsole/src/app/components/images/create-image/create-image.component.css index a25f952..fac8662 100644 --- a/ogWebconsole/src/app/components/images/create-image/create-image.component.css +++ b/ogWebconsole/src/app/components/images/create-image/create-image.component.css @@ -15,6 +15,14 @@ margin-bottom: 16px; } +.partition-info-container { + background-color: #f0f8ff; /* Un color de fondo suave */ + padding: 10px; + border: 1px solid #ccc; + margin-top: 10px; + border-radius: 5px; +} + /* Botones alineados al final, con margen superior */ .dialog-actions { display: flex; diff --git a/ogWebconsole/src/app/components/images/create-image/create-image.component.html b/ogWebconsole/src/app/components/images/create-image/create-image.component.html index e72c39f..368dab7 100644 --- a/ogWebconsole/src/app/components/images/create-image/create-image.component.html +++ b/ogWebconsole/src/app/components/images/create-image/create-image.component.html @@ -1,4 +1,4 @@ -

Añadir nueva imagen

+

{{ imageId ? 'Editar' : 'Crear' }} imagen

@@ -28,7 +28,7 @@ Perfil de software - + {{ profile.description }} @@ -41,7 +41,19 @@ > Remote Pc + + + +
+

Información de Partición de origen

+

Sistema de archivos: {{ partitionInfo['filesystem'] }}

+

Disco: {{ partitionInfo['numDisk'] }}

+

Particion: {{ partitionInfo['numPartition'] }}

+

Nombre del SO: {{ partitionInfo['osName'] }}

+

Código de partición: {{ partitionInfo['partitionCode'] }}

+
+
diff --git a/ogWebconsole/src/app/components/images/create-image/create-image.component.ts b/ogWebconsole/src/app/components/images/create-image/create-image.component.ts index d3b5d6a..ae6b57c 100644 --- a/ogWebconsole/src/app/components/images/create-image/create-image.component.ts +++ b/ogWebconsole/src/app/components/images/create-image/create-image.component.ts @@ -15,6 +15,7 @@ export class CreateImageComponent implements OnInit { imageId: string | null = null; softwareProfiles: any[] = []; repositories: any[] = []; + partitionInfo: { [key: string]: string } = {}; constructor( private fb: FormBuilder, @@ -29,7 +30,7 @@ export class CreateImageComponent implements OnInit { description: [''], comments: [''], remotePc: [false], - softwareProfile: ['', Validators.required], + softwareProfile: [''], imageRepository: ['', Validators.required], }); } @@ -50,10 +51,11 @@ export class CreateImageComponent implements OnInit { description: [response.description], comments: [response.comments], remotePc: [response.remotePc], - softwareProfile: [response.softwareProfile['@id'], Validators.required], - imageRepository: [response.repository['@id'], Validators.required], + softwareProfile: [response.softwareProfile ? response.softwareProfile['@id'] : null, Validators.required], + imageRepository: [response.imageRepository ? response.imageRepository['@id'] : null, Validators.required], }); this.imageId = response['@id']; + this.partitionInfo = response.partitionInfo; }, error: (err) => { console.error('Error fetching remote calendar:', err); @@ -93,13 +95,13 @@ export class CreateImageComponent implements OnInit { return; } - const payload = { + const payload: any = { name: this.imageForm.value.name, description: this.imageForm.value.description, comments: this.imageForm.value.comments, remotePc: this.imageForm.value.remotePc, - softwareProfile: this.imageForm.value.softwareProfile, imageRepository: this.imageForm.value.imageRepository, + ...(this.imageForm.value.softwareProfile ? { softwareProfile: this.imageForm.value.softwareProfile } : {}), }; if (this.imageId) { diff --git a/ogWebconsole/src/app/components/images/images.component.css b/ogWebconsole/src/app/components/images/images.component.css index 5b7e163..6c78292 100644 --- a/ogWebconsole/src/app/components/images/images.component.css +++ b/ogWebconsole/src/app/components/images/images.component.css @@ -1,44 +1,15 @@ .title { - font-size: 24px; + font-size: 24px; } .images-button-row { - display: flex; - justify-content: flex-start; - margin-top: 16px; + display: flex; + justify-content: flex-start; + margin-top: 16px; } .divider { - margin: 20px 0; -} - -.lists-container { - padding: 16px; -} - -.imagesLists-container { - flex: 1; -} - -.card.unidad-card { - height: 100%; - box-sizing: border-box; -} - -.image-container { - display: flex; - align-items: center; - margin-bottom: 16px; - border-bottom: 1px solid rgba(122, 122, 122, 0.555); -} - -.image-container h4 { - margin: 0; - flex: 1; -} - -.image-name{ - cursor: pointer; + margin: 20px 0; } table { @@ -91,12 +62,28 @@ table { margin-left: 8px; } -.example-button-row { +.button-row { display: table-cell; max-width: 600px; } -.example-button-row .mat-mdc-button-base { +.button-row .mat-mdc-button-base { margin: 8px 8px 8px 0; } +.status-success { + background-color: #4caf50; /* Verde */ + color: white; +} + +.status-failed { + background-color: #f44336; /* Rojo */ + color: white; +} + +.status-pending { + background-color: #ff9800; /* Naranja */ + color: white; +} + + diff --git a/ogWebconsole/src/app/components/images/images.component.html b/ogWebconsole/src/app/components/images/images.component.html index b75972e..fa31c21 100644 --- a/ogWebconsole/src/app/components/images/images.component.html +++ b/ogWebconsole/src/app/components/images/images.component.html @@ -1,4 +1,5 @@ -
+ +

Administrar imágenes

@@ -18,25 +19,49 @@ - + - diff --git a/ogWebconsole/src/app/components/images/images.component.ts b/ogWebconsole/src/app/components/images/images.component.ts index 9bca5c5..c9baa32 100644 --- a/ogWebconsole/src/app/components/images/images.component.ts +++ b/ogWebconsole/src/app/components/images/images.component.ts @@ -7,6 +7,9 @@ import { DatePipe } from '@angular/common'; import { CreateImageComponent } from './create-image/create-image.component'; import {CreateCommandComponent} from "../commands/main-commands/create-command/create-command.component"; import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component"; +import {ServerInfoDialogComponent} from "../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component"; +import {Observable} from "rxjs"; +import {InfoImageComponent} from "../ogboot/pxe-images/info-image/info-image/info-image.component"; @Component({ selector: 'app-images', @@ -21,6 +24,7 @@ export class ImagesComponent implements OnInit { page: number = 0; loading: boolean = false; filters: { [key: string]: string } = {}; + alertMessage: string | null = null; datePipe: DatePipe = new DatePipe('es-ES'); columns = [ { @@ -33,11 +37,6 @@ export class ImagesComponent implements OnInit { header: 'Nombre de imagen', cell: (image: any) => `${image.name}` }, - { - columnDef: 'softwareProfile', - header: 'Perfil de software', - cell: (image: any) => `${image.softwareProfile?.description}` - }, { columnDef: 'imageRepository', header: 'Repositorio', @@ -48,6 +47,16 @@ export class ImagesComponent implements OnInit { header: 'Remote Pc', cell: (image: any) => `${image.remotePc}` }, + { + columnDef: 'status', + header: 'Estado', + cell: (image: any) => `${image.status}` + }, + { + columnDef: 'imageFullsum', + header: 'Fullsum', + cell: (image: any) => `${image.imageFullsum}` + }, { columnDef: 'createdAt', header: 'Fecha de creación', @@ -70,7 +79,7 @@ export class ImagesComponent implements OnInit { addImage(): void { const dialogRef = this.dialog.open(CreateImageComponent, { - width: '600px' + width: '800px' }); dialogRef.afterClosed().subscribe(() => { @@ -96,7 +105,7 @@ export class ImagesComponent implements OnInit { editImage(event: MouseEvent, image: any): void { event.stopPropagation(); this.dialog.open(CreateImageComponent, { - width: '600px', + width: '800px', data: image['@id'] }).afterClosed().subscribe(() => this.search()); } @@ -127,4 +136,46 @@ export class ImagesComponent implements OnInit { this.length = event.length; this.search(); } + + loadImageAlert(image: any): Observable { + return this.http.get(`${this.apiUrl}/server/${image.uuid}/get`, {}); + } + + showImageInfo(event: MouseEvent, image:any) { + event.stopPropagation(); + this.loadImageAlert(image).subscribe( + response => { + this.alertMessage = response.output; + + this.dialog.open(ServerInfoDialogComponent, { + width: '600px', + data: { + message: this.alertMessage + } + }); + }, + error => { + this.toastService.error(error.error['hydra:description']); + } + ); + } + + toggleAction(image: any, action:string): void { + switch (action) { + case 'get-aux': + this.http.post(`${this.baseUrl}/images/server/${image.uuid}/create-aux-files`, {}).subscribe({ + next: () => { + this.toastService.success('Petición de creación de archivos auxiliares enviada'); + this.search() + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }); + break; + default: + console.error('Acción no soportada:', action); + break; + } + } } diff --git a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.css b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.css new file mode 100644 index 0000000..66ff364 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.css @@ -0,0 +1,307 @@ + +.client-header { + display: flex; + align-items: center; + margin-bottom: 10px; + background-color: #fff; + padding: 20px; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.client-icon { + flex-shrink: 0; + margin-right: 20px; + display: flex; + align-items: center; + justify-content: center; + min-width: 120px; + min-height: 120px; +} + +.row-container { + justify-content: space-between; + width: 100%; +} + +.table-container { + padding-right: 10px; +} + +.charts-wrapper { + width: 100%; + margin-top: 20px; +} + +.charts-row { + display: flex; + flex-wrap: wrap; + justify-content: space-between; /* Distribuye el espacio entre los gráficos */ + gap: 20px; /* Añade espacio entre los gráficos */ +} + +.disk-usage { + text-align: center; + flex: 1; + min-width: 200px; /* Ajusta este valor según el tamaño mínimo deseado para cada gráfico */ +} + +.circular-chart { + max-width: 150px; + max-height: 150px; + margin: 0 auto; /* Centra el gráfico dentro del contenedor */ +} + +.chart { + display: flex; + justify-content: center; +} + + +.icon-pc { + font-size: 25px; + color: #3b82f6; +} + +.client-title h1 { + font-size: 2rem; + margin-bottom: 10px; +} + +.client-title p { + margin: 2px 0; + font-size: 1rem; + color: #666; +} + +.client-info { + margin: 20px 0; +} + +.form-container { + padding: 20px; + border-radius: 8px; +} + +.repository-form { + display: flex; + flex-direction: column; + gap: 15px; +} + +.save-button{ + align-self: flex-start; + width: 100px !important; + margin-top: 20px; +} + +.info-section { + margin-bottom: 30px; + background-color: #fff; + padding: 20px; + border-radius: 12px; +} + +.two-column-table { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-top: 15px; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +.table-row { + display: flex; + justify-content: space-between; + padding: 10px; + border-bottom: 1px solid #e0e0e0; +} + +.column.property { + font-weight: bold; + text-align: left; + width: 45%; +} + +.column.value { + text-align: right; + width: 45%; +} + +.mat-tab-group { + min-height: 400px; +} + +.mat-tab-body-wrapper { + min-height: inherit; +} + +.info-section h2 { + font-size: 1.4rem; + margin-bottom: 10px; + color: #0056b3; +} + +.second-section { + display: grid; + gap: 20px; +} + +.buttons-row { + display: flex; + flex-direction: column; + background-color: #fff; + padding: 20px; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + justify-content: flex-start; +} + +.buttons-row button { + margin-bottom: 10px; + width: 100%; +} + + +.disk-usage-info{ + display: flex; + justify-content: start; + margin-top: 10px; +} + +p { + margin-left: 15px; +}.dashboard { + padding: 20px; + } + +.disk-usage-container { + display: flex; + align-items: flex-start; + margin-bottom: 20px; +} + +.header-button-container { + display: flex; + justify-content: flex-end; + gap: 10px; +} + +.disk-usage { + flex: 2; + margin-right: 20px; +} + +.services-status { + flex: 1; +} + +.services-status ul { + list-style-type: none; + padding: 0; +} + +.services-status li { + margin: 5px 0; + display: flex; + align-items: center; +} + +.status-led { + width: 10px; + height: 10px; + border-radius: 50%; + display: inline-block; + margin-right: 10px; +} + +.status-led.active { + background-color: green; +} + +.status-led.inactive { + background-color: red; +} + +table { + width: 100%; + margin-top: 50px; +} + +.button-container { + display: flex; + flex-direction: column; + gap: 10px; /* Espacio entre botones */ + margin-top: 50px; +} + +.btn:first-child { + margin-left: 0; +} + +.btn:last-child { + margin-right: 0; +} + +.title { + font-size: 24px; +} + +.images-button-row { + display: flex; + justify-content: flex-start; + margin-top: 16px; +} + +.divider { + margin: 20px 0; +} + +table { + width: 100%; + margin-top: 50px; +} + +.search-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 0 5px; + box-sizing: border-box; +} + +.search-string { + flex: 2; + padding: 5px; +} + +.search-boolean { + flex: 1; + padding: 5px; +} + +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + height: 100px; + padding: 10px; + margin-top: 16px; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +.paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} + + + diff --git a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html new file mode 100644 index 0000000..5dea2dd --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.html @@ -0,0 +1,141 @@ + + +
+

OgRepository server Status

+
+
+

Uso de disco

+ + +
+

Total: {{ diskUsage.total }}

+

Ocupado: {{ diskUsage.used }}

+

Disponible: {{ diskUsage.available }}

+

Ocupado ( % ): {{ diskUsage.percentage }}

+
+
+ +
+

Servicios

+
    +
  • + + {{ service.name }}: + + Activo + Detenido + No accesible + {{ service.status }} + +
  • +
+
+
+
+
+ +
+
+ + +
+ +

Editar datos repositorio

+
+ +
+ +
+
+ + + Nombre del repositorio + + + + + Ip + + + + + Comentarios + + + + + +
+
+
+ + +
+

Imágenes

+
+ + Buscar nombre de imagen + + search + Pulsar 'enter' para buscar + +
+ +
{{ column.header }} + + + + + {{ image[column.columnDef] }} + + + + {{ image[column.columnDef] ? 'check_circle' : 'cancel' }} - + + + {{ column.cell(image) }} Acciones - - + + + + + + + +
+ + + + + + + + + + + +
{{ column.header }} + + + {{ image[column.columnDef] ? 'check_circle' : 'cancel' }} + + + + {{ column.cell(image) }} + + Acciones + + + + + + + +
+
+ + +
+
+ + diff --git a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.spec.ts b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.spec.ts new file mode 100644 index 0000000..fdb5818 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MainRepositoryViewComponent } from './main-repository-view.component'; + +describe('MainRepositoryViewComponent', () => { + let component: MainRepositoryViewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [MainRepositoryViewComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MainRepositoryViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts new file mode 100644 index 0000000..bc378c7 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/main-repository-view/main-repository-view.component.ts @@ -0,0 +1,323 @@ +import {Component, Inject} from '@angular/core'; +import {FormBuilder, FormGroup, Validators} from "@angular/forms"; +import {HttpClient} from "@angular/common/http"; +import {ToastrService} from "ngx-toastr"; +import {DataService} from "../../images/data.service"; +import {ActivatedRoute} from "@angular/router"; +import {MatTableDataSource} from "@angular/material/table"; +import {DatePipe} from "@angular/common"; +import {ServerInfoDialogComponent} from "../../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component"; +import {CreateImageComponent} from "../../images/create-image/create-image.component"; +import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/delete-modal.component"; +import {Observable} from "rxjs"; +import {MatDialog} from "@angular/material/dialog"; + +@Component({ + selector: 'app-main-repository-view', + templateUrl: './main-repository-view.component.html', + styleUrl: './main-repository-view.component.css' +}) +export class MainRepositoryViewComponent { + baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; + repositoryForm: FormGroup; + repositoryId: string | null = null; + dataSource = new MatTableDataSource(); + loading: boolean = true; + diskUsage: any = {}; + servicesStatus: any = {}; + diskUsageChartData: any[] = []; + alertMessage: string | null = null; + length: number = 0; + itemsPerPage: number = 10; + page: number = 0; + view: [number, number] = [800, 500]; + gradient: boolean = true; + showLegend: boolean = true; + showLabels: boolean = true; + isDoughnut: boolean = true; + repositoryData: any = {}; + colorScheme: any = { + domain: ['#FF6384', '#3f51b5'] + }; + + filters: { [key: string]: string } = {}; + datePipe: DatePipe = new DatePipe('es-ES'); + columns = [ + { + columnDef: 'id', + header: 'Id', + cell: (image: any) => `${image.id}` + }, + { + columnDef: 'name', + header: 'Nombre de imagen', + cell: (image: any) => `${image.name}` + }, + { + columnDef: 'imageRepository', + header: 'Repositorio', + cell: (image: any) => `${image.imageRepository?.name}` + }, + { + columnDef: 'remotePc', + header: 'Remote Pc', + cell: (image: any) => `${image.remotePc}` + }, + { + columnDef: 'created', + header: 'Creado en repositorio', + cell: (image: any) => `${image.created}` + }, + { + columnDef: 'imageFullsum', + header: 'Fullsum', + cell: (image: any) => `${image.imageFullsum}` + }, + { + columnDef: 'createdAt', + header: 'Fecha de creación', + cell: (image: any) => `${this.datePipe.transform(image.createdAt, 'dd/MM/yyyy hh:mm:ss')}` + } + ]; + displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; + private apiUrl = `${this.baseUrl}/images`; + + constructor( + private fb: FormBuilder, + private http: HttpClient, + private toastService: ToastrService, + private dataService: DataService, + private route: ActivatedRoute, + public dialog: MatDialog, + ) { + this.repositoryForm = this.fb.group({ + name: ['', Validators.required], + ip: [''], + comments: [''], + }); + } + + ngOnInit() { + this.repositoryId = this.route.snapshot.paramMap.get('id'); + this.loading = true + this.load() + this.loadStatus() + } + + load(): void { + const url = `${this.baseUrl}/image-repositories/${this.repositoryId}`; + this.http.get(url).subscribe( + (response: any) => { + this.repositoryData = response; + this.repositoryForm = this.fb.group({ + name: [response.name, Validators.required], + ip: [response.ip], + comments: [response.comments], + }); + this.loading = false; + // Llamar searchImages() solo cuando la data de repository esté cargada + this.searchImages(); + }, + (error) => { + console.error('Error al cargar los datos del cliente:', error); + this.loading = false; + } + ); + } + + save(): void { + const payload = { + name: this.repositoryForm.value.name, + ip: this.repositoryForm.value.ip, + comments: this.repositoryForm.value.comments, + }; + + if (this.repositoryId) { + this.http.put(`${this.baseUrl}/image-repositories/${this.repositoryId}`, payload).subscribe( + (response) => { + this.toastService.success('Imagen editada correctamente'); + }, + (error) => { + this.toastService.error(error['error']['hydra:description']); + console.error('Error al editar la imagen', error); + } + ); + } else { + this.http.post(`${this.baseUrl}/image-repositories`, payload).subscribe( + (response) => { + this.toastService.success('Imagen añadida correctamente'); + }, + (error) => { + this.toastService.error(error['error']['hydra:description']); + console.error('Error al añadir la imagen', error); + } + ); + } + } + + loadStatus(): void { + this.http.get(`${this.baseUrl}/image-repositories/server/${this.repositoryId}/status`).subscribe(data => { + const diskData = data.output.disk; + const servicesData = data.output.services; + + this.diskUsage = { + total: diskData.total, + used: diskData.used, + available: diskData.available, + percentage: diskData.used_percentage + }; + + this.diskUsageChartData = [ + { + name: 'Usado', + value: parseFloat(diskData.used) + }, + { + name: 'Disponible', + value: parseFloat(diskData.available) + } + ]; + + this.servicesStatus = servicesData; + + }, error => { + console.error('Error fetching status', error); + }); + } + + getServices(): { name: string, status: string }[] { + return Object.keys(this.servicesStatus).map(key => ({ + name: key, + status: this.servicesStatus[key] + })); + } + + searchImages(): void { + this.loading = true; + this.http.get(`${this.apiUrl}?repository.id=${this.repositoryData.id}&page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe( + data => { + this.dataSource.data = data['hydra:member']; + this.length = data['hydra:totalItems']; + this.loading = false; + }, + error => { + console.error('Error fetching images', error); + this.loading = false; + } + ); + } + + editImage(event: MouseEvent, image: any): void { + event.stopPropagation(); + this.dialog.open(CreateImageComponent, { + width: '800px', + data: image['@id'] + }).afterClosed().subscribe(() => this.searchImages()); + } + + deleteImage(image: any): void { + this.dialog.open(DeleteModalComponent, { + width: '300px', + data: { name: image.name }, + }).afterClosed().subscribe((result) => { + if (result) { + this.http.delete(`${this.apiUrl}/server/${image.uuid}/delete`).subscribe({ + next: () => { + this.toastService.success('Imagen eliminada con éxito'); + this.searchImages(); + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + console.error('Error al eliminar la imagen:', error); + } + }); + } + }); + } + + loadImageAlert(image: any): Observable { + return this.http.get(`${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 { + return this.http.get(`${this.baseUrl}/image-repositories/server/get-collection`); + } + + syncRepository() { + this.http.post(`${this.apiUrl}/sync`, {}) + .subscribe(response => { + this.toastService.success('Sincronización completada'); + this.load() + }, error => { + console.error('Error al sincronizar', error); + this.toastService.error('Error al sincronizar'); + }); + } + + + toggleAction(image: any, action:string): void { + switch (action) { + case 'get-aux': + this.http.post(`${this.baseUrl}/images/server/${image.uuid}/create-aux-files`, {}).subscribe({ + next: () => { + this.toastService.success('Petición de creación de archivos auxiliares enviada'); + this.searchImages() + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }); + break; + case 'delete': + this.deleteImage(image); + break; + default: + console.error('Acción no soportada:', action); + break; + } + } + + openImageInfoDialog() { + this.loadAlert().subscribe( + response => { + this.alertMessage = response.output; + + this.dialog.open(ServerInfoDialogComponent, { + width: '800px', + data: { + message: this.alertMessage + } + }); + }, + error => { + console.error('Error al cargar la información del alert', error); + } + ); + } +} diff --git a/ogWebconsole/src/app/layout/header/header.component.css b/ogWebconsole/src/app/layout/header/header.component.css index c551adc..dab15f9 100644 --- a/ogWebconsole/src/app/layout/header/header.component.css +++ b/ogWebconsole/src/app/layout/header/header.component.css @@ -10,6 +10,10 @@ mat-toolbar { background-color: #e0e0e0; } +.trace-button .mat-icon { + color: #f0f0f0; +} + .navbar-button-row { display: flex; justify-content: end; diff --git a/ogWebconsole/src/app/layout/header/header.component.html b/ogWebconsole/src/app/layout/header/header.component.html index cb6ac15..f42c5e8 100644 --- a/ogWebconsole/src/app/layout/header/header.component.html +++ b/ogWebconsole/src/app/layout/header/header.component.html @@ -4,6 +4,7 @@ menu