diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ec8f6a..6e572ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,25 @@ # Changelog +## [0.11.0] - 2025-4-11 +### Added +- Se ha diseñado el nuevo formulario para poder ejecutar script. Sistema mejorado con variables etiquetadas. +- Se puede añadir descripcion a una imagen. +- Se han añadido al formulario de crear/editar repositorio, la posibilidad de añadir usuario y puerto ssh. +- Nuevo estado en pc => desconectado. +- Se ha añadido nueva accion para renombrar imagen monolitica. + +### Improved +- Se ha mejorado la interfaz de usuario tanto para el despliegue de imagenes, como el particionado. +- Se ha mejorado la responsividad de la vista de grupos. +- Cambios en el comportamiento general de muchos componentes modales. Se han añadido spinners de carga mas intuitivos. + +--- + ## [0.10.1] - 2025-3-27 ### Improved - Mejoras en el comportamiento del arbol de grupos. - Nueva regexp para controlar las "macs" en la creacion de clientes. +--- ## [0.10.0] - 2025-3-25 ### Added - Nuevo componenten de estado global. @@ -20,14 +36,17 @@ ### Fixed - Cambios en la expresion regular para la validacion de documentos DHCP en la carga masiva de pc. +--- ## [0.9.2] - 2025-03-19 ### Changed - Jenkinsfile to pubilsh packages in repo in case og release +--- ## [0.9.1] - 2025-03-12 ### Changed - Se ha modificado el acceso a Mercure añadiendo nueva variable de entorno. +--- ## [0.9.0] - 2025-3-4 ### Added - Integracion con Mercure. Subscriber tanto en "Trazas" con en "Clientes". @@ -43,17 +62,20 @@ - Cambios en DHCP. Nueva UX en "ver clientes". Ahora tenemos un buscador detallado. - Para gestionar/añadir clientes a subredes ahora tenemos un botón para "añadir todos" y tan solo nos aparecn los equipos que no estén previamente asignados en una subred. +--- ## [0.7.0] - 2024-12-10 ### Refactored - Refactored the group screen, removing the separate tabs for clients, advanced search, and organizational units. - Added support for partitioning functionality in the client detail view. +--- ## [0.6.1] - 2024-11-19 ### Improved - Introduced a new automatic sync mode for the ogdhcp and ogBoot components. - Improve test coverage. - New view for clients inside the classroom on the main page. +--- ## [0.6.0] - 2024-11-19 ### Added - Added functionality to execute actions from the menu in the general groups screen. diff --git a/ogWebconsole/src/app/app-routing.module.ts b/ogWebconsole/src/app/app-routing.module.ts index c2e3a06..cbd353a 100644 --- a/ogWebconsole/src/app/app-routing.module.ts +++ b/ogWebconsole/src/app/app-routing.module.ts @@ -40,6 +40,9 @@ import {EnvVarsComponent} from "./components/admin/env-vars/env-vars.component"; import {MenusComponent} from "./components/menus/menus.component"; import {OgDhcpSubnetsComponent} from "./components/ogdhcp/og-dhcp-subnets.component"; import {StatusComponent} from "./components/ogdhcp/status/status.component"; +import { + RunScriptAssistantComponent +} from "./components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component"; const routes: Routes = [ { path: '', redirectTo: 'auth/login', pathMatch: 'full' }, { path: '', component: MainLayoutComponent, @@ -63,6 +66,7 @@ const routes: Routes = [ { path: 'calendars', component: CalendarComponent }, { path: 'clients/deploy-image', component: DeployImageComponent }, { path: 'clients/partition-assistant', component: PartitionAssistantComponent }, + { path: 'clients/run-script', component: RunScriptAssistantComponent }, { path: 'clients/:id', component: ClientMainViewComponent }, { path: 'clients/:id/create-image', component: CreateClientImageComponent }, { path: 'repositories', component: RepositoriesComponent }, diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 3e5a1e6..2bd3f99 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -118,7 +118,6 @@ import { CreateMultipleClientComponent } from './components/groups/shared/client import { ExportImageComponent } from './components/images/export-image/export-image.component'; import { ImportImageComponent } from "./components/repositories/import-image/import-image.component"; import { LoadingComponent } from './shared/loading/loading.component'; -import { RepositoryImagesComponent } from './components/repositories/repository-images/repository-images.component'; import { InputDialogComponent } from './components/commands/commands-task/task-logs/input-dialog/input-dialog.component'; import { ManageOrganizationalUnitComponent } from './components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component'; import { BackupImageComponent } from './components/repositories/backup-image/backup-image.component'; @@ -134,9 +133,16 @@ import { ConvertImageComponent } from './components/repositories/convert-image/c import { registerLocaleData } from '@angular/common'; import localeEs from '@angular/common/locales/es'; import { GlobalStatusComponent } from './components/global-status/global-status.component'; -import { ShowImagesComponent } from './components/repositories/show-images/show-images.component'; +import { ShowMonoliticImagesComponent } from './components/repositories/show-monolitic-images/show-monolitic-images.component'; import { StatusTabComponent } from './components/global-status/status-tab/status-tab.component'; import { ConvertImageToVirtualComponent } from './components/repositories/convert-image-to-virtual/convert-image-to-virtual.component'; +import { RunScriptAssistantComponent } from './components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component'; +import { + SaveScriptComponent +} from "./components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component"; +import { EditImageComponent } from './components/repositories/edit-image/edit-image.component'; +import { ShowGitImagesComponent } from './components/repositories/show-git-images/show-git-images.component'; +import { RenameImageComponent } from './components/repositories/rename-image/rename-image.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); @@ -223,7 +229,6 @@ registerLocaleData(localeEs, 'es-ES'); ExportImageComponent, ImportImageComponent, LoadingComponent, - RepositoryImagesComponent, InputDialogComponent, ManageOrganizationalUnitComponent, BackupImageComponent, @@ -231,9 +236,14 @@ registerLocaleData(localeEs, 'es-ES'); OperationResultDialogComponent, ConvertImageComponent, GlobalStatusComponent, - ShowImagesComponent, + ShowMonoliticImagesComponent, StatusTabComponent, - ConvertImageToVirtualComponent + ConvertImageToVirtualComponent, + RunScriptAssistantComponent, + SaveScriptComponent, + EditImageComponent, + ShowGitImagesComponent, + RenameImageComponent ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts index d2282f5..60a233f 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts +++ b/ogWebconsole/src/app/components/commands/commands-task/task-logs/task-logs.component.ts @@ -228,7 +228,6 @@ export class TaskLogsComponent implements OnInit { this.http.get(`${this.baseUrl}/commands?&page=1&itemsPerPage=10000`).subscribe( response => { this.commands = response['hydra:member']; - console.log(this.commands); this.loading = false; }, error => { diff --git a/ogWebconsole/src/app/components/commands/main-commands/commands.component.ts b/ogWebconsole/src/app/components/commands/main-commands/commands.component.ts index 460064f..4993b29 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/commands.component.ts +++ b/ogWebconsole/src/app/components/commands/main-commands/commands.component.ts @@ -85,14 +85,14 @@ export class CommandsComponent implements OnInit { openCreateCommandModal(): void { this.dialog.open(CreateCommandComponent, { - width: '600px', + width: '800px', }).afterClosed().subscribe(() => this.search()); } editCommand(event: MouseEvent, command: any): void { event.stopPropagation(); this.dialog.open(CreateCommandComponent, { - width: '600px', + width: '800px', data: command['@id'] }).afterClosed().subscribe(() => this.search()); } diff --git a/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.css b/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.css index c45022f..d1f01f8 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.css +++ b/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.css @@ -57,4 +57,15 @@ justify-content: flex-end; gap: 1em; padding: 1.5em; -} \ No newline at end of file +} + +.checkbox-with-hint { + display: flex; + flex-direction: column; +} + +.hint-text { + font-size: 12px; + color: gray; + margin-left: 40px; +} diff --git a/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.html b/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.html index 6fe8575..5ae0b55 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.html +++ b/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.html @@ -13,7 +13,13 @@
{{ 'readOnlyLabel' | translate }} + {{ 'enabledLabel' | translate }} + +
+ {{ 'parameters' | translate }} + Si se selecciona esta opción los parámetros deben indicarse en el script con el símbolo @. +
diff --git a/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.ts b/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.ts index d0e74a2..c7de763 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.ts +++ b/ogWebconsole/src/app/components/commands/main-commands/create-command/create-command.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject } from '@angular/core'; +import {Component, Inject, OnInit} from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { HttpClient } from '@angular/common/http'; @@ -11,7 +11,7 @@ import { ConfigService } from "@services/config.service"; templateUrl: './create-command.component.html', styleUrls: ['./create-command.component.css'] }) -export class CreateCommandComponent { +export class CreateCommandComponent implements OnInit{ baseUrl: string; createCommandForm: FormGroup; commandId: string | null = null; @@ -30,6 +30,7 @@ export class CreateCommandComponent { name: ['', Validators.required], script: [''], readOnly: [false], + parameters: [false], enabled: [true], comments: [''], }); @@ -44,12 +45,12 @@ export class CreateCommandComponent { load(): void { this.dataService.getCommand(this.data).subscribe({ next: (response) => { - console.log(response); this.createCommandForm = this.fb.group({ name: [response.name, Validators.required], notes: [response.notes], script: [response.script], readOnly: [response.readOnly], + parameters: [response.parameters], enabled: [response.enabled], }); this.commandId = response['@id']; @@ -84,7 +85,6 @@ export class CreateCommandComponent { }, (error) => { this.toastService.error(error['error']['hydra:description']); - console.error('Error al editar el comando', error); } ); } else { @@ -95,7 +95,6 @@ export class CreateCommandComponent { }, (error) => { this.toastService.error(error['error']['hydra:description']); - console.error('Error al añadir comando', error); } ); } diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.html b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.html index 1d6eb62..d68c2bd 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.html +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.html @@ -11,8 +11,9 @@ - - \ No newline at end of file + diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts index bc0fd01..4c5aff9 100644 --- a/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/execute-command.component.ts @@ -29,7 +29,7 @@ export class ExecuteCommandComponent implements OnInit { { name: 'Particionar y Formatear', slug: 'partition', disabled: false }, { name: 'Inventario Software', slug: 'software-inventory', disabled: true }, { name: 'Inventario Hardware', slug: 'hardware-inventory', disabled: true }, - { name: 'Ejecutar script', slug: 'run-script', disabled: true }, + { name: 'Ejecutar comando', slug: 'run-script', disabled: false }, ]; client: any = {}; @@ -60,6 +60,14 @@ export class ExecuteCommandComponent implements OnInit { this.openDeployImageAssistant(); } + if (action === 'run-script') { + this.openRunScriptAssistant(); + } + + if (action === 'login') { + this.loginClient(); + } + if (action === 'reboot') { this.rebootClient(); } @@ -86,6 +94,19 @@ export class ExecuteCommandComponent implements OnInit { ); } + loginClient(): void { + this.http.post(`${this.baseUrl}/clients/server/login-client`, { + clients: this.clientData.map((client: any) => client['@id']) + }).subscribe( + response => { + this.toastService.success('Cliente actualizado correctamente'); + }, + error => { + this.toastService.error('Error de conexión con el cliente'); + } + ); + } + powerOnClient(): void { this.http.post(`${this.baseUrl}/image-repositories/wol`, { clients: this.clientData.map((client: any) => client['@id']) @@ -113,8 +134,18 @@ export class ExecuteCommandComponent implements OnInit { } openPartitionAssistant(): void { + const clientDataToSend = this.clientData.map(client => ({ + name: client.name, + mac: client.mac, + uuid: '/clients/'+client.uuid, + status: client.status, + partitions: client.partitions, + firmwareType: client.firmwareType, + ip: client.ip + })); + this.router.navigate(['/clients/partition-assistant'], { - state: { clientData: this.clientData }, + queryParams: { clientData: JSON.stringify(clientDataToSend) } }).then(r => { console.log('Navigated to partition assistant with data:', this.clientData); }); @@ -127,10 +158,38 @@ export class ExecuteCommandComponent implements OnInit { } openDeployImageAssistant(): void { + const clientDataToSend = this.clientData.map(client => ({ + name: client.name, + mac: client.mac, + uuid: '/clients/'+client.uuid, + status: client.status, + partitions: client.partitions, + ip: client.ip + })); + this.router.navigate(['/clients/deploy-image'], { - state: { clientData: this.clientData }, + queryParams: { clientData: JSON.stringify(clientDataToSend) } }).then(r => { console.log('Navigated to deploy image with data:', this.clientData); }); } + + openRunScriptAssistant(): void { + const clientDataToSend = this.clientData.map(client => ({ + name: client.name, + mac: client.mac, + uuid: '/clients/'+client.uuid, + status: client.status, + partitions: client.partitions, + ip: client.ip + })); + + this.router.navigate(['/clients/run-script'], { + queryParams: { clientData: JSON.stringify(clientDataToSend) } + }).then(() => { + console.log('Navigated to run script with data:', clientDataToSend); + }); + } + + } diff --git a/ogWebconsole/src/app/components/global-status/global-status.component.ts b/ogWebconsole/src/app/components/global-status/global-status.component.ts index 9991495..0b0206c 100644 --- a/ogWebconsole/src/app/components/global-status/global-status.component.ts +++ b/ogWebconsole/src/app/components/global-status/global-status.component.ts @@ -106,25 +106,27 @@ export class GlobalStatusComponent implements OnInit { const timeoutId = setTimeout(() => { this.loading = false; this.repositories.forEach(repository => { - this.errorRepositories[repository.uuid] = true; + if (!(repository.uuid in this.errorRepositories)) { + this.errorRepositories[repository.uuid] = true; + } }); }, 5000); + this.http.get(`${this.repositoriesUrl}?page=1&itemsPerPage=10`).subscribe( data => { this.repositories = data['hydra:member']; let remainingRepositories = this.repositories.length; + this.repositories.forEach(repository => { this.loadRepositoryStatus(repository.uuid, (errorOccurred: boolean) => { remainingRepositories--; + + this.errorRepositories[repository.uuid] = errorOccurred; + if (remainingRepositories === 0) { this.loading = false; clearTimeout(timeoutId); } - if (errorOccurred) { - this.errorRepositories[repository.uuid] = true; - } else { - this.errorRepositories[repository.uuid] = false; - } }); }); }, @@ -138,7 +140,7 @@ export class GlobalStatusComponent implements OnInit { } ); } - + loadRepositoryStatus(repositoryUuid: string, callback: (errorOccurred: boolean) => void): void { const timeoutId = setTimeout(() => { callback(true); diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css index c9e5bef..664c4d0 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/client-main-view.component.css @@ -42,23 +42,19 @@ 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 { @@ -84,6 +80,11 @@ .client-info { margin: 20px 0; + border-radius: 12px; + background-color: #f5f7fa; + padding: 20px; + border: 2px solid #d1d9e6; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); } .info-section { @@ -148,9 +149,7 @@ 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 */ } .buttons-row { @@ -235,29 +234,22 @@ animation: progress 1s ease-out forwards; } -/* Define colores distintos para cada partición */ .partition-0 { stroke: #00bfa5; } -/* Ejemplo: verde */ .partition-1 { stroke: #ff6f61; } -/* Ejemplo: rojo */ .partition-2 { stroke: #ffb400; } -/* Ejemplo: amarillo */ .partition-3 { stroke: #3498db; } -/* Ejemplo: azul */ - -/* Texto en el centro del gráfico */ .percentage { fill: #333; font-size: 0.7rem; @@ -276,14 +268,14 @@ box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); flex-wrap: wrap; justify-content: center; - align-items: center; /* Centra contenido verticalmente */ + align-items: stretch; margin-bottom: 20px; } .table-container { - flex: 1; + flex: 3; display: flex; - justify-content: center; /* Centrar la tabla */ + justify-content: center; align-items: center; } @@ -296,7 +288,7 @@ table.mat-elevation-z8 { } .mat-header-cell { - background-color: #d1d9e6 !important; /* Encabezado más moderno */ + background-color: #d1d9e6 !important; color: #333; font-weight: bold; text-align: center; @@ -312,11 +304,11 @@ table.mat-elevation-z8 { } .charts-container { - flex: 1; + flex: 2; display: flex; - flex-wrap: wrap; - justify-content: center; /* Centra los gráficos */ - gap: 20px; + flex-direction: column; + align-items: center; + justify-content: center; } .disk-usage { 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 bfda90e..f0c7970 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 @@ -34,7 +34,6 @@
-
@@ -55,7 +54,6 @@
-
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 51edd4d..bf77392 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 @@ -48,7 +48,7 @@ export class ClientMainViewComponent implements OnInit { { name: 'Particionar y Formatear', slug: 'partition' }, { name: 'Inventario Software', slug: 'software-inventory' }, { name: 'Inventario Hardware', slug: 'hardware-inventory' }, - { name: 'Ejecutar script', slug: 'run-script' }, + { name: 'Ejecutar comando', slug: 'run-script' }, ]; datePipe: DatePipe = new DatePipe('es-ES'); 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 index bafb062..53d269b 100644 --- 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 @@ -24,6 +24,7 @@ table { width: 100%; margin-top: 50px; + background-color: #eaeff6; } .search-container { @@ -36,18 +37,26 @@ table { } .select-container { - margin-top: 20px; - align-items: center; + gap: 16px; width: 100%; - padding: 0 5px; + box-sizing: border-box; + padding: 20px; +} + +.selector { + display: flex; + gap: 16px; + width: 100%; + margin-top: 30px; box-sizing: border-box; } -.custom-width { - width: 50%; - margin-bottom: 16px; +.half-width { + flex: 1; + max-width: 50%; } + .search-string { flex: 2; padding: 5px; @@ -87,3 +96,10 @@ table { padding-right: 1em; } +.partition-table-container { + background-color: #eaeff6; + padding: 20px; + border-radius: 12px; + margin-top: 20px; +} + 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 index 72a9280..2b2936b 100644 --- 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 @@ -7,39 +7,53 @@
- +
- - Nombre canónico - - +
+ + Nombre canónico + + + + + Seleccione imagen + + {{ image?.name }} + + Seleccione la imagen para sobreescribir si se requiere. + +
+ + +
+ + + + + + + + + + + + + +
Seleccionar partición + + + + + {{ column.header }} + {{ column.cell(image) }} +
+
+
- - - - - - - - - - - - - - -
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.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts index bf03038..d165c52 100644 --- 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 @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Output } from '@angular/core'; +import {Component, EventEmitter, OnInit, Output} from '@angular/core'; import { HttpClient } from "@angular/common/http"; import { ToastrService } from "ngx-toastr"; import { ActivatedRoute, Router } from "@angular/router"; @@ -11,7 +11,7 @@ import { ConfigService } from '@services/config.service'; templateUrl: './create-image.component.html', styleUrl: './create-image.component.css' }) -export class CreateClientImageComponent { +export class CreateClientImageComponent implements OnInit{ baseUrl: string; @Output() dataChange = new EventEmitter(); @@ -24,6 +24,7 @@ export class CreateClientImageComponent { name: string = ''; client: any = null; loading: boolean = false; + selectedImage: any = null; dataSource = new MatTableDataSource(); columns = [ { @@ -103,14 +104,29 @@ export class CreateClientImageComponent { ); } + resetCanonicalName() { + this.name = this.selectedImage ? this.selectedImage.name : ''; + } + save(): void { this.loading = true; + if (!this.selectedPartition) { + this.toastService.error('Debes seleccionar una partición'); + this.loading = false; + return; + } + + if (this.selectedImage) { + this.toastService.warning('Aviso: Está seleccionando una imagen previamente creada. Se procede a crear un backup de la misma. '); + } + const payload = { client: `/clients/${this.clientId}`, name: this.name, partition: this.selectedPartition['@id'], - source: 'assistant' + source: 'assistant', + selectedImage: this.selectedImage?.['@id'] }; 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 index 06eaade..f932c98 100644 --- 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 @@ -6,6 +6,7 @@ table { width: 100%; margin-top: 50px; + background-color: #eaeff6; } .search-container { @@ -38,10 +39,8 @@ table { .select-container { margin-top: 20px; align-items: center; - width: 100%; - padding: 0 5px; + padding: 20px; box-sizing: border-box; - padding-left: 1em; } .input-group { @@ -51,6 +50,10 @@ table { margin-top: 20px; } +mat-option .unit-name { + display: block; +} + .input-field { flex: 1 1 calc(33.33% - 16px); min-width: 250px; @@ -107,6 +110,27 @@ table { position: relative; padding: 8px; text-align: center; + cursor: pointer; + transition: background-color 0.3s, transform 0.2s; + + &:hover { + background-color: #f0f0f0; + transform: scale(1.02); + } +} + +::ng-deep .custom-tooltip { + white-space: pre-line !important; + max-width: 200px; + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 8px; + border-radius: 4px; +} + +.selected-client { + background-color: #a0c2e5 !important; /* Azul */ + color: white !important; } .client-details { @@ -114,11 +138,15 @@ table { } .client-name { - display: block; - font-size: 1.2em; + font-size: 0.9em; font-weight: 600; color: #333; margin-bottom: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; + display: inline-block; } .client-ip { @@ -137,3 +165,34 @@ table { display: flex; padding-right: 1em; } + +.partition-table-container { + background-color: #eaeff6; + padding: 20px; + border-radius: 12px; + margin-top: 20px; +} + +.disabled-client { + pointer-events: none; + opacity: 0.5; +} + +.error-message { + background-color: #de2323; + padding: 20px; + border-radius: 12px; + margin-top: 20px; + color: white; + font-weight: bold; +} + +.action-button { + margin-top: 10px; + margin-bottom: 10px; +} + +.mat-expansion-panel-header-description { + justify-content: space-between; + align-items: center; +} 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 index b2e3a4a..a967fef 100644 --- 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 @@ -7,34 +7,60 @@
- +
- + Clientes - Listado de clientes donde se desplegará la imagen + + Listado de clientes donde se desplegará la imagen + desktop_windows + -
-
-
- Client Icon +
+ +
-
- {{ client.name }} - {{ client.ip }} - {{ client.mac }} -
+
+
+
+ + Client Icon + +
+ {{ client.name | slice:0:20 }} + {{ client.ip }} + {{ client.mac }}
+ + + + + Modelo + +
+
+
@@ -45,96 +71,106 @@ Seleccione imagen - {{ image.image?.name }} + +
{{ image.name }}
+
{{ image.description }}
+
- Imágenes alojadas en {{ clientData[0].repository?.name }}
Seleccione método de deploy - - {{ method }} + + {{ method.name }}
+ +
+ {{ errorMessage }} +
+ +
+ + + + + + + + + + + + + +
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 + + +
+
+
- - - - - - - - - - - - - -
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 index 4d10254..c56b048 100644 --- 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 @@ -10,14 +10,15 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatTableModule } from '@angular/material/table'; import { MatDividerModule } from '@angular/material/divider'; -import { MatRadioModule } from '@angular/material/radio'; +import { MatRadioModule } from '@angular/material/radio'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { TranslateModule } from '@ngx-translate/core'; import { ToastrModule, ToastrService } from 'ngx-toastr'; -import { provideRouter } from '@angular/router'; +import { ActivatedRoute, provideRouter } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { MatSelectModule } from '@angular/material/select'; -import {MatExpansionModule} from "@angular/material/expansion"; -import {LoadingComponent} from "../../../../../shared/loading/loading.component"; +import { MatExpansionModule } from "@angular/material/expansion"; +import { LoadingComponent } from "../../../../../shared/loading/loading.component"; import { ConfigService } from '@services/config.service'; describe('DeployImageComponent', () => { @@ -63,13 +64,25 @@ describe('DeployImageComponent', () => { provide: MAT_DIALOG_DATA, useValue: {} }, + { + provide: ActivatedRoute, + useValue: { + queryParams: { + subscribe: (fn: (value: any) => void) => fn({ clientData: JSON.stringify([{ '@id': '123', uuid: 'client-uuid', status: 'og-live', partitions: [] }]) }) + } + } + }, { provide: ConfigService, useValue: mockConfigService } - ] + ], + schemas: [NO_ERRORS_SCHEMA] }) - .compileComponents(); + .compileComponents(); fixture = TestBed.createComponent(DeployImageComponent); component = fixture.componentInstance; + component.clientData = [{ '@id': '123', uuid: 'client-uuid', status: 'og-live', partitions: [] }]; + component.selectedModelClient = component.clientData[0]; + component.loadPartitions(component.selectedModelClient); fixture.detectChanges(); }); 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 index 040456a..9f1620f 100644 --- 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 @@ -19,7 +19,6 @@ export class DeployImageComponent { clientId: string | null = null; partitions: any[] = []; images: any[] = []; - clientName: string = ''; selectedImage: any = null; selectedMethod: string | null = null; selectedPartition: any = null; @@ -31,10 +30,10 @@ export class DeployImageComponent { mcastMaxTime: Number = 0; p2pMode: string = ''; p2pTime: Number = 0; - name: string = ''; client: any = null; clientData: any = []; loading: boolean = false; + allSelected = true; protected p2pModeOptions = [ { name: 'Leecher', value: 'leecher' }, @@ -46,13 +45,17 @@ export class DeployImageComponent { { name: 'Full duplex', value: "full" }, ]; + selectedClients: any[] = []; + selectedModelClient: any = null; + filteredPartitions: any[] = []; + selectedRepository: any = null; + allMethods = [ - 'uftp', - 'udpcast', - 'udpcast-direct', - 'unicast', - 'unicast-direct', - 'p2p' + { name: 'Multicast', value: 'udpcast' }, + { name: 'Unicast', value: 'unicast' }, + { name: 'Multicast (direct)', value: 'udpcast-direct' }, + { name: 'Unicast (direct)', value: 'unicast-direct' }, + { name: 'Torrent', value: 'p2p' }, ]; dataSource = new MatTableDataSource(); @@ -92,55 +95,109 @@ export class DeployImageComponent { private toastService: ToastrService, private configService: ConfigService, private router: Router, + private route: ActivatedRoute ) { this.baseUrl = this.configService.apiUrl; - const navigation = this.router.getCurrentNavigation(); - this.clientData = navigation?.extras?.state?.['clientData']; - this.clientId = this.clientData?.[0]['@id']; - this.loadImages(); - this.loadPartitions() + this.route.queryParams.subscribe(params => { + if (params['clientData']) { + this.clientData = JSON.parse(params['clientData']); + } + }); + this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null; + this.clientData.forEach((client: { selected: boolean; status: string}) => { + if (client.status === 'og-live') { + client.selected = true; + } + }); + this.selectedClients = this.clientData.filter( + (client: { status: string }) => client.status === 'og-live' + ); + + this.selectedModelClient = this.clientData.find( + (client: { status: string }) => client.status === 'og-live' + ) || null; + + if (this.selectedModelClient) { + this.loadPartitions(this.selectedModelClient); + } } isMethod(method: string): boolean { return this.selectedMethod === method; } - loadPartitions() { - const url = `${this.baseUrl}${this.clientId}`; - this.http.get(url).subscribe( - (response: any) => { - if (response.partitions) { - this.client = response; - this.clientName = response.name; - this.dataSource.data = response.partitions.filter((partition: any) => { - return partition.partitionNumber !== 0; - }); - this.p2pMode = response.organizationalUnit?.networkSettings?.p2pMode; - this.p2pTime = response.organizationalUnit?.networkSettings?.p2pTime; - this.mcastSpeed = response.organizationalUnit?.networkSettings?.mcastSpeed; - this.mcastMode = response.organizationalUnit?.networkSettings?.mcastMode; - this.mcastPort = response.organizationalUnit?.networkSettings?.mcastPort; - this.mcastIp = response.organizationalUnit?.networkSettings?.mcastIp; - } - }, - (error) => { - console.error('Error al cargar los datos del cliente:', error); - } + toggleClientSelection(client: any) { + client.selected = !client.selected; + this.updateSelectedClients(); + } + + updateSelectedClients() { + this.selectedClients = this.clientData.filter( + (client: { selected: boolean; state: string }) => client.selected && client.state === "og-live" ); + + if (!this.selectedClients.includes(this.selectedModelClient)) { + this.selectedModelClient = null; + this.filteredPartitions = []; + } + } + + getPartitionsTooltip(client: any): string { + if (!client.partitions || client.partitions.length === 0) { + return 'No hay particiones disponibles'; + } + + return client.partitions + .map((p: { partitionNumber: any; size: any; filesystem: any }) => `#${p.partitionNumber} ${p.filesystem} - ${p.size / 1024 }GB`) + .join('\n'); + } + + loadPartitions(client: any) { + if (client.selected) { + this.http.get(`${this.baseUrl}${client.uuid}`).subscribe( + (fullClientData: any) => { + this.filteredPartitions = fullClientData.partitions; + this.selectedRepository = fullClientData.repository ?? null; + + if (fullClientData.partitions) { + this.filteredPartitions = fullClientData.partitions.filter((partition: any) => { + return partition.partitionNumber !== 0; + }); + this.p2pMode = fullClientData.organizationalUnit?.networkSettings?.p2pMode; + this.p2pTime = fullClientData.organizationalUnit?.networkSettings?.p2pTime; + this.mcastSpeed = fullClientData.organizationalUnit?.networkSettings?.mcastSpeed; + this.mcastMode = fullClientData.organizationalUnit?.networkSettings?.mcastMode; + this.mcastPort = fullClientData.organizationalUnit?.networkSettings?.mcastPort; + this.mcastIp = fullClientData.organizationalUnit?.networkSettings?.mcastIp; + } + + this.loadImages(); + }, + (error) => { + console.error('Error al cargar los datos completos del cliente:', error); + } + ); + } else { + this.selectedClients = this.selectedClients.filter(c => c.uuid !== client.uuid); + this.filteredPartitions = []; + } + } + + + toggleSelectAll() { + this.allSelected = !this.allSelected; + this.clientData.forEach((client: { selected: boolean; status: string }) => { + if (client.status === "og-live") { + client.selected = this.allSelected; + } + }); } loadImages() { - if (!this.clientData || this.clientData.length === 0 || !this.clientData[0]) { - console.error('Error: clientData es nulo, indefinido o vacío.'); - return; - } - - const repositoryId = - this.clientData[0]?.repository?.id ?? - this.clientData[0]?.organizationalUnit?.networkSettings?.repository?.id; + const repositoryId = this.selectedRepository?.id; if (!repositoryId) { - console.error('Error: No se encontró repositoryId en clientData.'); + console.error('Error: No se encontró repositoryId en el cliente seleccionado.'); return; } @@ -156,9 +213,27 @@ export class DeployImageComponent { ); } + validateImageSize() { + if (this.selectedImage && this.selectedPartition) { + if ((this.selectedImage.datasize / 1024) / 1024 > this.selectedPartition.size) { + + this.errorMessage = "El tamaño de la imagen seleccionada excede el tamaño de la partición."; + return false; + } + } + this.errorMessage = ""; + return true; + } + save(): void { this.loading = true; + if (!this.selectedClients.length) { + this.toastService.error('Debe seleccionar al menos un cliente'); + this.loading = false; + return; + } + if (!this.selectedImage) { this.toastService.error('Debe seleccionar una imagen'); this.loading = false; @@ -180,7 +255,7 @@ export class DeployImageComponent { this.toastService.info('Preparando petición de despliegue'); const payload = { - clients: this.clientData.map((client: any) => client['@id']), + clients: this.selectedClients.map((client: any) => client.uuid), method: this.selectedMethod, // partition: this.selectedPartition['@id'], diskNumber: this.selectedPartition.diskNumber, @@ -203,7 +278,6 @@ export class DeployImageComponent { this.router.navigate(['/commands-logs']); }, error: (error) => { - console.error('Error:', error); this.toastService.error(error.error['hydra:description'], 'Se ha detectado un error en el despliegue de imágenes.', { "closeButton": true, "newestOnTop": false, @@ -215,7 +289,6 @@ export class DeployImageComponent { }); this.loading = false; } - } - ); + }); } } diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css index 020cb8b..1a1ea81 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css @@ -1,8 +1,8 @@ .partition-assistant { - font-family: 'Roboto', sans-serif; - background-color: #f9f9f9; - padding: 20px; - margin: 20px auto; + padding: 40px; + margin: 20px; + background-color: #eaeff6; + border-radius: 12px; } .header-container { @@ -19,40 +19,14 @@ color: #555; } -.partition-bar { - display: flex; - margin: 20px 0; - height: 40px; - border-radius: 8px; - overflow: hidden; - background-color: #e0e0e0; - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1); -} - -.partition-segment { - text-align: center; - color: white; - line-height: 40px; - font-weight: 500; - font-size: 0.9rem; - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2); - border-right: 2px solid white; /* Borde de separación */ -} - -.partition-segment:last-child { - border-right: none; -} - .partition-table { width: 100%; border-collapse: collapse; - background-color: #fff; overflow: hidden; margin-bottom: 20px; } .partition-table th { - background-color: #f5f5f5; color: #333; padding: 12px; font-weight: 600; @@ -178,26 +152,20 @@ button.remove-btn:hover { position: relative; } -.client-card { - background: #ffffff; - border-radius: 6px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - overflow: hidden; - position: relative; - padding: 8px; - text-align: center; -} - .client-details { margin-top: 4px; } .client-name { - display: block; - font-size: 1.2em; + font-size: 0.9em; font-weight: 600; color: #333; margin-bottom: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; + display: inline-block; } .client-ip { @@ -216,9 +184,68 @@ button.remove-btn:hover { margin-top: 20px; align-items: center; width: 100%; - padding: 0 5px; box-sizing: border-box; padding-left: 1em; } +.client-card { + background: #ffffff; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; + position: relative; + padding: 8px; + text-align: center; + cursor: pointer; + transition: background-color 0.3s, transform 0.2s; + + &:hover { + background-color: #f0f0f0; + transform: scale(1.02); + } +} + +::ng-deep .custom-tooltip { + white-space: pre-line !important; + max-width: 200px; + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 8px; + border-radius: 4px; +} + +.selected-client { + background-color: #a0c2e5 !important; + color: white !important; +} + +.button-row { + display: flex; + padding-right: 1em; +} + +.disabled-client { + pointer-events: none; + opacity: 0.5; +} + +.row-button { + display: flex; + align-items: center; + gap: 30px; +} + + +.action-button { + margin-top: 10px; + margin-bottom: 10px; +} + +.mat-expansion-panel-header-description { + justify-content: space-between; + align-items: center; +} + + + 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 a5ee1b8..100ace3 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 @@ -7,27 +7,55 @@
- +
- + Clientes - Listado de clientes donde se realizará el particionado + + Listado de clientes donde se realizará el particionado + desktop_windows + +
+ +
+
-
- Client Icon +
+ + Client Icon
{{ client.name }} {{ client.ip }} {{ client.mac }}
+ + + + Modelo + + +
@@ -49,15 +77,11 @@
-
-
- {{ partition.partitionCode }} ({{ (partition.size / 1024).toFixed(2) }} GB) -
-
-
+
+ + Tabla de particiones: {{ selectedModelClient.firmwareType }} +
@@ -119,5 +143,3 @@
- -
{{ errorMessage }}
\ No newline at end of file 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 365df59..a6416f8 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 @@ -1,11 +1,9 @@ import {Component, EventEmitter, Inject, Input, OnInit, Output} from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { ToastrService } from 'ngx-toastr'; -import {MAT_DIALOG_DATA} from "@angular/material/dialog"; import {ActivatedRoute, Router} from "@angular/router"; import { PARTITION_TYPES } from '../../../../../shared/constants/partition-types'; import { FILESYSTEM_TYPES } from '../../../../../shared/constants/filesystem-types'; -import {toUnredirectedSourceFile} from "@angular/compiler-cli/src/ngtsc/util/src/typescript"; import { ConfigService } from '@services/config.service'; interface Partition { @@ -47,6 +45,9 @@ export class PartitionAssistantComponent { view: [number, number] = [400, 300]; showLegend = true; showLabels = true; + allSelected = true; + selectedClients: any[] = []; + selectedModelClient: any = null; constructor( private http: HttpClient, @@ -57,19 +58,40 @@ export class PartitionAssistantComponent { ) { this.baseUrl = this.configService.apiUrl; this.apiUrl = this.baseUrl + '/partitions'; - const navigation = this.router.getCurrentNavigation(); - this.clientData = navigation?.extras?.state?.['clientData']; - this.clientId = this.clientData[0]['@id']; - this.loadPartitions(); + this.route.queryParams.subscribe(params => { + if (params['clientData']) { + this.clientData = JSON.parse(params['clientData']); + } + }); + this.clientId = this.clientData?.[0]['@id']; + this.clientData.forEach((client: { selected: boolean; status: string}) => { + if (client.status === 'og-live') { + client.selected = true; + } + }); + this.selectedClients = this.clientData.filter( + (client: { status: string }) => client.status === 'og-live' + ); + + this.selectedModelClient = this.clientData.find( + (client: { status: string }) => client.status === 'og-live' + ) || null; + + if (this.selectedModelClient) { + this.loadPartitions(this.selectedModelClient); + } } get selectedDisk():any { return this.disks.find(disk => disk.diskNumber === this.selectedDiskNumber) || null; } - loadPartitions() { - const url = `${this.baseUrl}${this.clientId}`; + loadPartitions(client: any) { + if (!client.selected) { + this.selectedModelClient = null; + } + const url = `${this.baseUrl}${client.uuid}`; this.http.get(url).subscribe( (response) => { this.data = response; @@ -81,7 +103,17 @@ export class PartitionAssistantComponent { ); } + toggleSelectAll() { + this.allSelected = !this.allSelected; + this.clientData.forEach((client: { selected: boolean; status: string }) => { + if (client.status === "og-live") { + client.selected = this.allSelected; + } + }); + } + initializeDisks() { + this.disks = []; const partitionsFromData = this.data.partitions; this.originalPartitions = JSON.parse(JSON.stringify(partitionsFromData)); @@ -137,15 +169,6 @@ export class PartitionAssistantComponent { return bytes } - activePartitions(diskNumber: number) { - const disk = this.disks.find((d) => d.diskNumber === diskNumber); - if (disk) { - return disk.partitions.filter((partition) => !partition.removed); - } - - return null; - } - updatePartitionPercentages(partitions: Partition[], totalDiskSize: number) { let totalUsedPercentage = 0; @@ -178,11 +201,31 @@ export class PartitionAssistantComponent { } } + toggleClientSelection(client: any) { + client.selected = !client.selected; + this.updateSelectedClients(); + } + + updateSelectedClients() { + this.selectedClients = this.clientData.filter((client: { selected: any; }) => client.selected); + } + + getPartitionsTooltip(client: any): string { + if (!client.partitions || client.partitions.length === 0) { + return 'No hay particiones disponibles'; + } + + return client.partitions + .map((p: { partitionNumber: any; size: any; filesystem: any }) => `#${p.partitionNumber} ${p.filesystem} - ${p.size / 1024 }GB`) + .join('\n'); + } + addPartition(diskNumber: number) { const disk = this.disks.find((d) => d.diskNumber === diskNumber); if (disk) { const remainingGB = this.getRemainingGB(disk.partitions, disk.totalDiskSize); + if (remainingGB > 0) { const removedPartitions = disk.partitions.filter((p) => !p.removed); const maxPartitionNumber = @@ -205,7 +248,7 @@ export class PartitionAssistantComponent { this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); this.updateDiskChart(disk); } else { - this.errorMessage = 'No hay suficiente espacio libre en el disco para crear una nueva partición.'; + this.toastService.error('No hay suficiente espacio libre en el disco para crear una nueva partición.'); } } } @@ -237,28 +280,9 @@ export class PartitionAssistantComponent { return Math.max(0, totalDiskSize - totalUsedGB); } - getModifiedOrNewPartitions() { - const modifiedPartitions: any[] = []; - - this.disks.forEach((disk) => { - disk.partitions.forEach((partition) => { - const originalPartition = this.originalPartitions.find( - (p) => p.diskNumber === disk.diskNumber && p.partitionNumber === partition.partitionNumber - ); - modifiedPartitions.push({ - partition, - diskNumber: disk.diskNumber, - partitionNumber: partition.partitionNumber, - }); - }); - }); - - return modifiedPartitions; - } - save() { if (!this.selectedDisk) { - this.errorMessage = 'Por favor selecciona un disco antes de guardar.'; + this.toastService.error('No se ha seleccionado un disco.'); return; } @@ -267,7 +291,7 @@ export class PartitionAssistantComponent { const totalPartitionSize = this.selectedDisk.partitions.reduce((sum: any, partition: { size: any; }) => sum + partition.size, 0); if (totalPartitionSize > this.selectedDisk.totalDiskSize) { - this.errorMessage = 'El tamaño total de las particiones en el disco seleccionado excede el tamaño total del disco.'; + this.toastService.error('El tamaño total de las particiones en el disco seleccionado excede el tamaño total del disco.'); this.loading = false; return; } @@ -276,7 +300,7 @@ export class PartitionAssistantComponent { if (modifiedPartitions.length === 0) { this.loading = false; - this.errorMessage = 'No hay cambios para guardar en el disco seleccionado.'; + this.toastService.info('No hay cambios para guardar en el disco seleccionado.'); return; } @@ -295,7 +319,7 @@ export class PartitionAssistantComponent { if (newPartitions.length > 0) { const bulkPayload = { partitions: newPartitions, - clients: this.clientData.map((client: any) => client['@id']), + clients: this.selectedClients.map((client: any) => client.uuid), }; this.http.post(this.apiUrl, bulkPayload).subscribe( @@ -305,7 +329,6 @@ export class PartitionAssistantComponent { this.router.navigate(['/commands-logs']); }, (error) => { - console.error('Error al crear las particiones:', error); this.loading = false; this.toastService.error('Error al crear las particiones.'); } diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.css new file mode 100644 index 0000000..1f6be0a --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.css @@ -0,0 +1,264 @@ + +.divider { + margin: 20px 0; +} + +table { + width: 100%; + margin-top: 50px; +} + +.search-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + box-sizing: border-box; +} + +.deploy-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + gap: 10px; +} + +.script-container { + display: flex; + flex-wrap: wrap; + gap: 20px; + padding: 20px; + background-color: #eaeff6; + border-radius: 12px; + margin-top: 20px; +} + +.script-content { + flex: 2; + min-width: 60%; +} + +.script-params { + flex: 1; + min-width: 35%; +} + +@media (max-width: 768px) { + .script-container { + flex-direction: column; + } + + .script-content, .script-params { + min-width: 100%; + } +} + +.select-container { + margin-top: 20px; + align-items: center; + padding: 20px; + 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; +} + +.script-preview { + background-color: #f4f4f4; + border: 1px solid #ccc; + padding: 10px; + border-radius: 5px; + font-family: monospace; + white-space: pre-wrap; + min-height: 50px; +} + +.custom-width { + width: 50%; + 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 10px; + border-bottom: 1px solid #ddd; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +.paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} + +.clients-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 8px; +} + +.client-item { + position: relative; +} + +.client-card { + background: #ffffff; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; + position: relative; + padding: 8px; + text-align: center; + cursor: pointer; + transition: background-color 0.3s, transform 0.2s; + + &:hover { + background-color: #f0f0f0; + transform: scale(1.02); + } +} + +.client-details { + margin-top: 4px; +} + +.client-name { + font-size: 0.9em; + font-weight: 600; + color: #333; + margin-bottom: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; + display: inline-block; +} + +.client-ip { + display: block; + font-size: 0.9em; + color: #666; +} + +.header-container-title { + flex-grow: 1; + text-align: left; + padding-left: 1em; +} + +.button-row { + display: flex; + padding-right: 1em; +} + +.client-card { + background: #ffffff; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; + position: relative; + padding: 8px; + text-align: center; + cursor: pointer; + transition: background-color 0.3s, transform 0.2s; + + &:hover { + background-color: #f0f0f0; + transform: scale(1.02); + } +} + +::ng-deep .custom-tooltip { + white-space: pre-line !important; + max-width: 200px; + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 8px; + border-radius: 4px; +} + +.selected-client { + background-color: #a0c2e5 !important; + color: white !important; +} + +.button-row { + display: flex; + padding-right: 1em; +} + +.disabled-client { + pointer-events: none; + opacity: 0.5; +} + +.action-button { + margin-top: 10px; + margin-bottom: 10px; +} + +.mat-expansion-panel-header-description { + justify-content: space-between; + align-items: center; +} + +.new-command-container { + display: flex; + flex-direction: column; + gap: 10px; + padding: 15px; + background-color: #eaeff6; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); + margin-top: 15px; +} + +.new-command-container mat-form-field { + width: 100%; +} + +.new-command-container textarea { + font-family: monospace; + resize: vertical; +} + +.new-command-container .action-button { + align-self: flex-end; + color: white; + padding: 8px 16px; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.full-width { + width: 100%; +} + + diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html new file mode 100644 index 0000000..e41eea9 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html @@ -0,0 +1,103 @@ + + +
+
+

+ {{ 'runScript' | translate }} +

+
+
+ +
+
+ + +
+ + + Clientes + + Listado de clientes donde se ejecutara el script seleccionado + desktop_windows + + + +
+ +
+ +
+
+
+ + Client Icon + +
+ {{ client.name }} + {{ client.ip }} + {{ client.mac }} +
+
+
+
+
+
+ + + +
+
+ + Comando nuevo + Comando existente + +
+ +
+ + Ingrese el script + + + +
+ +
+ + Seleccione script a ejecutar + + {{ script.name }} + + +
+ +
+
+

Script:

+
+
+ +
+

Ingrese los valores de los parámetros detectados:

+
+ + {{ paramName }} + + +
+
+
+
+ diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.spec.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.spec.ts new file mode 100644 index 0000000..8b6edc1 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.spec.ts @@ -0,0 +1,99 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RunScriptAssistantComponent } from './run-script-assistant.component'; +import { DeployImageComponent } from "../deploy-image/deploy-image.component"; +import { LoadingComponent } from "../../../../../shared/loading/loading.component"; +import { FormBuilder, FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from "@angular/material/dialog"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatCheckboxModule } from "@angular/material/checkbox"; +import { MatExpansionModule } from "@angular/material/expansion"; +import { MatButtonModule } from "@angular/material/button"; +import { MatTableModule } from "@angular/material/table"; +import { MatDividerModule } from "@angular/material/divider"; +import { MatRadioModule } from "@angular/material/radio"; +import { MatSelectModule } from "@angular/material/select"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { ToastrModule, ToastrService } from "ngx-toastr"; +import { TranslateModule } from "@ngx-translate/core"; +import { provideHttpClient } from "@angular/common/http"; +import { provideHttpClientTesting } from "@angular/common/http/testing"; +import { provideRouter } from "@angular/router"; +import { ConfigService } from "@services/config.service"; +import { TranslateLoader } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import {MatIconModule} from "@angular/material/icon"; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http); +} + +describe('RunScriptAssistantComponent', () => { + let component: RunScriptAssistantComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url', + mercureUrl: 'http://mock-mercure-url' + }; + + await TestBed.configureTestingModule({ + declarations: [RunScriptAssistantComponent, DeployImageComponent, LoadingComponent], + imports: [ + ReactiveFormsModule, + FormsModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + MatCheckboxModule, + MatExpansionModule, + MatButtonModule, + MatTableModule, + MatDividerModule, + MatRadioModule, + MatProgressSpinnerModule, + MatSelectModule, + BrowserAnimationsModule, + MatIconModule, + ToastrModule.forRoot(), + HttpClientTestingModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }) + ], + providers: [ + FormBuilder, + ToastrService, + provideHttpClient(), + provideHttpClientTesting(), + provideRouter([]), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: {} + }, + { provide: ConfigService, useValue: mockConfigService } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(RunScriptAssistantComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts new file mode 100644 index 0000000..83bb0d0 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts @@ -0,0 +1,177 @@ +import {Component, EventEmitter, Output} from '@angular/core'; +import {SelectionModel} from "@angular/cdk/collections"; +import {HttpClient} from "@angular/common/http"; +import {ToastrService} from "ngx-toastr"; +import {ConfigService} from "@services/config.service"; +import {ActivatedRoute, Router} from "@angular/router"; +import {SaveScriptComponent} from "./save-script/save-script.component"; +import {MatDialog} from "@angular/material/dialog"; + +@Component({ + selector: 'app-run-script-assistant', + templateUrl: './run-script-assistant.component.html', + styleUrl: './run-script-assistant.component.css' +}) +export class RunScriptAssistantComponent { + baseUrl: string; + @Output() dataChange = new EventEmitter(); + + errorMessage = ''; + clientId: string | null = null; + name: string = ''; + client: any = null; + clientData: any = []; + loading: boolean = false; + scripts: any[] = []; + scriptContent: string = ""; + parameters: any = {}; + selectedScript: any = null; + selectedClients: any[] = []; + allSelected: boolean = true; + commandType: string = 'existing'; + newScript: string = ''; + selection = new SelectionModel(true, []); + parameterNames: string[] = Object.keys(this.parameters); + + constructor( + private http: HttpClient, + private toastService: ToastrService, + private configService: ConfigService, + private router: Router, + private dialog: MatDialog, + private route: ActivatedRoute + ) { + this.baseUrl = this.configService.apiUrl; + this.route.queryParams.subscribe(params => { + if (params['clientData']) { + this.clientData = JSON.parse(params['clientData']); + } + }); + this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null; + this.clientData.forEach((client: { selected: boolean; status: string}) => { + if (client.status === 'og-live') { + client.selected = true; + } + }); + this.selectedClients = this.clientData.filter( + (client: { status: string }) => client.status === 'og-live' + ); + this.loadScripts() + } + + loadScripts(): void { + this.loading = true; + + this.http.get(`${this.baseUrl}/commands?readOnly=false&enabled=true`).subscribe((data: any) => { + this.scripts = data['hydra:member']; + this.loading = false; + }, (error) => { + this.toastService.error(error.error['hydra:description']); + this.loading = false; + }); + } + + saveNewScript() { + if (!this.newScript.trim()) { + this.toastService.error('Debe ingresar un script antes de guardar.'); + return; + } + const dialogRef = this.dialog.open(SaveScriptComponent, { + width: '400px', + data: this.newScript + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.toastService.success('Script guardado correctamente'); + } + }); + } + + toggleClientSelection(client: any) { + client.selected = !client.selected; + this.updateSelectedClients(); + } + + updateSelectedClients() { + this.selectedClients = this.clientData.filter( + (client: { selected: boolean; status: string }) => client.selected && client.status === "og-live" + ); + } + + toggleSelectAll() { + this.allSelected = !this.allSelected; + this.clientData.forEach((client: { selected: boolean; status: string }) => { + if (client.status === "og-live") { + client.selected = this.allSelected; + } + }); + } + + getPartitionsTooltip(client: any): string { + if (!client.partitions || client.partitions.length === 0) { + return 'No hay particiones disponibles'; + } + + return client.partitions + .map((p: { partitionNumber: any; size: any; filesystem: any }) => `#${p.partitionNumber} ${p.filesystem} - ${p.size / 1024 }GB`) + .join('\n'); + } + + onScriptChange() { + if (this.selectedScript) { + this.scriptContent = this.selectedScript.script; + + const matches = this.scriptContent.match(/@(\w+)/g) || []; + const uniqueParams = Array.from(new Set(matches.map(m => m.slice(1)))); + + this.parameters = {}; + uniqueParams.forEach(param => this.parameters[param] = ''); + + this.parameterNames = uniqueParams; + + this.updateScript(); + } + } + + onParamChange(name: string, value: string): void { + this.parameters[name] = value; + this.updateScript(); + } + + updateScript(): void { + let updatedScript = this.selectedScript.script; + + for (const [key, value] of Object.entries(this.parameters)) { + const regex = new RegExp(`@${key}\\b`, 'g'); + updatedScript = updatedScript.replace(regex, value || `@${key}`); + } + + this.scriptContent = updatedScript; + } + + trackByIndex(index: number): number { + return index; + } + + + save(): void { + this.loading = true; + + this.http.post(`${this.baseUrl}/commands/run-script`, { + clients: this.selectedClients.map((client: any) => client.uuid), + script: this.commandType === 'existing' ? this.scriptContent : this.newScript, + }).subscribe( + response => { + this.toastService.success('Script ejecutado correctamente'); + this.dataChange.emit(); + this.router.navigate(['/commands-logs']); + }, + error => { + this.toastService.error('Error al ejecutar el script'); + } + ); + + this.loading = false; + } +} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.css new file mode 100644 index 0000000..be10ae6 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.css @@ -0,0 +1,40 @@ +.dialog-content { + display: flex; + flex-direction: column; + padding: 40px; +} + +.repository-form { + width: 100%; + display: flex; + flex-direction: column; +} + +.full-width{ + width: 100%; + margin-top: 16px; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} + +@media (max-width: 600px) { + .form-field { + width: 100%; + } + + .dialog-actions { + flex-direction: column; + align-items: stretch; + } + + button { + width: 100%; + margin-left: 0; + margin-bottom: 8px; + } +} diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.html new file mode 100644 index 0000000..2b318e1 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.html @@ -0,0 +1,12 @@ +

Guardar Comando

+ +

Introduce un nombre para el comando:

+ + Nombre del Comando + + +
+
+ + +
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.spec.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.spec.ts new file mode 100644 index 0000000..860d305 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.spec.ts @@ -0,0 +1,94 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import {RunScriptAssistantComponent} from "../run-script-assistant.component"; +import {DeployImageComponent} from "../../deploy-image/deploy-image.component"; +import {LoadingComponent} from "../../../../../../shared/loading/loading.component"; +import {FormBuilder, FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatInputModule} from "@angular/material/input"; +import {MatCheckboxModule} from "@angular/material/checkbox"; +import {MatExpansionModule} from "@angular/material/expansion"; +import {MatButtonModule} from "@angular/material/button"; +import {MatTableModule} from "@angular/material/table"; +import {MatDividerModule} from "@angular/material/divider"; +import {MatRadioModule} from "@angular/material/radio"; +import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; +import {MatSelectModule} from "@angular/material/select"; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {MatIconModule} from "@angular/material/icon"; +import {ToastrModule, ToastrService} from "ngx-toastr"; +import {HttpClientTestingModule, provideHttpClientTesting} from "@angular/common/http/testing"; +import {TranslateLoader, TranslateModule} from "@ngx-translate/core"; +import {HttpClient, provideHttpClient} from "@angular/common/http"; +import {provideRouter} from "@angular/router"; +import {ConfigService} from "@services/config.service"; +import {HttpLoaderFactory} from "../run-script-assistant.component.spec"; +import {SaveScriptComponent} from "./save-script.component"; + +describe('SaveScriptComponent', () => { + let component: SaveScriptComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url', + mercureUrl: 'http://mock-mercure-url' + }; + + await TestBed.configureTestingModule({ + declarations: [RunScriptAssistantComponent, DeployImageComponent, LoadingComponent], + imports: [ + ReactiveFormsModule, + FormsModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + MatCheckboxModule, + MatExpansionModule, + MatButtonModule, + MatTableModule, + MatDividerModule, + MatRadioModule, + MatProgressSpinnerModule, + MatSelectModule, + BrowserAnimationsModule, + MatIconModule, + ToastrModule.forRoot(), + HttpClientTestingModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }) + ], + providers: [ + FormBuilder, + ToastrService, + provideHttpClient(), + provideHttpClientTesting(), + provideRouter([]), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: {} + }, + { provide: ConfigService, useValue: mockConfigService } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SaveScriptComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.ts new file mode 100644 index 0000000..2fde430 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component.ts @@ -0,0 +1,51 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {FormBuilder, Validators} from "@angular/forms"; +import {HttpClient} from "@angular/common/http"; +import {ToastrService} from "ngx-toastr"; +import {ConfigService} from "@services/config.service"; +import {DataService} from "../../../../../commands/main-commands/data.service"; + +@Component({ + selector: 'app-save-script', + templateUrl: './save-script.component.html', + styleUrl: './save-script.component.css' +}) +export class SaveScriptComponent { + commandName: string = ''; + baseUrl: string; + + constructor( + private http: HttpClient, + public dialogRef: MatDialogRef, + private toastService: ToastrService, + private configService: ConfigService, + private dataService: DataService, + @Inject(MAT_DIALOG_DATA) public data: any + ) { + this.baseUrl = this.configService.apiUrl; + } + + save() { + const payload = { + name: this.commandName, + script: this.data, + readOnly: false, + enabled: true, + }; + + this.http.post(`${this.baseUrl}/commands`, payload).subscribe( + (response) => { + this.toastService.success('Comando añadido correctamente'); + this.dialogRef.close(); + }, + (error) => { + this.toastService.error(error['error']['hydra:description']); + } + ); + } + + close() { + this.dialogRef.close(); + } +} diff --git a/ogWebconsole/src/app/components/groups/groups.component.css b/ogWebconsole/src/app/components/groups/groups.component.css index cc550ac..f4a5383 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.css +++ b/ogWebconsole/src/app/components/groups/groups.component.css @@ -22,6 +22,96 @@ overflow: hidden; } +.clients-mat-divider { + display: none; +} + +@media (max-width: 992px) { + .main-container { + display: flex !important; + flex-direction: column !important; + width: 100% !important; + height: 100% !important; + overflow: hidden !important; + } + + .filters-and-tree-container { + display: flex !important; + flex-direction: row !important; + flex-grow: 1 !important; + flex-shrink: 1 !important; + width: 100% !important; + max-width: none !important; + min-width: 0 !important; + padding: 1rem !important; + box-sizing: border-box !important; + min-height: 250px !important; + overflow-y: auto !important; + } + + .filters-panel, + .tree-container { + flex: 1 1 50% !important; + overflow-y: auto !important; + padding: 0.5rem !important; + box-sizing: border-box !important; + margin-bottom: 0 !important; + } + + .filters-container { + padding: 0 !important; + } + + .tree-mat-divider { + display: none !important; + } + + .clients-mat-divider { + display: inline; + } + + .clients-view-header { + display: flex; + flex-direction: row !important; + justify-content: space-between !important; + margin-bottom: 0.5rem !important; + margin-top: 0.5rem !important; + align-items: center !important; + padding-right: 1rem !important; + } + + .groups-button-row { + display: none !important; + } + + .cards-view { + max-height: none !important; + } + + .clients-table { + max-height: unset !important; + overflow: unset !important; + display: table !important; + flex-direction: unset !important; + } + + .clients-container { + padding: 0em 1em 0em 1em !important; + } +} + +@media (max-width: 1400px) { + .type-view-text { + display: none; + } +} + +@media (max-width: 450px) { + .clients-title-name { + font-size: 20px !important; + } +} + .header-container-title { flex-grow: 1; text-align: left; @@ -34,15 +124,6 @@ padding-right: 0.5rem; } -.clients-container { - flex-grow: 1; - box-sizing: border-box; - overflow: hidden; - display: flex; - flex-direction: column; - padding: 0rem 1rem 0rem 0.5rem; -} - .clients-view-header { display: flex; flex-direction: row; @@ -53,6 +134,32 @@ padding-right: 1rem; } +@media (max-width: 768px) { + .clients-view-header { + display: flex; + flex-direction: column !important; + } +} + +.clients-title-name { + font-size: x-large; + display: block; + padding: 1rem 1rem 1rem 13px; + margin-left: 0.6rem; + flex: 1 1 auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.view-type-container { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; + flex-shrink: 0; +} + .clients-view { flex-grow: 1; overflow-y: auto; @@ -75,16 +182,35 @@ padding-left: 1rem; } +.list-view { + overflow-x: auto; +} + .clients-table { max-height: calc(100vh - 330px); overflow: auto; display: flex; flex-direction: column; + width: 100%; + table-layout: auto; + border-collapse: collapse; } -.clients-table table { - flex-grow: 1; - overflow: auto; +.clients-table th, +.clients-table td { + text-align: left; + padding: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.clients-table th { + font-weight: 500; +} + +.clients-table td { + border-bottom: 1px solid #ddd; } .paginator-container { @@ -124,18 +250,6 @@ flex: 1; } -@media (max-width: 1024px) { - .header-container { - flex-direction: column; - gap: 10px; - } - - .groups-button-row { - flex-wrap: wrap; - gap: 10px; - } -} - mat-tree { background-color: #f9f9f9; padding: 0px 10px 10px 10px; @@ -265,21 +379,36 @@ mat-tree mat-tree-node.disabled:hover { padding-right: 1rem; } -.filters-container { +.clients-container { + flex: 8; + box-sizing: border-box; + overflow: hidden; display: flex; flex-direction: column; - justify-content: start; - padding: 1em 1em 0em 1em; -} - -.filter-form-field { - min-width: 21rem; + padding: 0rem 1rem 0rem 0.5rem; } .filters-and-tree-container { + flex: 2; display: flex; flex-direction: column; - height: 100%; + flex-shrink: 1; + width: 100%; + box-sizing: border-box; +} + +.filters-container { + display: flex; + flex-direction: column; + padding: 1em; + flex-grow: 1; +} + +.filter-form-field { + flex: 1; + min-width: 150px; + max-width: 100%; + box-sizing: border-box; } .filters-panel { @@ -327,6 +456,10 @@ mat-tree mat-tree-node.disabled:hover { margin-top: 4px; } +.type-view-text { + margin-left: 0.5vw; +} + .action-icons { display: flex; justify-content: center; @@ -373,22 +506,6 @@ mat-tree mat-tree-node.disabled:hover { position: relative; } -@media (max-width: 1560px) { - .clients-view-header { - display: flex; - flex-direction: column; - margin-bottom: 1rem; - margin-top: 0; - } -} - -.clients-title-name { - font-size: x-large; - display: block; - padding: 1rem 1rem 1rem 13px; - margin-left: 0.6rem; -} - .no-clients-info { display: flex; align-items: center; @@ -397,13 +514,6 @@ mat-tree mat-tree-node.disabled:hover { margin-left: 1.6rem; } -.view-type-container { - display: flex; - justify-content: flex-end; - gap: 2rem; - align-items: center; -} - mat-button-toggle-group { border: none; } diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index f6d651a..a54f2d3 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -25,6 +25,27 @@ {{ 'legendButton' | translate }}
+ + +
+ + + + + + + +
@@ -72,7 +93,7 @@ - +
- +
-
- - {{ 'clients' | translate }} - {{ selectedNode?.name }} - -
+ + {{ 'clients' | translate }} + {{ selectedNode?.name }} +
- list {{ 'Vista Lista' | translate }} + list {{ 'Vista Lista' | translate }} - grid_view {{ 'Vista Tarjeta' | translate }} + grid_view {{ 'Vista Tarjeta' | translate }}
@@ -226,10 +245,10 @@
+ [checked]="selection.isSelected(client)" [disabled]="client.status === 'busy' || client.status === 'off' || client.status === 'disconnected'"> - Client Icon + Client Icon
{{ client.name }} @@ -295,7 +314,7 @@ + [checked]="selection.isSelected(row)" [disabled]="row.status === 'busy' || row.status === 'off' || row.status === 'disconnected'"> @@ -304,7 +323,7 @@
- Client Icon @@ -317,7 +336,7 @@ {{ 'name' | translate }} -

{{ client.name }}

+

{{ client.name }}

@@ -359,7 +378,8 @@ more_vert + [disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"> + + +
diff --git a/ogWebconsole/src/app/components/repositories/edit-image/edit-image.component.spec.ts b/ogWebconsole/src/app/components/repositories/edit-image/edit-image.component.spec.ts new file mode 100644 index 0000000..e5898f3 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/edit-image/edit-image.component.spec.ts @@ -0,0 +1,72 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, provideHttpClientTesting } from '@angular/common/http/testing'; +import { EditImageComponent } from './edit-image.component'; +import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog'; +import { ToastrModule, ToastrService } from 'ngx-toastr'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FormBuilder } from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { provideHttpClient } from '@angular/common/http'; +import { ConfigService } from '@services/config.service'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +describe('EditImageComponent', () => { + let component: EditImageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url' + }; + + const mockDialogData = { + image: { + '@id': 'mock-id', + name: 'Mock Image', + description: 'Mock Description' + } + }; + + await TestBed.configureTestingModule({ + declarations: [EditImageComponent], + imports: [ + HttpClientTestingModule, + MatDialogModule, + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + ToastrModule.forRoot(), + BrowserAnimationsModule + ], + providers: [ + FormBuilder, + ToastrService, + provideHttpClient(), + provideHttpClientTesting(), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: mockDialogData + }, + { + provide: ConfigService, + useValue: mockConfigService + } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(EditImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/ogWebconsole/src/app/components/repositories/edit-image/edit-image.component.ts b/ogWebconsole/src/app/components/repositories/edit-image/edit-image.component.ts new file mode 100644 index 0000000..9ed426c --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/edit-image/edit-image.component.ts @@ -0,0 +1,70 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {ToastrService} from "ngx-toastr"; +import {Router} from "@angular/router"; +import {ConfigService} from "@services/config.service"; +import {FormBuilder, FormGroup} from "@angular/forms"; + +@Component({ + selector: 'app-edit-image', + templateUrl: './edit-image.component.html', + styleUrl: './edit-image.component.css' +}) +export class EditImageComponent implements OnInit{ + baseUrl: string; + loading: boolean = true; + imageForm: FormGroup; + + constructor( + private http: HttpClient, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private toastService: ToastrService, + private router: Router, + private configService: ConfigService, + @Inject(MAT_DIALOG_DATA) public data: { image: any } + ) { + this.baseUrl = this.configService.apiUrl; + this.imageForm = this.fb.group({ + description: [''] + }) + } + + ngOnInit(): void { + this.loading = true; + this.load() + } + + load() { + this.http.get(`${this.baseUrl}${this.data.image['@id']}`).subscribe({ + next: (response) => { + this.imageForm.patchValue({ + description: response.description + }); + this.loading = false; + }, + error: error => { + this.toastService.error(error.error['hydra:description']); + } + }); + } + + save() { + const payload = this.imageForm.value; + + this.http.put(`${this.baseUrl}${this.data.image['@id']}`, payload).subscribe({ + next: (response) => { + this.toastService.success('Image updated successfully'); + this.dialogRef.close(true); + }, + error: error => { + this.toastService.error(error.error['hydra:description']); + } + }); + } + + close() { + this.dialogRef.close(); + } +} diff --git a/ogWebconsole/src/app/components/repositories/manage-repository/manage-repository.component.html b/ogWebconsole/src/app/components/repositories/manage-repository/manage-repository.component.html index 1edb619..b9dfea4 100644 --- a/ogWebconsole/src/app/components/repositories/manage-repository/manage-repository.component.html +++ b/ogWebconsole/src/app/components/repositories/manage-repository/manage-repository.component.html @@ -12,6 +12,18 @@ + + Puerto ssh + + Valor que utilizará el sistema para algunas acciones como transferir imagenes. + + + + Usuario + + Este usuario se utilizara para conectarse al repositorio. + + Comentarios diff --git a/ogWebconsole/src/app/components/repositories/manage-repository/manage-repository.component.ts b/ogWebconsole/src/app/components/repositories/manage-repository/manage-repository.component.ts index d570f90..4e2b71d 100644 --- a/ogWebconsole/src/app/components/repositories/manage-repository/manage-repository.component.ts +++ b/ogWebconsole/src/app/components/repositories/manage-repository/manage-repository.component.ts @@ -30,6 +30,8 @@ export class ManageRepositoryComponent implements OnInit { this.imageForm = this.fb.group({ name: [null, Validators.required], ip: [null], + sshPort: [null], + user: [null], comments: [null], }); } @@ -46,6 +48,8 @@ export class ManageRepositoryComponent implements OnInit { this.imageForm = this.fb.group({ name: [response.name, Validators.required], ip: [response.ip], + sshPort: [response.sshPort?? '22' ], + user: [response.user?? 'opengnsys'], comments: [response.comments], }); this.repositoryId = response['@id']; @@ -60,6 +64,8 @@ export class ManageRepositoryComponent implements OnInit { const payload = { name: this.imageForm.value.name, ip: this.imageForm.value.ip, + sshPort: this.imageForm.value.sshPort, + user: this.imageForm.value.user, comments: this.imageForm.value.comments, }; diff --git a/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.css b/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.css new file mode 100644 index 0000000..bdecbfa --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.css @@ -0,0 +1,39 @@ +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100px; +} + +mat-form-field { + width: 100%; +} + +mat-dialog-actions { + display: flex; + justify-content: flex-end; +} + +.checkbox-group { + display: flex; + flex-direction: column; + gap: 10px; +} + +.selected-list ul { + list-style: none; + padding: 0; +} + +.selected-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; + border-bottom: 1px solid #ccc; +} + +.selected-item button { + margin-left: 10px; +} diff --git a/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.html b/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.html new file mode 100644 index 0000000..e0d4297 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.html @@ -0,0 +1,13 @@ +

Renombrar imagen {{ data.imageImageRepository?.name }}

+ + + + Nuevo nombre + + + + +
+ + +
diff --git a/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.spec.ts b/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.spec.ts new file mode 100644 index 0000000..715058c --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.spec.ts @@ -0,0 +1,54 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RenameImageComponent } from './rename-image.component'; +import {ConvertImageToVirtualComponent} from "../convert-image-to-virtual/convert-image-to-virtual.component"; +import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog"; +import {ToastrModule} from "ngx-toastr"; +import {provideHttpClient} from "@angular/common/http"; +import {provideHttpClientTesting} from "@angular/common/http/testing"; +import {ConfigService} from "@services/config.service"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; + +describe('RenameImageComponent', () => { + let component: RenameImageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url' + }; + await TestBed.configureTestingModule({ + declarations: [RenameImageComponent], + imports: [ + MatDialogModule, + ToastrModule.forRoot() + ], + providers: [ + provideHttpClient(), + provideHttpClientTesting(), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: {} + }, + { + provide: ConfigService, + useValue: mockConfigService + } + ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + + fixture = TestBed.createComponent(RenameImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.ts b/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.ts new file mode 100644 index 0000000..9557357 --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/rename-image/rename-image.component.ts @@ -0,0 +1,51 @@ +import {Component, EventEmitter, Inject, OnInit, Output} from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {ToastrService} from "ngx-toastr"; +import {Router} from "@angular/router"; +import {ConfigService} from "@services/config.service"; + +@Component({ + selector: 'app-rename-image', + templateUrl: './rename-image.component.html', + styleUrl: './rename-image.component.css' +}) +export class RenameImageComponent implements OnInit{ + baseUrl: string; + loading: boolean = true; + name: string = ''; + @Output() eventEmitter = new EventEmitter(); + + constructor( + private http: HttpClient, + public dialogRef: MatDialogRef, + private toastService: ToastrService, + private router: Router, + private configService: ConfigService, + @Inject(MAT_DIALOG_DATA) public data: { imageImageRepository: any } + ) { + this.baseUrl = this.configService.apiUrl; + } + + ngOnInit(): void { + this.loading = true; + } + + save() { + this.http.post(`${this.baseUrl}${this.data.imageImageRepository['@id']}/rename-image`, { + newName: this.name + }).subscribe({ + next: (response) => { + this.toastService.success('Imagen renombrada correctamente'); + this.dialogRef.close(true); + }, + error: error => { + this.toastService.error(error.error['hydra:description']); + } + }); + } + + close() { + this.dialogRef.close(); + } +} diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.html b/ogWebconsole/src/app/components/repositories/repositories.component.html index 2f33a45..3ddf7ec 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.html +++ b/ogWebconsole/src/app/components/repositories/repositories.component.html @@ -5,7 +5,7 @@ help
-

+

{{ 'repositoryTitle' | translate }}

@@ -42,7 +42,8 @@ - + + diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.ts b/ogWebconsole/src/app/components/repositories/repositories.component.ts index 6443e59..b53de26 100644 --- a/ogWebconsole/src/app/components/repositories/repositories.component.ts +++ b/ogWebconsole/src/app/components/repositories/repositories.component.ts @@ -9,7 +9,8 @@ import { JoyrideService } from 'ngx-joyride'; import { Router } from '@angular/router'; import { ConfigService } from '@services/config.service'; import {Subnet} from "../ogdhcp/og-dhcp-subnets.component"; -import {ShowImagesComponent} from "./show-images/show-images.component"; +import {ShowMonoliticImagesComponent} from "./show-monolitic-images/show-monolitic-images.component"; +import {ShowGitImagesComponent} from "./show-git-images/show-git-images.component"; import {ManageRepositoryComponent} from "./manage-repository/manage-repository.component"; @Component({ @@ -38,6 +39,11 @@ export class RepositoriesComponent implements OnInit { header: 'Nombre de repositorio', cell: (repository: any) => `${repository.name}` }, + { + columnDef: 'user', + header: 'Usuario', + cell: (repository: any) => `${repository.user?? 'opengnsys'}` + }, { columnDef: 'ip', header: 'Ip', @@ -54,7 +60,8 @@ export class RepositoriesComponent implements OnInit { cell: (repository: any) => `${this.datePipe.transform(repository.createdAt, 'dd/MM/yyyy hh:mm:ss')}` } ]; - displayedColumns: string[] = ['id', 'name', 'ip', 'images', 'createdAt', 'actions']; + isGitModuleInstalled: boolean = true; + displayedColumns: string[] = ['id', 'name', 'ip', 'user', 'images', 'createdAt', 'actions']; constructor( public dialog: MatDialog, @@ -91,7 +98,6 @@ export class RepositoriesComponent implements OnInit { this.loading = false; }, error => { - console.error('Error fetching images', error); this.loading = false; } ); @@ -125,12 +131,12 @@ export class RepositoriesComponent implements OnInit { }); } - openShowImagesDialog(repository: Subnet) { - const dialogRef = this.dialog.open(ShowImagesComponent, { - width: '100vw', - height: '100vh', - maxWidth: '100vw', - maxHeight: '100vh', + openShowMonoliticImagesDialog(repository: Subnet) { + const dialogRef = this.dialog.open(ShowMonoliticImagesComponent, { + width: '85vw', + height: '85vh', + maxWidth: '85vw', + maxHeight: '85vh', data: { repositoryId: repository.id, repositoryName: repository.name, repositoryUuid: repository.uuid } }); @@ -139,6 +145,19 @@ export class RepositoriesComponent implements OnInit { }); } + openShowGitImagesDialog(repository: Subnet) { + const dialogRef = this.dialog.open(ShowGitImagesComponent, { + width: '85vw', + height: '85vh', + maxWidth: '85vw', + maxHeight: '85vh', + data: { repositoryId: repository.id, repositoryName: repository.name, repositoryUuid: repository.uuid } + }); + + dialogRef.afterClosed().subscribe(result => { + this.search(); + }); + } onPageChange(event: any): void { this.page = event.pageIndex; @@ -150,7 +169,7 @@ export class RepositoriesComponent implements OnInit { iniciarTour(): void { this.joyrideService.startTour({ steps: [ - 'titleStep', + 'repositoryTitleStep', 'addStep', ], showPrevButton: true, diff --git a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.html b/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.html deleted file mode 100644 index e1f3c60..0000000 --- a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.html +++ /dev/null @@ -1,104 +0,0 @@ - - -
- -
-

- {{ 'imagesTitle' | translate }} en {{ repository?.name }} -

-
-
- -
-
- -
-
- -
- - {{ 'searchLabel' | translate }} - - search - {{ 'searchHint' | translate }} - - - Estado - - Fallido - Pendiente - Transfiriendo - Creado con éxito - En progreso - Papelera - Creando archivos auxiliares - - -
- - - - - - - - - - - - - -
{{ column.header }} - - - {{ image.image[column.columnDef] ? 'check_circle' : 'cancel' }} - - - - - {{ getStatusLabel(image[column.columnDef]) }} - - - - {{ column.cell(image) }} - - Acciones - - - - - - - - - - - - -
- -
- - -
- diff --git a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.spec.ts b/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.spec.ts deleted file mode 100644 index 064bad7..0000000 --- a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { provideHttpClient } from '@angular/common/http'; -import { provideHttpClientTesting } from '@angular/common/http/testing'; -import { ReactiveFormsModule, FormsModule, FormBuilder } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { MatTableModule } from '@angular/material/table'; -import { MatPaginatorModule } from '@angular/material/paginator'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatIconModule } from '@angular/material/icon'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { TranslateModule } from '@ngx-translate/core'; -import { ToastrModule, ToastrService } from 'ngx-toastr'; -import { JoyrideModule } from 'ngx-joyride'; -import { CommonModule } from '@angular/common'; -import {MatProgressSpinner} from "@angular/material/progress-spinner"; -import {RepositoryImagesComponent} from "./repository-images.component"; -import {LoadingComponent} from "../../../shared/loading/loading.component"; -import {MatOptionModule} from "@angular/material/core"; -import {MatSelectModule} from "@angular/material/select"; -import { ConfigService } from '@services/config.service'; - -describe('RepositoriesComponent', () => { - let component: RepositoryImagesComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - const mockConfigService = { - apiUrl: 'http://mock-api-url', - mercureUrl: 'http://mock-mercure-url' - }; - - await TestBed.configureTestingModule({ - declarations: [RepositoryImagesComponent, LoadingComponent], - imports: [ - ReactiveFormsModule, - FormsModule, - MatDialogModule, - MatFormFieldModule, - MatInputModule, - MatCheckboxModule, - MatButtonModule, - MatTableModule, - MatPaginatorModule, - MatDividerModule, - MatIconModule, - MatOptionModule, - BrowserAnimationsModule, - MatProgressSpinner, - MatSelectModule , - ToastrModule.forRoot(), - TranslateModule.forRoot(), - JoyrideModule.forRoot(), - CommonModule - ], - providers: [ - FormBuilder, - ToastrService, - provideHttpClient(), - provideHttpClientTesting(), - { - provide: MatDialogRef, - useValue: {} - }, - { - provide: MAT_DIALOG_DATA, - useValue: {} - }, - { provide: ConfigService, useValue: mockConfigService } - ] - }).compileComponents(); - - fixture = TestBed.createComponent(RepositoryImagesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.css b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.css similarity index 90% rename from ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.css rename to ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.css index ecb91dd..45b503c 100644 --- a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.css +++ b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.css @@ -1,8 +1,3 @@ - -table { - width: 100%; -} - .search-container { display: flex; justify-content: space-between; @@ -12,43 +7,23 @@ table { box-sizing: border-box; } +.header-container { + display: flex; + align-items: center; + justify-content: space-between; +} + +.header-container-title { + display: flex; + align-items: center; + gap: 1em; +} + .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 10px; - border-bottom: 1px solid #ddd; -} - -.mat-elevation-z8 { - box-shadow: 0px 0px 0px rgba(0,0,0,0.2); -} - -.paginator-container { - display: flex; - justify-content: end; - margin-bottom: 30px; -} - -.example-headers-align .mat-expansion-panel-header-description { - justify-content: space-between; - align-items: center; -} - -.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field { - margin-left: 8px; -} - .button-row { display: table-cell; max-width: 600px; @@ -83,15 +58,43 @@ table { color: white; } -.header-container-title { - flex-grow: 1; - text-align: left; - margin-left: 1em; -} - .images-button-row { display: flex; gap: 15px; padding: 5px; } +table { + width: 100%; +} + +.search-boolean { + flex: 1; + padding: 5px; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +.paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} + +.example-headers-align .mat-expansion-panel-header-description { + justify-content: space-between; + align-items: center; +} + +.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field { + margin-left: 8px; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} diff --git a/ogWebconsole/src/app/components/repositories/show-images/show-images.component.html b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.html similarity index 69% rename from ogWebconsole/src/app/components/repositories/show-images/show-images.component.html rename to ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.html index fc28fe2..5b0d65d 100644 --- a/ogWebconsole/src/app/components/repositories/show-images/show-images.component.html +++ b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.html @@ -1,26 +1,19 @@ -

Gestionar imágenes en {{data.repositoryName}}

-
-
+ +

Gestionar imágenes git en {{data.repositoryName}}

-
-
-
-
-
-
@@ -28,15 +21,17 @@
- + {{ 'searchLabel' | translate }} - + search {{ 'searchHint' | translate }} Estado - + Fallido Pendiente Transfiriendo @@ -48,7 +43,8 @@
- +
@@ -77,7 +74,11 @@ @@ -101,17 +109,12 @@
{{ column.header }} @@ -68,7 +64,8 @@ {{ getStatusLabel(image[column.columnDef]) }} - + {{ column.cell(image) }} Acciones - + + @@ -86,13 +87,20 @@ - - - - - - - + + + + + + +
- +
- - - + \ No newline at end of file diff --git a/ogWebconsole/src/app/components/repositories/show-images/show-images.component.spec.ts b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.spec.ts similarity index 85% rename from ogWebconsole/src/app/components/repositories/show-images/show-images.component.spec.ts rename to ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.spec.ts index c7cc4a1..187a9b5 100644 --- a/ogWebconsole/src/app/components/repositories/show-images/show-images.component.spec.ts +++ b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ShowGitImagesComponent } from './show-git-images.component'; import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { ShowImagesComponent } from './show-images.component'; import { ToastrModule } from 'ngx-toastr'; import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { TranslateModule } from '@ngx-translate/core'; @@ -17,9 +17,9 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { MatInputModule } from '@angular/material/input'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -describe('ShowImagesComponent', () => { - let component: ShowImagesComponent; - let fixture: ComponentFixture; +describe('ShowGitImagesComponent', () => { + let component: ShowGitImagesComponent; + let fixture: ComponentFixture; beforeEach(async () => { const mockConfigService = { @@ -27,7 +27,7 @@ describe('ShowImagesComponent', () => { }; await TestBed.configureTestingModule({ - declarations: [ShowImagesComponent, LoadingComponent], + declarations: [ShowGitImagesComponent, LoadingComponent], imports: [ HttpClientTestingModule, ToastrModule.forRoot(), @@ -52,7 +52,7 @@ describe('ShowImagesComponent', () => { }) .compileComponents(); - fixture = TestBed.createComponent(ShowImagesComponent); + fixture = TestBed.createComponent(ShowGitImagesComponent); component = fixture.componentInstance; fixture.detectChanges(); }); @@ -60,4 +60,4 @@ describe('ShowImagesComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); -}); \ No newline at end of file +}); diff --git a/ogWebconsole/src/app/components/repositories/show-images/show-images.component.ts b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.ts similarity index 92% rename from ogWebconsole/src/app/components/repositories/show-images/show-images.component.ts rename to ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.ts index 270c73b..2608b8a 100644 --- a/ogWebconsole/src/app/components/repositories/show-images/show-images.component.ts +++ b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.ts @@ -15,14 +15,15 @@ import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/de import {ExportImageComponent} from "../../images/export-image/export-image.component"; import {BackupImageComponent} from "../backup-image/backup-image.component"; import {ConvertImageToVirtualComponent} from "../convert-image-to-virtual/convert-image-to-virtual.component"; +import {EditImageComponent} from "../edit-image/edit-image.component"; @Component({ - selector: 'app-show-images', - templateUrl: './show-images.component.html', - styleUrl: './show-images.component.css' + selector: 'app-show-git-images', + templateUrl: './show-git-images.component.html', + styleUrl: './show-git-images.component.css' }) -export class ShowImagesComponent implements OnInit { - baseUrl: string; +export class ShowGitImagesComponent { +baseUrl: string; private apiUrl: string; dataSource = new MatTableDataSource(); length: number = 0; @@ -45,9 +46,9 @@ export class ShowImagesComponent implements OnInit { cell: (image: any) => `${image.image.name}` }, { - columnDef: 'remotePc', - header: 'Remote Pc', - cell: (image: any) => `${image.image?.remotePc}` + columnDef: 'version', + header: 'Version', + cell: (image: any) => `${image.version ? image.version : '0'}` }, { columnDef: 'isGlobal', @@ -60,9 +61,9 @@ export class ShowImagesComponent implements OnInit { cell: (image: any) => `${image.status}` }, { - columnDef: 'imageFullsum', - header: 'Fullsum', - cell: (image: any) => `${image.imageFullsum}` + columnDef: 'description', + header: 'Descripción', + cell: (image: any) => `${image.description ? image.description : 'Sin descripción'}` }, { columnDef: 'createdAt', @@ -79,7 +80,7 @@ export class ShowImagesComponent implements OnInit { private joyrideService: JoyrideService, private configService: ConfigService, private router: Router, - public dialogRef: MatDialogRef, + public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any ) { this.baseUrl = this.configService.apiUrl; @@ -254,6 +255,18 @@ export class ShowImagesComponent implements OnInit { } }); break; + case 'edit': + this.dialog.open(EditImageComponent, { + width: '600px', + data: { + image: image, + } + }).afterClosed().subscribe((result) => { + if (result) { + this.loadData(); + } + }); + break; case 'recover': this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/recover`, {}).subscribe({ next: () => { @@ -332,6 +345,7 @@ export class ShowImagesComponent implements OnInit { imageImageRepository: image } }); + this.router.navigate(['/commands-logs']); }, error: (error) => { this.toastService.error(error.error['hydra:description']); diff --git a/ogWebconsole/src/app/components/repositories/show-images/show-images.component.css b/ogWebconsole/src/app/components/repositories/show-images/show-images.component.css deleted file mode 100644 index 01712ec..0000000 --- a/ogWebconsole/src/app/components/repositories/show-images/show-images.component.css +++ /dev/null @@ -1,202 +0,0 @@ - -table { - width: 100%; -} - -.search-container { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - margin: 1.5rem 0rem 1.5rem 0rem; - 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 10px; - border-bottom: 1px solid #ddd; -} - -.mat-elevation-z8 { - box-shadow: 0px 0px 0px rgba(0,0,0,0.2); -} - -.paginator-container { - display: flex; - justify-content: end; - margin-bottom: 30px; -} - -.example-headers-align .mat-expansion-panel-header-description { - justify-content: space-between; - align-items: center; -} - -.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field { - margin-left: 8px; -} - -.button-row { - display: table-cell; - max-width: 600px; -} - -.button-row .mat-mdc-button-base { - margin: 8px 8px 8px 0; -} - -.chip-failed { - background-color: #e87979 !important; - color: white; -} - -.chip-success { - background-color: #46c446 !important; - color: white; -} - -.chip-pending { - background-color: lightgrey !important; - color: black; -} - -.chip-in-progress { - background-color: #f5a623 !important; - color: white; -} - -.chip-transferring { - background-color: #f5a623 !important; - color: white; -} - -.header-container-title { - flex-grow: 1; - text-align: left; - margin-left: 1em; -} - -.images-button-row { - display: flex; - gap: 15px; - padding: 5px; -} - - -table { - width: 100%; -} - -.search-container { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - margin: 1.5rem 0rem 1.5rem 0rem; - 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 10px; - border-bottom: 1px solid #ddd; -} - -.mat-elevation-z8 { - box-shadow: 0px 0px 0px rgba(0,0,0,0.2); -} - -.paginator-container { - display: flex; - justify-content: end; - margin-bottom: 30px; -} - -.example-headers-align .mat-expansion-panel-header-description { - justify-content: space-between; - align-items: center; -} - -.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field { - margin-left: 8px; -} - -.button-row { - display: table-cell; - max-width: 600px; -} - -.button-row .mat-mdc-button-base { - margin: 8px 8px 8px 0; -} - -.chip-failed { - background-color: #e87979 !important; - color: white; -} - -.chip-success { - background-color: #46c446 !important; - color: white; -} - -.chip-pending { - background-color: lightgrey !important; - color: black; -} - -.chip-in-progress { - background-color: #f5a623 !important; - color: white; -} - -.chip-transferring { - background-color: #f5a623 !important; - color: white; -} - -.action-container { - display: flex; - justify-content: flex-end; - gap: 1em; - padding: 1.5em; -} - -.header-container-title { - flex-grow: 1; - text-align: left; - margin-left: 1em; -} - -.images-button-row { - display: flex; - gap: 15px; - padding: 5px; -} - - diff --git a/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.css b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.css new file mode 100644 index 0000000..45b503c --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.css @@ -0,0 +1,100 @@ +.search-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + margin: 1.5rem 0rem 1.5rem 0rem; + box-sizing: border-box; +} + +.header-container { + display: flex; + align-items: center; + justify-content: space-between; +} + +.header-container-title { + display: flex; + align-items: center; + gap: 1em; +} + +.search-string { + flex: 2; + padding: 5px; +} + +.button-row { + display: table-cell; + max-width: 600px; +} + +.button-row .mat-mdc-button-base { + margin: 8px 8px 8px 0; +} + +.chip-failed { + background-color: #e87979 !important; + color: white; +} + +.chip-success { + background-color: #46c446 !important; + color: white; +} + +.chip-pending { + background-color: lightgrey !important; + color: black; +} + +.chip-in-progress { + background-color: #f5a623 !important; + color: white; +} + +.chip-transferring { + background-color: #f5a623 !important; + color: white; +} + +.images-button-row { + display: flex; + gap: 15px; + padding: 5px; +} + +table { + width: 100%; +} + +.search-boolean { + flex: 1; + padding: 5px; +} + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +.paginator-container { + display: flex; + justify-content: end; + margin-bottom: 30px; +} + +.example-headers-align .mat-expansion-panel-header-description { + justify-content: space-between; + align-items: center; +} + +.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field { + margin-left: 8px; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} diff --git a/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.html b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.html new file mode 100644 index 0000000..f9db89e --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.html @@ -0,0 +1,122 @@ + + + +
+
+ +

Gestionar imágenes monolíticas en {{data.repositoryName}}

+
+
+ + + + +
+
+ +
+ + {{ 'searchLabel' | translate }} + + search + {{ 'searchHint' | translate }} + + + Estado + + Fallido + Pendiente + Transfiriendo + Creado con éxito + En progreso + Papelera + Creando archivos auxiliares + + +
+ + + + + + + + + + + + + +
{{ column.header }} + + + {{ image.image[column.columnDef] ? 'check_circle' : 'cancel' }} + + + + + {{ getStatusLabel(image[column.columnDef]) }} + + + + {{ column.cell(image) }} + + Acciones + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+ + + diff --git a/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.spec.ts b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.spec.ts new file mode 100644 index 0000000..50f7e3c --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.spec.ts @@ -0,0 +1,63 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ShowMonoliticImagesComponent } from './show-monolitic-images.component'; +import { ToastrModule } from 'ngx-toastr'; +import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { TranslateModule } from '@ngx-translate/core'; +import { JoyrideModule } from 'ngx-joyride'; +import { ConfigService } from '@services/config.service'; +import { LoadingComponent } from '../../../shared/loading/loading.component'; +import { MatIconModule } from '@angular/material/icon'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatTableModule } from '@angular/material/table'; +import { FormsModule } from '@angular/forms'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; + +describe('ShowMonoliticImagesComponent', () => { + let component: ShowMonoliticImagesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url' + }; + + await TestBed.configureTestingModule({ + declarations: [ShowMonoliticImagesComponent, LoadingComponent], + imports: [ + HttpClientTestingModule, + ToastrModule.forRoot(), + MatDialogModule, + TranslateModule.forRoot(), + JoyrideModule.forRoot(), + MatIconModule, + MatFormFieldModule, + MatSelectModule, + MatPaginatorModule, + MatTableModule, + FormsModule, + NoopAnimationsModule, + MatInputModule, + MatProgressSpinnerModule + ], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: ConfigService, useValue: mockConfigService } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ShowMonoliticImagesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.ts b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.ts similarity index 70% rename from ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.ts rename to ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.ts index 5fde29f..2179996 100644 --- a/ogWebconsole/src/app/components/repositories/repository-images/repository-images.component.ts +++ b/ogWebconsole/src/app/components/repositories/show-monolitic-images/show-monolitic-images.component.ts @@ -1,26 +1,29 @@ -import {Component, Input, isDevMode, OnInit} from '@angular/core'; +import {Component, Inject, Input, isDevMode, OnInit} from '@angular/core'; import {MatTableDataSource} from "@angular/material/table"; import {DatePipe} from "@angular/common"; -import {MatDialog} from "@angular/material/dialog"; +import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog"; import {HttpClient} from "@angular/common/http"; import {ToastrService} from "ngx-toastr"; import {JoyrideService} from "ngx-joyride"; +import {ConfigService} from "@services/config.service"; +import {Router} from "@angular/router"; import {Observable} from "rxjs"; -import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/delete-modal.component"; -import {ExportImageComponent} from "../../images/export-image/export-image.component"; -import {BackupImageComponent} from "../backup-image/backup-image.component"; import {ServerInfoDialogComponent} from "../../ogdhcp/server-info-dialog/server-info-dialog.component"; import {ImportImageComponent} from "../import-image/import-image.component"; import {ConvertImageComponent} from "../convert-image/convert-image.component"; -import { ConfigService } from '@services/config.service'; -import {Router} from "@angular/router"; +import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/delete-modal.component"; +import {ExportImageComponent} from "../../images/export-image/export-image.component"; +import {BackupImageComponent} from "../backup-image/backup-image.component"; +import {ConvertImageToVirtualComponent} from "../convert-image-to-virtual/convert-image-to-virtual.component"; +import {EditImageComponent} from "../edit-image/edit-image.component"; +import {RenameImageComponent} from "../rename-image/rename-image.component"; @Component({ - selector: 'app-repository-images', - templateUrl: './repository-images.component.html', - styleUrl: './repository-images.component.css' + selector: 'app-show-monolitic-images', + templateUrl: './show-monolitic-images.component.html', + styleUrl: './show-monolitic-images.component.css' }) -export class RepositoryImagesComponent implements OnInit { +export class ShowMonoliticImagesComponent implements OnInit { baseUrl: string; private apiUrl: string; dataSource = new MatTableDataSource(); @@ -41,12 +44,12 @@ export class RepositoryImagesComponent implements OnInit { { columnDef: 'name', header: 'Nombre de imagen', - cell: (image: any) => `${image.image.name}` + cell: (image: any) => `${image.name}` }, { - columnDef: 'remotePc', - header: 'Remote Pc', - cell: (image: any) => `${image.image?.remotePc}` + columnDef: 'version', + header: 'Version', + cell: (image: any) => `${image.version ? image.version : '0'}` }, { columnDef: 'isGlobal', @@ -59,9 +62,9 @@ export class RepositoryImagesComponent implements OnInit { cell: (image: any) => `${image.status}` }, { - columnDef: 'imageFullsum', - header: 'Fullsum', - cell: (image: any) => `${image.imageFullsum}` + columnDef: 'description', + header: 'Descripción', + cell: (image: any) => `${image.description ? image.description : 'Sin descripción'}` }, { columnDef: 'createdAt', @@ -71,9 +74,6 @@ export class RepositoryImagesComponent implements OnInit { ]; displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; - @Input() repositoryUuid: any - private repositoryId: any; - constructor( public dialog: MatDialog, private http: HttpClient, @@ -81,25 +81,27 @@ export class RepositoryImagesComponent implements OnInit { private joyrideService: JoyrideService, private configService: ConfigService, private router: Router, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any ) { this.baseUrl = this.configService.apiUrl; this.apiUrl = `${this.baseUrl}/image-image-repositories`; } ngOnInit(): void { - if (this.repositoryUuid) { - this.loadRepository() - } else { - this.search(); + console.error() + if (this.data) { + this.loadData(); } } - loadRepository(): void { - this.http.get(`${this.baseUrl}/image-repositories/${this.repositoryUuid}`, {}).subscribe( + loadData(): void { + this.loading = true; + this.http.get(`${this.apiUrl}?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}&repository.id=${this.data.repositoryId}`, { params: this.filters }).subscribe( data => { - this.repositoryId = data.id; - this.repository = data - this.search(); + this.dataSource.data = data['hydra:member']; + this.length = data['hydra:totalItems']; + this.loading = false; }, error => { console.error('Error fetching image repositories', error); @@ -128,26 +130,11 @@ export class RepositoryImagesComponent implements OnInit { } } - search(): void { - this.loading = true; - this.http.get(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}&repository.id=${this.repositoryId}`, { 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; - } - ); - } - onPageChange(event: any): void { this.page = event.pageIndex; this.itemsPerPage = event.pageSize; this.length = event.length; - this.search(); + this.loadData(); } loadImageAlert(image: any): Observable { @@ -177,15 +164,16 @@ export class RepositoryImagesComponent implements OnInit { } importImage(): void { + console.log(this.data) this.dialog.open(ImportImageComponent, { width: '600px', data: { - repositoryUuid: this.repositoryUuid, - name: this.repository.name + repositoryUuid: this.data.repositoryUuid, + name: this.data.repositoryName } }).afterClosed().subscribe((result) => { if (result) { - this.search(); + this.loadData(); } }); } @@ -194,12 +182,12 @@ export class RepositoryImagesComponent implements OnInit { this.dialog.open(ConvertImageComponent, { width: '600px', data: { - repositoryUuid: this.repositoryUuid, - name: this.repository.name + repositoryUuid: this.data.repositoryUuid, + name: this.data.repositoryName } }).afterClosed().subscribe((result) => { if (result) { - this.search(); + this.loadData(); } }); } @@ -210,7 +198,7 @@ export class RepositoryImagesComponent implements OnInit { this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/create-aux-files`, {}).subscribe({ next: (message) => { this.toastService.success('Petición de creación de archivos auxiliares enviada'); - this.search() + this.loadData() }, error: (error) => { this.toastService.error(error.error['hydra:description']); @@ -228,7 +216,7 @@ export class RepositoryImagesComponent implements OnInit { this.http.delete(`${this.baseUrl}${image['@id']}`).subscribe({ next: () => { this.toastService.success('Image deleted successfully'); - this.search() + this.loadData() }, error: (error) => { this.toastService.error('Error deleting image'); @@ -237,11 +225,11 @@ export class RepositoryImagesComponent implements OnInit { }); } else { this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/delete-trash`, - { repository: `/image-repositories/${this.repositoryUuid}` }) + { repository: `/image-repositories/${this.data.repositoryUuid}` }) .subscribe({ next: () => { this.toastService.success('Petición de eliminación de la papelera temporal enviada'); - this.search() + this.loadData() }, error: (error) => { this.toastService.error(error.error['hydra:description']); @@ -259,7 +247,7 @@ export class RepositoryImagesComponent implements OnInit { this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/delete-permanent`, {}).subscribe({ next: () => { this.toastService.success('Petición de eliminación de la papelera temporal enviada'); - this.search() + this.loadData() }, error: (error) => { this.toastService.error(error.error['hydra:description']); @@ -268,11 +256,23 @@ export class RepositoryImagesComponent implements OnInit { } }); break; + case 'edit': + this.dialog.open(EditImageComponent, { + width: '600px', + data: { + image: image, + } + }).afterClosed().subscribe((result) => { + if (result) { + this.loadData(); + } + }); + break; case 'recover': this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/recover`, {}).subscribe({ next: () => { this.toastService.success('Petición de recuperación de la imagen enviada'); - this.search() + this.loadData() }, error: (error) => { this.toastService.error(error.error['hydra:description']); @@ -283,7 +283,7 @@ export class RepositoryImagesComponent implements OnInit { this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/status`, {}).subscribe({ next: (response: any) => { this.toastService.info(response?.output); - this.search() + this.loadData() }, error: (error) => { this.toastService.error(error.error['hydra:description']); @@ -336,11 +336,41 @@ export class RepositoryImagesComponent implements OnInit { } }); break; + case 'convert-image-to-virtual': + this.http.get(`${this.baseUrl}${image.image['@id']}`).subscribe({ + next: (response) => { + this.dialog.open(ConvertImageToVirtualComponent, { + width: '600px', + data: { + image: response, + imageImageRepository: image + } + }); + this.router.navigate(['/commands-logs']); + }, + error: (error) => { + this.toastService.error(error.error['hydra:description']); + } + }); + break; + case 'rename': + this.dialog.open(RenameImageComponent, { + width: '600px', + data: { + imageImageRepository: image + } + }).afterClosed().subscribe((result) => { + if (result) { + this.loadData(); + } + }); + break; default: console.error('Acción no soportada:', action); break; } } + iniciarTour(): void { this.joyrideService.startTour({ steps: [ @@ -358,5 +388,42 @@ export class RepositoryImagesComponent implements OnInit { }); } + loadAlert(): Observable { + return this.http.post(`${this.baseUrl}/image-repositories/server/${this.data.repositoryUuid}/get-collection`, {}); + } + + syncRepository() { + this.http.post(`${this.baseUrl}/image-repositories/server/${this.data.repositoryUuid}/sync`, {}) + .subscribe(response => { + this.toastService.success('Sincronización completada'); + this.loadData() + }, error => { + console.error('Error al sincronizar', error); + this.toastService.error('Error al sincronizar'); + }); + } + + 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); + } + ); + } + + onNoClick(): void { + this.dialogRef.close(); + } + protected readonly isDevMode = isDevMode; } diff --git a/ogWebconsole/src/app/layout/header/header.component.css b/ogWebconsole/src/app/layout/header/header.component.css index c742d4c..ec5f5c3 100644 --- a/ogWebconsole/src/app/layout/header/header.component.css +++ b/ogWebconsole/src/app/layout/header/header.component.css @@ -1,6 +1,7 @@ mat-toolbar { /*height: 7vh;*/ - min-height: 60px; + min-height: 65px; + min-width: 375px; background-color: #3f51b5; color: white; } @@ -45,3 +46,38 @@ mat-toolbar { background-color: #ced0df; cursor: not-allowed; } + +.hide-on-small { + display: none; +} + +@media (min-width: 1500px) { + .hide-on-small { + display: inline; + } +} + +@media (max-width: 576px) { + .navbar-actions-row { + display: none; + } + + mat-toolbar { + display: flex; + justify-content: space-between; + } + + .isSmallScreenButtons { + display: flex; + justify-content: center; + align-items: center; + } + + .trace-button .mat-icon { + margin-right: 0px !important; + } + + .smallScreenMenubutton { + margin-right: 2vh; + } +} \ No newline at end of file diff --git a/ogWebconsole/src/app/layout/header/header.component.html b/ogWebconsole/src/app/layout/header/header.component.html index d70bb71..3413417 100644 --- a/ogWebconsole/src/app/layout/header/header.component.html +++ b/ogWebconsole/src/app/layout/header/header.component.html @@ -1,5 +1,6 @@ - + Opengnsys webconsole @@ -8,12 +9,14 @@ menu - + + + + + + \ No newline at end of file diff --git a/ogWebconsole/src/app/layout/header/header.component.ts b/ogWebconsole/src/app/layout/header/header.component.ts index 7e50f1d..212ef50 100644 --- a/ogWebconsole/src/app/layout/header/header.component.ts +++ b/ogWebconsole/src/app/layout/header/header.component.ts @@ -3,6 +3,7 @@ import { jwtDecode } from 'jwt-decode'; import { ChangePasswordModalComponent } from '../../components/admin/users/users/change-password-modal/change-password-modal.component'; import { MatDialog } from "@angular/material/dialog"; import { GlobalStatusComponent } from 'src/app/components/global-status/global-status.component'; +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; @Component({ selector: 'app-header', @@ -12,6 +13,7 @@ import { GlobalStatusComponent } from 'src/app/components/global-status/global-s export class HeaderComponent implements OnInit { isSuperAdmin: boolean = false; + isSmallScreen: boolean = false; @Output() toggleSidebar = new EventEmitter(); private decodedToken: any; @@ -21,7 +23,7 @@ export class HeaderComponent implements OnInit { this.toggleSidebar.emit(); } - constructor(public dialog: MatDialog) { } + constructor(public dialog: MatDialog, private breakpointObserver: BreakpointObserver) { } ngOnInit(): void { const token = localStorage.getItem('loginToken'); @@ -35,6 +37,9 @@ export class HeaderComponent implements OnInit { console.error('Error decoding JWT:', error); } } + this.breakpointObserver.observe(['(max-width: 576px)']).subscribe((result) => { + this.isSmallScreen = result.matches; + }) } ngDoCheck(): void { @@ -52,8 +57,6 @@ export class HeaderComponent implements OnInit { this.dialog.open(GlobalStatusComponent, { width: '45vw', height: '80vh', - // maxWidth: '60vw', - // maxHeight: '60vh' }) } } diff --git a/ogWebconsole/src/app/layout/main-layout/main-layout.component.css b/ogWebconsole/src/app/layout/main-layout/main-layout.component.css index 1fd8655..f8efe00 100644 --- a/ogWebconsole/src/app/layout/main-layout/main-layout.component.css +++ b/ogWebconsole/src/app/layout/main-layout/main-layout.component.css @@ -1,10 +1,10 @@ .container { width: 100vw; height: calc(100vh - 7vh); + min-width: 375px; } .sidebar { width: 15vw; min-width: 250px; - z-index: auto !important; -} +} \ No newline at end of file diff --git a/ogWebconsole/src/app/layout/main-layout/main-layout.component.html b/ogWebconsole/src/app/layout/main-layout/main-layout.component.html index 59eebe9..c8241e0 100644 --- a/ogWebconsole/src/app/layout/main-layout/main-layout.component.html +++ b/ogWebconsole/src/app/layout/main-layout/main-layout.component.html @@ -1,11 +1,12 @@ - - + + + - + \ No newline at end of file diff --git a/ogWebconsole/src/app/layout/main-layout/main-layout.component.ts b/ogWebconsole/src/app/layout/main-layout/main-layout.component.ts index dc6d912..98dd5d9 100644 --- a/ogWebconsole/src/app/layout/main-layout/main-layout.component.ts +++ b/ogWebconsole/src/app/layout/main-layout/main-layout.component.ts @@ -1,14 +1,30 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; @Component({ selector: 'app-main-layout', templateUrl: './main-layout.component.html', styleUrls: ['./main-layout.component.css'], }) -export class MainLayoutComponent { +export class MainLayoutComponent implements OnInit { isSidebarVisible: boolean = true; + sidebarMode: 'side' | 'over' = 'side'; + + constructor(private breakpointObserver: BreakpointObserver) { } + + ngOnInit(): void { + this.breakpointObserver.observe(['(max-width: 1500px)']).subscribe((result) => { + if (result.matches) { + this.sidebarMode = 'over'; + this.isSidebarVisible = false; + } else { + this.sidebarMode = 'side'; + this.isSidebarVisible = true; + } + }); + } toggleSidebar() { this.isSidebarVisible = !this.isSidebarVisible; } -} +} \ No newline at end of file diff --git a/ogWebconsole/src/app/layout/sidebar/sidebar.component.html b/ogWebconsole/src/app/layout/sidebar/sidebar.component.html index cde15ee..1c77fa8 100644 --- a/ogWebconsole/src/app/layout/sidebar/sidebar.component.html +++ b/ogWebconsole/src/app/layout/sidebar/sidebar.component.html @@ -7,14 +7,16 @@ - + apartment {{ 'groups' | translate }} - + playlist_play {{ 'actions' | translate }} @@ -23,19 +25,22 @@ - + chevron_right {{ 'commands' | translate }} - + chevron_right {{ 'commandGroups' | translate }} - + chevron_right {{ 'tasks' | translate }} @@ -43,14 +48,16 @@ - + lan {{ 'subnets' | translate }} - + desktop_windows {{ 'boot' | translate }} @@ -59,19 +66,22 @@ - + album {{ 'ogLive' | translate }} - + assignment {{ 'pxeTemplates' | translate }} - + save {{ 'pxeBootFiles' | translate }} @@ -79,14 +89,16 @@ - + calendar_month {{ 'calendars' | translate }} - + terminal {{ 'software' | translate }} @@ -95,19 +107,22 @@ - + list {{ 'softwareList' | translate }} - + folder_shared {{ 'softwareProfiles' | translate }} - + terminal {{ 'operativeSystems' | translate }} @@ -115,17 +130,19 @@ - + warehouse {{ 'repositories' | translate }} - + list {{ 'menus' | translate }} - + \ No newline at end of file diff --git a/ogWebconsole/src/app/layout/sidebar/sidebar.component.ts b/ogWebconsole/src/app/layout/sidebar/sidebar.component.ts index 8de24e6..83d250f 100644 --- a/ogWebconsole/src/app/layout/sidebar/sidebar.component.ts +++ b/ogWebconsole/src/app/layout/sidebar/sidebar.component.ts @@ -1,6 +1,7 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, Output, EventEmitter } from '@angular/core'; import { jwtDecode } from 'jwt-decode'; import { MatDialog } from '@angular/material/dialog'; + @Component({ selector: 'app-sidebar', templateUrl: './sidebar.component.html', @@ -8,6 +9,9 @@ import { MatDialog } from '@angular/material/dialog'; }) export class SidebarComponent { @Input() isVisible: boolean = false; + @Input() sidebarMode: 'side' | 'over' = 'side'; + @Output() closeSidebar: EventEmitter = new EventEmitter(); + isSuperAdmin: boolean = false; username: string = ""; decodedToken: any = ""; @@ -29,6 +33,12 @@ export class SidebarComponent { this.showSoftwareSub = !this.showSoftwareSub; } + onItemClick() { + if (this.isVisible && this.sidebarMode === 'over') { + this.closeSidebar.emit(); + } + } + constructor(public dialog: MatDialog) {} ngOnInit(): void { @@ -44,4 +54,4 @@ export class SidebarComponent { } } } -} +} \ No newline at end of file diff --git a/ogWebconsole/src/assets/images/close-delete-remove-trash-cancel-cross-svgrepo-com.svg b/ogWebconsole/src/assets/images/close-delete-remove-trash-cancel-cross-svgrepo-com.svg new file mode 100644 index 0000000..1d3d548 --- /dev/null +++ b/ogWebconsole/src/assets/images/close-delete-remove-trash-cancel-cross-svgrepo-com.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ogWebconsole/src/assets/images/computer_busy.svg b/ogWebconsole/src/assets/images/computer_busy.svg new file mode 100644 index 0000000..7e76860 --- /dev/null +++ b/ogWebconsole/src/assets/images/computer_busy.svg @@ -0,0 +1,3 @@ + + + diff --git a/ogWebconsole/src/assets/images/computer_disconnected.svg b/ogWebconsole/src/assets/images/computer_disconnected.svg new file mode 100644 index 0000000..e455b73 --- /dev/null +++ b/ogWebconsole/src/assets/images/computer_disconnected.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ogWebconsole/src/assets/images/computer_initializing.svg b/ogWebconsole/src/assets/images/computer_initializing.svg new file mode 100644 index 0000000..152a739 --- /dev/null +++ b/ogWebconsole/src/assets/images/computer_initializing.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ogWebconsole/src/assets/images/computer_linux-session.svg b/ogWebconsole/src/assets/images/computer_linux-session.svg new file mode 100644 index 0000000..ce9750c --- /dev/null +++ b/ogWebconsole/src/assets/images/computer_linux-session.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ogWebconsole/src/assets/images/computer_linux.svg b/ogWebconsole/src/assets/images/computer_linux.svg new file mode 100644 index 0000000..0374358 --- /dev/null +++ b/ogWebconsole/src/assets/images/computer_linux.svg @@ -0,0 +1,3 @@ + + + diff --git a/ogWebconsole/src/assets/images/computer_macos.svg b/ogWebconsole/src/assets/images/computer_macos.svg new file mode 100644 index 0000000..b647af6 --- /dev/null +++ b/ogWebconsole/src/assets/images/computer_macos.svg @@ -0,0 +1,3 @@ + + + diff --git a/ogWebconsole/src/assets/images/computer_off.svg b/ogWebconsole/src/assets/images/computer_off.svg new file mode 100644 index 0000000..f23c580 --- /dev/null +++ b/ogWebconsole/src/assets/images/computer_off.svg @@ -0,0 +1,2 @@ + + diff --git a/ogWebconsole/src/assets/images/computer_og-live.svg b/ogWebconsole/src/assets/images/computer_og-live.svg new file mode 100644 index 0000000..4e5b75e --- /dev/null +++ b/ogWebconsole/src/assets/images/computer_og-live.svg @@ -0,0 +1,3 @@ + + + diff --git a/ogWebconsole/src/assets/images/computer_turning-off.svg b/ogWebconsole/src/assets/images/computer_turning-off.svg new file mode 100644 index 0000000..f23c580 --- /dev/null +++ b/ogWebconsole/src/assets/images/computer_turning-off.svg @@ -0,0 +1,2 @@ + + diff --git a/ogWebconsole/src/assets/images/computer_windows-session.svg b/ogWebconsole/src/assets/images/computer_windows-session.svg new file mode 100644 index 0000000..dc9b531 --- /dev/null +++ b/ogWebconsole/src/assets/images/computer_windows-session.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ogWebconsole/src/assets/images/computer_windows.svg b/ogWebconsole/src/assets/images/computer_windows.svg new file mode 100644 index 0000000..8333a89 --- /dev/null +++ b/ogWebconsole/src/assets/images/computer_windows.svg @@ -0,0 +1,3 @@ + + + diff --git a/ogWebconsole/src/assets/images/ordenador_MNT.png b/ogWebconsole/src/assets/images/ordenador_MNT.png deleted file mode 100644 index 69da340..0000000 Binary files a/ogWebconsole/src/assets/images/ordenador_MNT.png and /dev/null differ diff --git a/ogWebconsole/src/assets/images/ordenador_busy.png b/ogWebconsole/src/assets/images/ordenador_busy.png deleted file mode 100644 index f010d84..0000000 Binary files a/ogWebconsole/src/assets/images/ordenador_busy.png and /dev/null differ diff --git a/ogWebconsole/src/assets/images/ordenador_initializing.png b/ogWebconsole/src/assets/images/ordenador_initializing.png deleted file mode 100644 index 6073601..0000000 Binary files a/ogWebconsole/src/assets/images/ordenador_initializing.png and /dev/null differ diff --git a/ogWebconsole/src/assets/images/ordenador_linux-session.png b/ogWebconsole/src/assets/images/ordenador_linux-session.png deleted file mode 100644 index 649eca7..0000000 Binary files a/ogWebconsole/src/assets/images/ordenador_linux-session.png and /dev/null differ diff --git a/ogWebconsole/src/assets/images/ordenador_linux.png b/ogWebconsole/src/assets/images/ordenador_linux.png deleted file mode 100644 index 3a7ae12..0000000 Binary files a/ogWebconsole/src/assets/images/ordenador_linux.png and /dev/null differ diff --git a/ogWebconsole/src/assets/images/ordenador_macos.png b/ogWebconsole/src/assets/images/ordenador_macos.png deleted file mode 100644 index 8816e0d..0000000 Binary files a/ogWebconsole/src/assets/images/ordenador_macos.png and /dev/null differ diff --git a/ogWebconsole/src/assets/images/ordenador_off.png b/ogWebconsole/src/assets/images/ordenador_off.png deleted file mode 100644 index 69da340..0000000 Binary files a/ogWebconsole/src/assets/images/ordenador_off.png and /dev/null differ diff --git a/ogWebconsole/src/assets/images/ordenador_og-live.png b/ogWebconsole/src/assets/images/ordenador_og-live.png deleted file mode 100644 index 4256bce..0000000 Binary files a/ogWebconsole/src/assets/images/ordenador_og-live.png and /dev/null differ diff --git a/ogWebconsole/src/assets/images/ordenador_turning-off.png b/ogWebconsole/src/assets/images/ordenador_turning-off.png deleted file mode 100644 index 69da340..0000000 Binary files a/ogWebconsole/src/assets/images/ordenador_turning-off.png and /dev/null differ diff --git a/ogWebconsole/src/assets/images/ordenador_windows-session.png b/ogWebconsole/src/assets/images/ordenador_windows-session.png deleted file mode 100644 index e45f574..0000000 Binary files a/ogWebconsole/src/assets/images/ordenador_windows-session.png and /dev/null differ diff --git a/ogWebconsole/src/assets/images/ordenador_windows.png b/ogWebconsole/src/assets/images/ordenador_windows.png deleted file mode 100644 index 107b4e0..0000000 Binary files a/ogWebconsole/src/assets/images/ordenador_windows.png and /dev/null differ diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json index afdbe80..e4cdc65 100644 --- a/ogWebconsole/src/locale/en.json +++ b/ogWebconsole/src/locale/en.json @@ -40,6 +40,8 @@ "labelRoleName": "Name", "sectionTitlePermissions": "Permissions:", "checkboxSuperAdmin": "Super Admin", + "parameters": "Parameters", + "runScripts": "Run scripts", "checkboxOrgAdmin": "Organizational Unit Admin", "checkboxOrgOperator": "Organizational Unit Operator", "checkboxOrgMinimal": "Minimal Organizational Unit", @@ -478,5 +480,6 @@ "CpuUsage": "CPU Usage", "processes": "Processes", "usedPercentageLabel": "Used", - "errorLoadingData": "Error fetching data. Service not available" + "errorLoadingData": "Error fetching data. Service not available", + "repositoryTitleStep": "On this screen you can manage image repositories." } diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json index 314061a..61d4a44 100644 --- a/ogWebconsole/src/locale/es.json +++ b/ogWebconsole/src/locale/es.json @@ -38,7 +38,9 @@ "rulesHeader": "Reglas", "statusUnavailable": "No disponible", "statusAvailable": "Disponible", + "parameters": "Parámetros", "labelRoleName": "Nombre", + "runScript": "Ejecutar comando", "sectionTitlePermissions": "Permisos:", "checkboxSuperAdmin": "Super Admin", "checkboxOrgAdmin": "Admin de Unidad Organizativa", @@ -480,6 +482,6 @@ "CpuUsage": "Uso de CPU", "processes": "Procesos", "usedPercentageLabel": "Usado", - "errorLoadingData": "Error al cargar los datos. Servicio inactivo" - + "errorLoadingData": "Error al cargar los datos. Servicio inactivo", + "repositoryTitleStep": "En esta pantalla se pueden gestionar los repositorios de imágenes." }