diff --git a/CHANGELOG.md b/CHANGELOG.md index a68cb37..1def32f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ # Changelog +## [0.14.0] - 2025-06-02 +### Added +- Se ha añadido funcionalidad de usuarios/roles para separar las vistas segun los permisos. +- Nuevo boton de "mover" en la pantalla de grupos, para mover equipos entre aulas y grupos. + +### Improved +- Se ha completado la opcion de inicion de sesion y eliminar imagen cache. + +--- ## [0.13.1] - 2025-05-23 ### Changed - Desactivado temporalmente la funcionalidad de ogGit. diff --git a/ogWebconsole/src/app/app-routing.module.ts b/ogWebconsole/src/app/app-routing.module.ts index f901d43..501a3ad 100644 --- a/ogWebconsole/src/app/app-routing.module.ts +++ b/ogWebconsole/src/app/app-routing.module.ts @@ -3,28 +3,26 @@ import { RouterModule, Routes } from '@angular/router'; import { MainLayoutComponent } from './layout/main-layout/main-layout.component'; import { AuthLayoutComponent } from './layout/auth-layout/auth-layout.component'; import { LoginComponent } from './components/login/login.component'; -import { DashboardComponent } from './components/dashboard/dashboard.component'; import { PageNotFoundComponent } from './shared/page-not-found/page-not-found.component'; -import { AdminComponent } from './components/admin/admin.component'; import { UsersComponent } from './components/admin/users/users/users.component'; import { RolesComponent } from './components/admin/roles/roles/roles.component'; import { GroupsComponent } from './components/groups/groups.component'; import { PXEimagesComponent } from './components/ogboot/pxe-images/pxe-images.component'; import { PxeComponent } from './components/ogboot/pxe/pxe.component'; import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component'; -import {OgbootStatusComponent} from "./components/ogboot/ogboot-status/ogboot-status.component"; +import { OgbootStatusComponent } from "./components/ogboot/ogboot-status/ogboot-status.component"; import { CalendarComponent } from "./components/calendar/calendar.component"; import { CommandsComponent } from './components/commands/main-commands/commands.component'; import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component'; import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component'; import { TaskLogsComponent } from './components/task-logs/task-logs.component'; -import {SoftwareComponent} from "./components/software/software.component"; -import {SoftwareProfileComponent} from "./components/software-profile/software-profile.component"; -import {OperativeSystemComponent} from "./components/operative-system/operative-system.component"; +import { SoftwareComponent } from "./components/software/software.component"; +import { SoftwareProfileComponent } from "./components/software-profile/software-profile.component"; +import { OperativeSystemComponent } from "./components/operative-system/operative-system.component"; import { PartitionAssistantComponent } from "./components/groups/components/client-main-view/partition-assistant/partition-assistant.component"; -import {RepositoriesComponent} from "./components/repositories/repositories.component"; +import { RepositoriesComponent } from "./components/repositories/repositories.component"; import { CreateClientImageComponent } from "./components/groups/components/client-main-view/create-image/create-image.component"; @@ -34,44 +32,44 @@ import { import { MainRepositoryViewComponent } from "./components/repositories/main-repository-view/main-repository-view.component"; -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 { 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"; +import { roleGuard } from './guards/role.guard'; const routes: Routes = [ { path: '', redirectTo: 'auth/login', pathMatch: 'full' }, - { path: '', component: MainLayoutComponent, + { + path: '', component: MainLayoutComponent, children: [ - { path: 'dashboard', component: DashboardComponent }, - { path: 'admin', component: AdminComponent }, - { path: 'users', component: UsersComponent }, - { path: 'env-vars', component: EnvVarsComponent }, - { path: 'user-groups', component: RolesComponent }, + { path: 'users', component: UsersComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin'] } }, + { path: 'env-vars', component: EnvVarsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin'] } }, + { path: 'roles', component: RolesComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin'] } }, { path: 'groups', component: GroupsComponent }, - { path: 'pxe-images', component: PXEimagesComponent }, - { path: 'pxe', component: PxeComponent }, - { path: 'pxe-boot-file', component: PxeBootFilesComponent }, - { path: 'ogboot-status', component: OgbootStatusComponent }, - { path: 'subnets', component: OgDhcpSubnetsComponent }, - { path: 'ogdhcp-status', component: StatusComponent }, - { path: 'commands', component: CommandsComponent }, - { path: 'commands-groups', component: CommandsGroupsComponent }, - { path: 'commands-task', component: CommandsTaskComponent }, - { path: 'commands-logs', component: TaskLogsComponent }, - { 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/create-image', component: CreateClientImageComponent }, - { path: 'repositories', component: RepositoriesComponent }, - { path: 'repository/:id', component: MainRepositoryViewComponent }, - { path: 'software', component: SoftwareComponent }, - { path: 'software-profiles', component: SoftwareProfileComponent }, - { path: 'operative-systems', component: OperativeSystemComponent }, - { path: 'menus', component: MenusComponent }, + { path: 'pxe-images', component: PXEimagesComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'pxe', component: PxeComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'pxe-boot-file', component: PxeBootFilesComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'ogboot-status', component: OgbootStatusComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'subnets', component: OgDhcpSubnetsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'ogdhcp-status', component: StatusComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'commands', component: CommandsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'commands-groups', component: CommandsGroupsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'commands-task', component: CommandsTaskComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'commands-logs', component: TaskLogsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'calendars', component: CalendarComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'clients/deploy-image', component: DeployImageComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'clients/partition-assistant', component: PartitionAssistantComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'clients/run-script', component: RunScriptAssistantComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'clients/:id/create-image', component: CreateClientImageComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'repositories', component: RepositoriesComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'repository/:id', component: MainRepositoryViewComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'software', component: SoftwareComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'software-profiles', component: SoftwareProfileComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'operative-systems', component: OperativeSystemComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, + { path: 'menus', component: MenusComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } }, ], }, { diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index bd95fa4..ed2d3f9 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -17,7 +17,6 @@ import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { MatSidenavModule } from '@angular/material/sidenav'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { AdminComponent } from './components/admin/admin.component'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatFormFieldModule } from '@angular/material/form-field'; @@ -151,6 +150,9 @@ import { CreateTaskScriptComponent } from './components/commands/commands-task/c import { ViewParametersModalComponent } from './components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component'; import { OutputDialogComponent } from './components/task-logs/output-dialog/output-dialog.component'; import { ClientTaskLogsComponent } from './components/task-logs/client-task-logs/client-task-logs.component'; +import { BootSoPartitionComponent } from './components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component'; +import { RemoveCacheImageComponent } from './components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component'; +import { ChangeParentComponent } from './components/groups/shared/change-parent/change-parent.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); @@ -170,7 +172,6 @@ registerLocaleData(localeEs, 'es-ES'); HeaderComponent, SidebarComponent, LoginComponent, - AdminComponent, MainLayoutComponent, UsersComponent, RolesComponent, @@ -259,7 +260,10 @@ registerLocaleData(localeEs, 'es-ES'); CreateTaskScriptComponent, ViewParametersModalComponent, OutputDialogComponent, - ClientTaskLogsComponent + ClientTaskLogsComponent, + BootSoPartitionComponent, + RemoveCacheImageComponent, + ChangeParentComponent ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/admin/admin.component.css b/ogWebconsole/src/app/components/admin/admin.component.css deleted file mode 100644 index 8c90c1c..0000000 --- a/ogWebconsole/src/app/components/admin/admin.component.css +++ /dev/null @@ -1,48 +0,0 @@ -/* Estilos del contenedor para centrar los botones */ -.container { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; -} - -/* Estilos del contenedor de cada botón y texto */ -.button-container { - display: flex; - flex-direction: column; - align-items: center; - margin: 0 10px; -} - -/* Estilos del texto debajo de los botones */ -span{ - margin: 0; - font-size: 20px; - text-align: center; -} - -/* Media query para hacer los botones responsive */ -@media (max-width: 900px) { - button { - height: 120px; - width: 120px; - } -} - -@media (max-width: 600px) { - button { - height: 90px; - width: 90px; - } -} - -@media (max-width: 400px) { - button { - height: 70px; - width: 70px; - } - - span{ - font-size: 14px; - } -} diff --git a/ogWebconsole/src/app/components/admin/admin.component.html b/ogWebconsole/src/app/components/admin/admin.component.html deleted file mode 100644 index bf2ca78..0000000 --- a/ogWebconsole/src/app/components/admin/admin.component.html +++ /dev/null @@ -1,10 +0,0 @@ -
- - -
diff --git a/ogWebconsole/src/app/components/admin/admin.component.spec.ts b/ogWebconsole/src/app/components/admin/admin.component.spec.ts deleted file mode 100644 index 2ed2fec..0000000 --- a/ogWebconsole/src/app/components/admin/admin.component.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { AdminComponent } from './admin.component'; -import { MatButtonModule } from '@angular/material/button'; -import { MatIconModule } from '@angular/material/icon'; -import { TranslateModule } from '@ngx-translate/core'; -import { Router } from '@angular/router'; - -describe('AdminComponent', () => { - let component: AdminComponent; - let fixture: ComponentFixture; - let router: Router; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AdminComponent], - imports: [ - RouterTestingModule, - MatButtonModule, - MatIconModule, - TranslateModule.forRoot() - ] - }).compileComponents(); - - router = TestBed.inject(Router); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(AdminComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('debería crear el componente', () => { - expect(component).toBeTruthy(); - }); - - it('debería renderizar dos botones', () => { - const buttons = fixture.nativeElement.querySelectorAll('button'); - expect(buttons.length).toBe(2); - }); - - it('debería tener un botón con routerLink a "/users"', () => { - const button = fixture.nativeElement.querySelector('button[routerLink="/users"]'); - expect(button).toBeTruthy(); - expect(button.querySelector('mat-icon').textContent.trim()).toBe('group'); - }); -}); diff --git a/ogWebconsole/src/app/components/admin/admin.component.ts b/ogWebconsole/src/app/components/admin/admin.component.ts deleted file mode 100644 index 256f80d..0000000 --- a/ogWebconsole/src/app/components/admin/admin.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component } from '@angular/core'; - - -@Component({ - selector: 'app-admin', - templateUrl: './admin.component.html', - styleUrl: './admin.component.css' -}) - -export class AdminComponent { - -} - diff --git a/ogWebconsole/src/app/components/commands/commands-groups/commands-groups.component.html b/ogWebconsole/src/app/components/commands/commands-groups/commands-groups.component.html index 3092715..424b5a9 100644 --- a/ogWebconsole/src/app/components/commands/commands-groups/commands-groups.component.html +++ b/ogWebconsole/src/app/components/commands/commands-groups/commands-groups.component.html @@ -33,15 +33,6 @@ {{ column.cell(commandGroup) }} - - - - - - - diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.css b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.css index d14ed3c..11a5938 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.css +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.css @@ -6,6 +6,11 @@ padding: 20px; } +.select-task { + padding: 20px; + margin-bottom: 16px; +} + .full-width { width: 100%; } diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html index b9a8aa8..79de16a 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.html @@ -1,9 +1,40 @@ -

{{ editing ? ('editTask' | translate) : ('createTask' | translate) }}

+

+ {{ editing ? ('editTask' | translate) : ('createTask' | translate) }} +

-
+ + + Crear tarea + Introducir en tarea existente + + + +
+ + Seleccione una tarea + + {{ task.name }} + + + + + Orden de ejecución + + +
+ + + {{ 'nameLabel' | translate }} @@ -17,17 +48,17 @@ Ámbito - - Unidad Organizativa - Grupo de aulas - Aulas - Grupos de clientes + + Unidad Organizativa + Grupo de aulas + Aulas + Grupos de clientes - + {{ 'organizationalUnitLabel' | translate }} - +
{{ unit.name }}
{{ unit.path }}
@@ -35,11 +66,18 @@
- ¿Quieres programar la tarea al finalizar su creación? + + ¿Quieres programar la tarea al finalizar su creación? +
- + diff --git a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts index 92c6440..9ff9ba6 100644 --- a/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts +++ b/ogWebconsole/src/app/components/commands/commands-task/create-task/create-task.component.ts @@ -25,6 +25,10 @@ export class CreateTaskComponent implements OnInit { clients: any[] = []; allOrganizationalUnits: any[] = []; loading: boolean = false; + taskMode: 'create' | 'add' = 'create'; + existingTasks: any[] = []; + selectedExistingTask: string | null = null; + executionOrder: number | null = null; constructor( private fb: FormBuilder, @@ -53,6 +57,7 @@ export class CreateTaskComponent implements OnInit { this.loadIndividualCommands(), this.loadOrganizationalUnits(), this.startUnitsFilter(), + this.loadTasks() ]; Promise.all(observables).then(() => { @@ -90,6 +95,21 @@ export class CreateTaskComponent implements OnInit { }) } + loadTasks(): Promise { + return new Promise((resolve, reject) => { + this.http.get(`${this.apiUrl}?page=1&itemsPerPage=100`).subscribe( + (data) => { + this.existingTasks = data['hydra:member']; + resolve(); + }, + (error) => { + this.toastr.error('Error al cargar las tareas existentes'); + reject(error); + } + ); + }); + } + onScopeChange(scope: string): void { this.filterUnits(scope).subscribe(filteredUnits => { this.availableOrganizationalUnits = filteredUnits; @@ -161,6 +181,27 @@ export class CreateTaskComponent implements OnInit { }); } + addToExistingTask() { + if (!this.selectedExistingTask) { + this.toastr.error('Debes seleccionar una tarea existente.'); + return; + } + + if (this.executionOrder == null || this.executionOrder < 1) { + this.toastr.error('Debes introducir un orden de ejecución válido (mayor que 0).'); + return; + } + + const data = { + taskId: this.selectedExistingTask, + executionOrder: this.executionOrder + }; + + this.toastr.success('Tarea actualizada con éxito'); + this.dialogRef.close(data); + } + + saveTask(): void { if (this.taskForm.invalid) { this.toastr.error('Por favor, rellene todos los campos obligatorios'); diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.css b/ogWebconsole/src/app/components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.css new file mode 100644 index 0000000..9f58e44 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.css @@ -0,0 +1,117 @@ +.dialog-content { + display: flex; + flex-direction: column; + padding: 40px; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} + +.select-container { + margin-top: 20px; + align-items: center; + padding: 20px; + box-sizing: border-box; +} + +.clients-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 8px; +} + +.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); + } +} + +.button-row { + display: flex; + padding-right: 1em; +} + +.action-button { + margin-top: 10px; + margin-bottom: 10px; +} + +.client-item { + position: relative; +} + +.mat-expansion-panel-header-description { + justify-content: space-between; + align-items: center; +} + +.selected-client { + background-color: #a0c2e5 !important; + color: white !important; +} + +.loading-spinner { + display: block; + margin: 0 auto; + align-items: center; + justify-content: center; +} + +.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; +} + + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +@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/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.html b/ogWebconsole/src/app/components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.html new file mode 100644 index 0000000..15769b1 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.html @@ -0,0 +1,100 @@ +

Seleccionar particion para arrancar SO

+ + + + +
+ + + Clientes + + Listado de clientes para arrancar un SO + desktop_windows + + + +
+ +
+ +
+
+
+ + Client Icon + +
+ {{ client.name | slice:0:20 }} + {{ client.ip }} + {{ client.mac }} +
+ + + + + Modelo + + +
+
+
+ +
+
+ + + +
+ + + + + + + + + + + + + +
Seleccionar partición + + + + + {{ column.header }} + + {{ column.cell(image) }} + + + + + +
+ {{ image.size }} MB + {{ image.size / 1024 }} GB +
+
+ +
+
+
+ +
+ + +
diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.spec.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.spec.ts new file mode 100644 index 0000000..ccd7bfe --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.spec.ts @@ -0,0 +1,82 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { BootSoPartitionComponent } from './boot-so-partition.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 { MatButtonModule } from "@angular/material/button"; +import { MatMenuModule } from "@angular/material/menu"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { MatTableModule } from "@angular/material/table"; +import { MatSelectModule } from "@angular/material/select"; +import { MatIconModule } from "@angular/material/icon"; +import { ToastrModule, ToastrService } from "ngx-toastr"; +import { TranslateModule } from "@ngx-translate/core"; +import { DataService } from "../../data.service"; +import { provideHttpClient } from "@angular/common/http"; +import { provideHttpClientTesting } from "@angular/common/http/testing"; +import { ConfigService } from "@services/config.service"; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatDividerModule } from '@angular/material/divider'; + +describe('BootSoPartitionComponent', () => { + let component: BootSoPartitionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url', + mercureUrl: 'http://mock-mercure-url' + }; + + await TestBed.configureTestingModule({ + declarations: [BootSoPartitionComponent], + imports: [ + ReactiveFormsModule, + FormsModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + MatCheckboxModule, + MatButtonModule, + MatExpansionModule, + MatMenuModule, + BrowserAnimationsModule, + MatTableModule, + MatDividerModule, + MatSelectModule, + MatIconModule, + ToastrModule.forRoot(), + TranslateModule.forRoot() + ], + providers: [ + FormBuilder, + ToastrService, + DataService, + provideHttpClient(), + provideHttpClientTesting(), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: { + clients: [] + } + }, + { provide: ConfigService, useValue: mockConfigService } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BootSoPartitionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.ts new file mode 100644 index 0000000..46d8290 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component.ts @@ -0,0 +1,154 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; +import { MatTableDataSource } from "@angular/material/table"; +import { ConfigService } from "@services/config.service"; +import { HttpClient } from "@angular/common/http"; +import { ToastrService } from "ngx-toastr"; + +@Component({ + selector: 'app-boot-so-partition', + templateUrl: './boot-so-partition.component.html', + styleUrl: './boot-so-partition.component.css' +}) +export class BootSoPartitionComponent implements OnInit { + baseUrl: string; + selectedPartition: any = null; + dataSource = new MatTableDataSource(); + clientId: string | null = null; + selectedClients: any[] = []; + selectedModelClient: any = null; + filteredPartitions: any[] = []; + allSelected: boolean = false; + clientData: any[] = []; + loading: boolean = false; + columns = [ + { + columnDef: 'diskNumber', + header: 'Disco', + cell: (partition: any) => partition.diskNumber + }, + { + columnDef: 'partitionNumber', + header: 'Particion', + cell: (partition: any) => partition.partitionNumber + }, + { + columnDef: 'size', + header: 'Tamaño', + cell: (partition: any) => `${partition.size} MB` + }, + { + columnDef: 'partitionCode', + header: 'Tipo de partición', + cell: (partition: any) => partition.partitionCode + }, + { + columnDef: 'filesystem', + header: 'Sistema de ficheros', + cell: (partition: any) => partition.filesystem + }, + { + columnDef: 'operativeSystem', + header: 'SO', + cell: (partition: any) => partition.operativeSystem?.name + } + ]; + + displayedColumns = ['select', ...this.columns.map(column => column.columnDef)]; + + constructor( + @Inject(MAT_DIALOG_DATA) public data: { clients: any }, + private dialogRef: MatDialogRef, + private configService: ConfigService, + private http: HttpClient, + private toastService: ToastrService, + ) { + this.baseUrl = this.configService.apiUrl; + this.clientId = this.data.clients?.length ? this.data.clients[0]['@id'] : null; + + this.data.clients.forEach((client: { selected: boolean; status: string }) => { + if (client.status === 'og-live') { + client.selected = true; + } + }); + + this.selectedClients = this.data.clients.filter( + (client: { status: string }) => client.status === 'og-live' + ); + + this.selectedModelClient = this.data.clients.find( + (client: { status: string }) => client.status === 'og-live' + ) || null; + + if (this.selectedModelClient) { + this.loadPartitions(this.selectedModelClient); + } + } + + ngOnInit() { + + } + + loadPartitions(client: any) { + const url = `${this.baseUrl}${client.uuid}`; + this.http.get(url).subscribe( + (response: any) => { + if (response.partitions) { + this.dataSource.data = response.partitions.filter((partition: any) => { + return partition.partitionNumber !== 0; + }); + } + }, + (error) => { + console.error('Error al cargar los datos del cliente:', error); + } + ); + } + + toggleClientSelection(client: any) { + client.selected = !client.selected; + this.updateSelectedClients(); + } + + updateSelectedClients() { + this.selectedClients = this.data.clients.filter( + (client: { selected: boolean; state: string }) => client.selected && client.state === "og-live" + ); + + if (!this.selectedClients.includes(this.selectedModelClient)) { + this.selectedModelClient = null; + this.filteredPartitions = []; + } + } + + toggleSelectAll() { + this.allSelected = !this.allSelected; + this.data.clients.forEach((client: { selected: boolean; status: string }) => { + if (client.status === "og-live") { + client.selected = this.allSelected; + } + }); + } + + close() { + this.dialogRef.close(); + } + + execute(): void { + this.loading = true; + this.http.post(`${this.baseUrl}/clients/server/boot-client`, { + clients: this.selectedClients.map((client: any) => client.uuid), + partition: this.selectedPartition['@id'] + }).subscribe( + response => { + this.toastService.success('Cliente actualizado correctamente'); + this.dialogRef.close(); + this.loading = false; + }, + error => { + this.toastService.error(error.error['hydra:description']); + this.loading = false; + } + ); + } +} 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 1b871a4..0efcd55 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 @@ -3,6 +3,10 @@ import { HttpClient } from '@angular/common/http'; import { Router } from "@angular/router"; import { ToastrService } from "ngx-toastr"; import { ConfigService } from '@services/config.service'; +import { BootSoPartitionComponent } from "./boot-so-partition/boot-so-partition.component"; +import { MatDialog } from "@angular/material/dialog"; +import { RemoveCacheImageComponent } from "./remove-cache-image/remove-cache-image.component"; +import { AuthService } from '@services/auth.service'; @Component({ selector: 'app-execute-command', @@ -27,7 +31,7 @@ export class ExecuteCommandComponent implements OnInit { { translationKey: 'executeCommands.login', slug: 'login', disabled: true }, { translationKey: 'executeCommands.createImage', slug: 'create-image', disabled: false }, { translationKey: 'executeCommands.deployImage', slug: 'deploy-image', disabled: false }, - { translationKey: 'executeCommands.deleteImageCache', slug: 'delete-image-cache', disabled: true }, + { translationKey: 'executeCommands.deleteImageCache', slug: 'remove-cache-image', disabled: false }, { translationKey: 'executeCommands.partition', slug: 'partition', disabled: false }, { translationKey: 'executeCommands.softwareInventory', slug: 'software-inventory', disabled: true }, { translationKey: 'executeCommands.hardwareInventory', slug: 'hardware-inventory', disabled: true }, @@ -40,13 +44,18 @@ export class ExecuteCommandComponent implements OnInit { private http: HttpClient, private router: Router, private configService: ConfigService, - private toastService: ToastrService + private toastService: ToastrService, + public auth: AuthService, + private dialog: MatDialog, ) { this.baseUrl = this.configService.apiUrl; } ngOnInit(): void { this.clientData = this.clientData || []; + const allowed = this.getAllowedCommandsByRole(); + this.arrayCommands = this.arrayCommands.filter(c => allowed.includes(c.slug)); + this.updateCommandStates(); } @@ -54,6 +63,34 @@ export class ExecuteCommandComponent implements OnInit { this.updateCommandStates(); } + private getAllowedCommandsByRole(): string[] { + const role = this.auth.userCategory; + + const permissions: Record = { + 'super-admin': ['*'], + 'ou-admin': ['*'], + 'ou-operator': [ + 'power-on', + 'power-off', + 'reboot', + 'login', + 'deploy-image', + 'software-inventory', + 'hardware-inventory', + 'remove-cache-image', + 'partition' + ], + 'ou-minimal': [ + 'power-on', + 'power-off' + ] + }; + + const allowed = permissions[role] || []; + return allowed.includes('*') ? this.arrayCommands.map(c => c.slug) : allowed; + } + + private updateCommandStates(): void { let states: string[] = []; @@ -74,13 +111,13 @@ export class ExecuteCommandComponent implements OnInit { if (states[0] === 'off' || states[0] === 'disconnected') { command.disabled = command.slug !== 'power-on'; } else { - command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'partition', 'run-script'].includes(command.slug); + command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'remove-cache-image', 'partition', 'run-script'].includes(command.slug); } } else { if (command.slug === 'create-image') { command.disabled = multipleClients; } else if ( - ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'run-script'].includes(command.slug) + ['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'remove-cache-image', 'run-script'].includes(command.slug) ) { command.disabled = false; } else { @@ -123,6 +160,10 @@ export class ExecuteCommandComponent implements OnInit { if (action === 'power-on') { this.powerOnClient(); } + + if (action === 'remove-cache-image') { + this.removeImageCache(); + } } rebootClient(): void { @@ -139,16 +180,51 @@ 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'); + 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 + })); + + const dialogRef = this.dialog.open(BootSoPartitionComponent, { + width: '70vw', + height: 'auto', + data: { clients: clientDataToSend } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.toastService.success('Petición de arranque de SO enviada correctamente'); } - ); + }); + } + + removeImageCache(): 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 + })); + + const dialogRef = this.dialog.open(RemoveCacheImageComponent, { + width: '70vw', + height: 'auto', + data: { clients: clientDataToSend } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.toastService.success('Petición de borrado de caché de imagen enviada correctamente'); + } + }); } powerOnClient(): void { diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.css b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.css new file mode 100644 index 0000000..9f58e44 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.css @@ -0,0 +1,117 @@ +.dialog-content { + display: flex; + flex-direction: column; + padding: 40px; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 1em; + padding: 1.5em; +} + +.select-container { + margin-top: 20px; + align-items: center; + padding: 20px; + box-sizing: border-box; +} + +.clients-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 8px; +} + +.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); + } +} + +.button-row { + display: flex; + padding-right: 1em; +} + +.action-button { + margin-top: 10px; + margin-bottom: 10px; +} + +.client-item { + position: relative; +} + +.mat-expansion-panel-header-description { + justify-content: space-between; + align-items: center; +} + +.selected-client { + background-color: #a0c2e5 !important; + color: white !important; +} + +.loading-spinner { + display: block; + margin: 0 auto; + align-items: center; + justify-content: center; +} + +.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; +} + + +.mat-elevation-z8 { + box-shadow: 0px 0px 0px rgba(0,0,0,0.2); +} + +@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/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.html b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.html new file mode 100644 index 0000000..78f4df5 --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.html @@ -0,0 +1,105 @@ +

Seleccionar imagen para eliminar de la cache

+ + + + +
+ + + Clientes + + Listado de clientes para arrancar un SO + desktop_windows + + + +
+ +
+ +
+
+
+ + Client Icon + +
+ {{ client.name | slice:0:20 }} + {{ client.ip }} + {{ client.mac }} +
+ + + + + Modelo + + +
+
+
+ +
+
+ + + +
+ + + + + + + + + + + + + +
Seleccionar imagen + + + + + {{ column.header }} + + {{ column.cell(image) }} + + + +
+ {{ image.size }} MB + {{ image.size / 1024 }} GB +
+
+ + +
+ {{ image.operativeSystem?.name }} + {{ image.image?.name}} +
+
+ +
+
+
+ +
+ + +
diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.spec.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.spec.ts new file mode 100644 index 0000000..801a02a --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.spec.ts @@ -0,0 +1,92 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RemoveCacheImageComponent } from './remove-cache-image.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 { MatButtonModule } from "@angular/material/button"; +import { MatMenuModule } from "@angular/material/menu"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { MatTableModule } from "@angular/material/table"; +import { MatSelectModule } from "@angular/material/select"; +import { MatIconModule } from "@angular/material/icon"; +import { ToastrModule, ToastrService } from "ngx-toastr"; +import { TranslateModule } from "@ngx-translate/core"; +import { DataService } from "../../data.service"; +import { provideHttpClient } from "@angular/common/http"; +import { provideHttpClientTesting } from "@angular/common/http/testing"; +import { ConfigService } from "@services/config.service"; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatRadioModule } from '@angular/material/radio'; + +describe('RemoveCacheImageComponent', () => { + let component: RemoveCacheImageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { + apiUrl: 'http://mock-api-url', + mercureUrl: 'http://mock-mercure-url' + }; + + await TestBed.configureTestingModule({ + declarations: [RemoveCacheImageComponent], + imports: [ + ReactiveFormsModule, + FormsModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + MatCheckboxModule, + MatButtonModule, + MatMenuModule, + MatExpansionModule, + BrowserAnimationsModule, + MatTableModule, + MatDividerModule, + MatSelectModule, + MatRadioModule, + MatIconModule, + ToastrModule.forRoot(), + TranslateModule.forRoot() + ], + providers: [ + FormBuilder, + ToastrService, + DataService, + provideHttpClient(), + provideHttpClientTesting(), + { + provide: MatDialogRef, + useValue: {} + }, + { + provide: MAT_DIALOG_DATA, + useValue: { + clients: [ + { + '@id': '/clients/1', + uuid: 'client-uuid-1', + selected: false, + status: 'og-live', + state: 'og-live' + } + ] + } + }, + { provide: ConfigService, useValue: mockConfigService } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(RemoveCacheImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.ts b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.ts new file mode 100644 index 0000000..c650a3b --- /dev/null +++ b/ogWebconsole/src/app/components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component.ts @@ -0,0 +1,154 @@ +import {Component, Inject} from '@angular/core'; +import {MatTableDataSource} from "@angular/material/table"; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {ConfigService} from "@services/config.service"; +import {HttpClient} from "@angular/common/http"; +import {ToastrService} from "ngx-toastr"; + +@Component({ + selector: 'app-remove-cache-image', + templateUrl: './remove-cache-image.component.html', + styleUrl: './remove-cache-image.component.css' +}) +export class RemoveCacheImageComponent { + baseUrl: string; + selectedPartition: any = null; + dataSource = new MatTableDataSource(); + clientId: string | null = null; + selectedClients: any[] = []; + selectedModelClient: any = null; + filteredPartitions: any[] = []; + allSelected: boolean = false; + clientData: any[] = []; + loading: boolean = false; + columns = [ + { + columnDef: 'diskNumber', + header: 'Disco', + cell: (partition: any) => partition.diskNumber + }, + { + columnDef: 'partitionNumber', + header: 'Particion', + cell: (partition: any) => partition.partitionNumber + }, + { + columnDef: 'size', + header: 'Tamaño', + cell: (partition: any) => `${partition.size} MB` + }, + { + columnDef: 'partitionCode', + header: 'Tipo de partición', + cell: (partition: any) => partition.partitionCode + }, + { + columnDef: 'filesystem', + header: 'Sistema de ficheros', + cell: (partition: any) => partition.filesystem + }, + { + columnDef: 'operativeSystem', + header: 'SO', + cell: (partition: any) => partition.operativeSystem?.name + } + ]; + + displayedColumns = ['select', ...this.columns.map(column => column.columnDef)]; + + constructor( + @Inject(MAT_DIALOG_DATA) public data: { clients: any }, + private dialogRef: MatDialogRef, + private configService: ConfigService, + private http: HttpClient, + private toastService: ToastrService, + ) { + this.baseUrl = this.configService.apiUrl; + this.clientId = this.data.clients?.length ? this.data.clients[0]['@id'] : null; + + this.data.clients.forEach((client: { selected: boolean; status: string }) => { + if (client.status === 'og-live') { + client.selected = true; + } + }); + + this.selectedClients = this.data.clients.filter( + (client: { status: string }) => client.status === 'og-live' + ); + + this.selectedModelClient = this.data.clients.find( + (client: { status: string }) => client.status === 'og-live' + ) || null; + + if (this.selectedModelClient) { + this.loadPartitions(this.selectedModelClient); + } + } + + ngOnInit() { + + } + + loadPartitions(client: any) { + const url = `${this.baseUrl}${client.uuid}`; + this.http.get(url).subscribe( + (response: any) => { + if (response.partitions) { + this.dataSource.data = response.partitions.filter((partition: any) => { + return partition.partitionNumber !== 0 && partition.image; + }); + } + }, + (error) => { + console.error('Error al cargar los datos del cliente:', error); + } + ); + } + + toggleClientSelection(client: any) { + client.selected = !client.selected; + this.updateSelectedClients(); + } + + updateSelectedClients() { + this.selectedClients = this.data.clients.filter( + (client: { selected: boolean; state: string }) => client.selected && client.state === "og-live" + ); + + if (!this.selectedClients.includes(this.selectedModelClient)) { + this.selectedModelClient = null; + this.filteredPartitions = []; + } + } + + toggleSelectAll() { + this.allSelected = !this.allSelected; + this.data.clients.forEach((client: { selected: boolean; status: string }) => { + if (client.status === "og-live") { + client.selected = this.allSelected; + } + }); + } + + close() { + this.dialogRef.close(); + } + + execute(): void { + this.loading = true; + this.http.post(`${this.baseUrl}/clients/server/remove-cache-image`, { + clients: this.selectedClients.map((client: any) => client.uuid), + partition: this.selectedPartition['@id'] + }).subscribe( + response => { + this.toastService.success('Cliente actualizado correctamente'); + this.dialogRef.close(); + this.loading = false; + }, + error => { + this.toastService.error(error.error['hydra:description']); + this.loading = false; + } + ); + } +} diff --git a/ogWebconsole/src/app/components/dashboard/dashboard.component.css b/ogWebconsole/src/app/components/dashboard/dashboard.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/ogWebconsole/src/app/components/dashboard/dashboard.component.html b/ogWebconsole/src/app/components/dashboard/dashboard.component.html deleted file mode 100644 index c83a790..0000000 --- a/ogWebconsole/src/app/components/dashboard/dashboard.component.html +++ /dev/null @@ -1 +0,0 @@ -

dashboard works!

\ No newline at end of file diff --git a/ogWebconsole/src/app/components/dashboard/dashboard.component.spec.ts b/ogWebconsole/src/app/components/dashboard/dashboard.component.spec.ts deleted file mode 100644 index 5e8286f..0000000 --- a/ogWebconsole/src/app/components/dashboard/dashboard.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { DashboardComponent } from './dashboard.component'; - -describe('DashboardComponent', () => { - let component: DashboardComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [DashboardComponent] - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(DashboardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create the component', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/ogWebconsole/src/app/components/dashboard/dashboard.component.ts b/ogWebconsole/src/app/components/dashboard/dashboard.component.ts deleted file mode 100644 index e32c2fc..0000000 --- a/ogWebconsole/src/app/components/dashboard/dashboard.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-dashboard', - templateUrl: './dashboard.component.html', - styleUrl: './dashboard.component.css' -}) -export class DashboardComponent { - -} 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 dc0838f..006f2e8 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 @@ -10,11 +10,15 @@
- +
-
@@ -144,18 +148,21 @@
Puerto - + Dirección - + Modo Multicast - - + + {{ option.name }} @@ -163,25 +170,29 @@ Velocidad - + Máximo Clientes - + Tiempo Máximo de Espera - +
Modo P2P - - + + {{ option.name }} @@ -189,7 +200,8 @@ Semilla - +
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 7ab67dc..ff560c5 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 @@ -332,13 +332,34 @@ export class DeployImageComponent implements OnInit{ }); } + isFormValid(): boolean { + if (!this.allSelected || !this.selectedModelClient || !this.selectedImage || !this.selectedMethod || !this.selectedPartition) { + return false; + } + + if (this.isMethod('udpcast') || this.isMethod('uftp') || this.isMethod('udpcast-direct')) { + if (!this.mcastPort || !this.mcastIp || !this.mcastMode || !this.mcastSpeed || !this.mcastMaxClients || !this.mcastMaxTime) { + return false; + } + } + + if (this.isMethod('p2p')) { + if (!this.p2pMode || !this.p2pTime) { + return false; + } + } + + return true; + } + openScheduleModal(): void { const dialogRef = this.dialog.open(CreateTaskComponent, { width: '800px', data: { scope: this.runScriptContext.type, - organizationalUnit: this.runScriptContext['@id'] + organizationalUnit: this.runScriptContext['@id'], + source: 'assistant' } }); @@ -359,9 +380,9 @@ export class DeployImageComponent implements OnInit{ }; this.http.post(`${this.baseUrl}/command-task-scripts`, { - commandTask: result['@id'], + commandTask: result['taskId'] ? result['taskId']['@id'] : result['@id'], parameters: payload, - order: 1, + order: result['executionOrder'] || 1, type: 'deploy-image', }).subscribe({ next: () => { 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 2e034f9..9513cb6 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 @@ -267,5 +267,18 @@ button.remove-btn:hover { font-weight: 500; } +.instructions-box { + margin-top: 15px; + background-color: #f5f5f5; + border: 1px solid #ccc; + padding: 15px; + border-radius: 6px; +} + +.instructions-textarea textarea { + font-family: monospace; + white-space: pre; +} + 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 ec2d67c..838f72d 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 @@ -13,6 +13,12 @@ +
+ +
+
+ +
+ + + +
+
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 2eb888d..5fc8b78 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 @@ -52,6 +52,7 @@ export class PartitionAssistantComponent implements OnInit{ selectedClients: any[] = []; selectedModelClient: any = null; partitionCode: string = ''; + generatedInstructions: string = ''; constructor( private http: HttpClient, @@ -426,7 +427,8 @@ export class PartitionAssistantComponent implements OnInit{ width: '800px', data: { scope: this.runScriptContext.type, - organizationalUnit: this.runScriptContext['@id'] + organizationalUnit: this.runScriptContext['@id'], + source: 'assistant' } }); @@ -473,4 +475,39 @@ export class PartitionAssistantComponent implements OnInit{ } }); } + + generateInstructions(): void { + if (!this.selectedDisk || !this.selectedDisk.partitions) { + this.generatedInstructions = 'No hay particiones configuradas para generar instrucciones.'; + return; + } + + const diskNumber = this.selectedDisk.diskNumber; + const partitionTable = this.partitionCode || 'MSDOS'; + + let instructions = `ogCreatePartitionTable ${diskNumber} ${partitionTable}\n`; + instructions += `ogEcho log session "[0] $MSG_HELP_ogCreatePartitions"\n`; + instructions += `ogEcho session "[10] $MSG_HELP_ogUnmountAll ${diskNumber}"\n`; + instructions += `ogUnmountAll ${diskNumber} 2>/dev/null\n`; + instructions += `ogUnmountCache\n`; + instructions += `ogEcho session "[30] $MSG_HELP_ogUpdatePartitionTable ${diskNumber}"\n`; + instructions += `ogDeletePartitionTable ${diskNumber}\n`; + instructions += `ogUpdatePartitionTable ${diskNumber}\n`; + + this.selectedDisk.partitions.forEach((partition: { removed: any; partitionNumber: any; partitionCode: any; filesystem: any; size: any; format: any; }, index: any) => { + if (partition.removed) return; + + const partNumber = partition.partitionNumber; + const partType = partition.partitionCode; + const fs = partition.filesystem; + const size = partition.size; + const shouldFormat = partition.format ? 'yes' : 'no'; + + instructions += `ogCreatePartition ${diskNumber} ${partNumber} ${partType} ${fs} ${size}MB ${shouldFormat}\n`; + }); + + instructions += `ogExecAndLog command session ogListPartitions ${diskNumber}\n`; + + this.generatedInstructions = instructions; + } } 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 index b463fc0..2909ac5 100644 --- 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 @@ -203,7 +203,8 @@ export class RunScriptAssistantComponent implements OnInit{ width: '800px', data: { scope: this.runScriptContext.type, - organizationalUnit: this.runScriptContext['@id'] + organizationalUnit: this.runScriptContext['@id'], + source: 'assistant' } }); diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index 1d7f19f..2895e9a 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -11,11 +11,12 @@
- - + - - - - - @@ -224,11 +225,16 @@ {{ selectedNode?.name }}
+
+ @@ -287,7 +293,7 @@ - @@ -304,7 +310,7 @@ list_alt {{ 'procedimientosCliente' | translate }} - @@ -422,7 +428,7 @@ [runScriptContext]="getRunScriptContext([client])"> - @@ -438,7 +444,7 @@ list_alt {{ 'procedimientosCliente' | translate }} - @@ -469,4 +475,4 @@
-
\ No newline at end of file +
diff --git a/ogWebconsole/src/app/components/groups/groups.component.ts b/ogWebconsole/src/app/components/groups/groups.component.ts index c75f2fd..ade27dd 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.ts +++ b/ogWebconsole/src/app/components/groups/groups.component.ts @@ -29,6 +29,8 @@ import { MatMenuTrigger } from '@angular/material/menu'; import { ClientDetailsComponent } from './shared/client-details/client-details.component'; import { PartitionTypeOrganizatorComponent } from './shared/partition-type-organizator/partition-type-organizator.component'; import { ClientTaskLogsComponent } from '../task-logs/client-task-logs/client-task-logs.component'; +import {ChangeParentComponent} from "./shared/change-parent/change-parent.component"; +import { AuthService } from '@services/auth.service'; enum NodeType { OrganizationalUnit = 'organizational-unit', @@ -114,6 +116,7 @@ export class GroupsComponent implements OnInit, OnDestroy { private joyrideService: JoyrideService, private breakpointObserver: BreakpointObserver, private toastr: ToastrService, + public auth: AuthService, private configService: ConfigService, private cd: ChangeDetectorRef, ) { @@ -132,7 +135,7 @@ export class GroupsComponent implements OnInit, OnDestroy { ); this.treeDataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); - this.currentView = localStorage.getItem('groupsView') || 'list'; + this.currentView = this.auth.groupsView || 'list'; } @@ -864,12 +867,27 @@ export class GroupsComponent implements OnInit, OnDestroy { }); } + changeParent(event: MouseEvent, ): void { + event.stopPropagation(); + + const dialogRef = this.dialog.open(ChangeParentComponent, { + data: { clients: this.selection.selected }, + width: '700px', + }); + + dialogRef.afterClosed().subscribe((result) => { + if (result) { + this.refreshData(); + } + }); + } + openClientTaskLogs(event: MouseEvent, client: Client): void { event.stopPropagation(); this.dialog.open(ClientTaskLogsComponent, { width: '1200px', - data: {client} + data: { client } }) } } diff --git a/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.css b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.css new file mode 100644 index 0000000..6e0686f --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.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; /* Alinea texto a la izquierda y botón a la derecha */ + align-items: center; /* Centra verticalmente */ + padding: 8px; + border-bottom: 1px solid #ccc; +} + +.selected-item button { + margin-left: 10px; +} diff --git a/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.html b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.html new file mode 100644 index 0000000..a8d0f48 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.html @@ -0,0 +1,15 @@ +

Mover clientes a:

+ + + + Seleccione aula + + {{ unit.name }} + + + + +
+ + +
diff --git a/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.spec.ts b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.spec.ts new file mode 100644 index 0000000..e9e4e25 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.spec.ts @@ -0,0 +1,46 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeParentComponent } from './change-parent.component'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { ToastrService } from 'ngx-toastr'; +import { ConfigService } from '@services/config.service'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { FormsModule } from '@angular/forms'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +describe('ChangeParentComponent', () => { + let component: ChangeParentComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + const mockConfigService = { apiUrl: 'http://mock-api-url' }; + + await TestBed.configureTestingModule({ + declarations: [ChangeParentComponent], + imports: [ + HttpClientTestingModule, + MatDialogModule, + MatFormFieldModule, + MatSelectModule, + FormsModule, + BrowserAnimationsModule + ], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: { clients: [] } }, + { provide: ToastrService, useValue: { success: () => { }, error: () => { } } }, + { provide: ConfigService, useValue: mockConfigService } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ChangeParentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.ts b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.ts new file mode 100644 index 0000000..95a3256 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/change-parent/change-parent.component.ts @@ -0,0 +1,63 @@ +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"; + +@Component({ + selector: 'app-change-parent', + templateUrl: './change-parent.component.html', + styleUrl: './change-parent.component.css' +}) +export class ChangeParentComponent implements OnInit { + baseUrl: string; + loading: boolean = true; + units: any[] = []; + newOU: any; + + constructor( + private http: HttpClient, + public dialogRef: MatDialogRef, + private toastService: ToastrService, + private router: Router, + private configService: ConfigService, + @Inject(MAT_DIALOG_DATA) public data: { clients: any} + ) { + this.baseUrl = this.configService.apiUrl; + } + + ngOnInit(): void { + this.loading = true; + this.loadUnits(); + } + + loadUnits() { + this.http.get(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=500`).subscribe( + response => { + this.units = response['hydra:member']; + this.loading = false; + }, + error => console.error('Error fetching organizational units:', error) + ); + } + + save() { + this.http.post(`${this.baseUrl}/clients/change-organizational-unit`, { + clients: this.data.clients.map((client: any) => client['@id']), + organizationalUnit: this.newOU['@id'] + }).subscribe({ + next: (response) => { + this.toastService.success('Parent changed 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/login/login.component.ts b/ogWebconsole/src/app/components/login/login.component.ts index 2a55593..4a58364 100644 --- a/ogWebconsole/src/app/components/login/login.component.ts +++ b/ogWebconsole/src/app/components/login/login.component.ts @@ -3,10 +3,10 @@ import { Component, signal } from '@angular/core'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { ToastrService } from "ngx-toastr"; -import { jwtDecode } from "jwt-decode"; import { ConfigService } from '@services/config.service'; import { MatDialog } from '@angular/material/dialog'; import { GlobalStatusComponent } from '../global-status/global-status.component' +import { AuthService } from '@services/auth.service'; @Component({ selector: 'app-login', @@ -20,8 +20,6 @@ export class LoginComponent { }; errorMessage: string = ''; isLoading: boolean = false; - decodedToken: any; - baseUrl: string; constructor( @@ -29,6 +27,7 @@ export class LoginComponent { private router: Router, private configService: ConfigService, private toastService: ToastrService, + private auth: AuthService, private translateService: TranslateService, private dialog: MatDialog ) { @@ -62,13 +61,8 @@ export class LoginComponent { next: (res: any) => { if (res.token) { localStorage.setItem('loginToken', res.token); - localStorage.setItem('refreshToken', res.refreshToken); - localStorage.setItem('username', this.loginObj.username); - - this.decodedToken = jwtDecode(res.token); - localStorage.setItem('groupsView', this.decodedToken.groupsView); - - this.openSnackBar(false, 'Bienvenido ' + this.loginObj.username); + this.auth.refresh(); + this.openSnackBar(false, 'Bienvenido ' + this.auth.username); this.router.navigateByUrl('/groups'); this.dialog.open(GlobalStatusComponent, { width: '45vw', 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 4e2b71d..581e656 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,8 +30,8 @@ export class ManageRepositoryComponent implements OnInit { this.imageForm = this.fb.group({ name: [null, Validators.required], ip: [null], - sshPort: [null], - user: [null], + sshPort: ['22'], + user: ['opengnsys'], comments: [null], }); } diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css index fa2c0ba..4a3ab81 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.css @@ -44,15 +44,10 @@ table { display: flex; justify-content: space-between; align-items: center; - margin: 1.5rem 0rem 1.5rem 0rem; + margin: 1.5rem 0rem 0.5rem 0rem; box-sizing: border-box; } -.search-string { - flex: 1; - padding: 5px; -} - .search-boolean { flex: 1; padding: 5px; @@ -63,6 +58,11 @@ table { padding: 5px; } +.search-date { + flex: 1; + padding: 5px; +} + .mat-elevation-z8 { box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); } diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html index 57e84ba..843bd76 100644 --- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html +++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.html @@ -47,13 +47,29 @@ close + + + Desde + + + + + + + Hasta + + + + +
- +
-
{{ column.header }} @@ -69,7 +85,8 @@ -
+
+
{{ 'informationLabel' | translate }} +