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 @@
-
-
- group
- {{ 'labelUsers' | translate }}
-
-
- admin_panel_settings
- {{ 'labelRoles' | translate }}
-
-
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) }}
-
-
- {{ 'viewCommands' | translate }}
-
-
- {{ command.name }}
-
-
-
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) }}
+
-
{{ 'buttonCancel' | translate }}
- {{ 'buttonSave' | translate }}
+
+ {{ 'buttonSave' | translate }}
+
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
+
+
+
+
+
+ {{ allSelected ? 'Desmarcar todos' : 'Marcar todos' }}
+
+
+
+
+
+
+
+
+
+
+ {{ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancelar
+ Ejecutar
+
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
+
+
+
+
+
+ {{ allSelected ? 'Desmarcar todos' : 'Marcar todos' }}
+
+
+
+
+
+
+
+
+
+
+ {{ 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}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancelar
+ Ejecutar
+
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 @@
- Ejecutar
+ Ejecutar
-
+
schedule Opciones de programación
@@ -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 @@
Ejecutar
+
+
+ Generar instrucciones
+
+
+
schedule Opciones de programación
@@ -86,6 +92,13 @@
+
+
+
+
+
+
+
Añadir partición
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 @@
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 }}
+
+
+
+
+
+ Cancelar
+ Continuar
+
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 }}
-
+
diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.spec.ts b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.spec.ts
index 1b0b661..1adde3c 100644
--- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.spec.ts
+++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.spec.ts
@@ -13,6 +13,7 @@ import { LoadingComponent } from 'src/app/shared/loading/loading.component';
import { MatPaginatorModule } from '@angular/material/paginator';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { MatInputModule } from '@angular/material/input';
describe('ClientTaskLogsComponent', () => {
let component: ClientTaskLogsComponent;
@@ -32,6 +33,7 @@ describe('ClientTaskLogsComponent', () => {
TranslateModule.forRoot(),
MatFormFieldModule,
MatPaginatorModule,
+ MatInputModule,
MatSelectModule,
BrowserAnimationsModule
],
diff --git a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts
index f19afca..3b28e20 100644
--- a/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts
+++ b/ogWebconsole/src/app/components/task-logs/client-task-logs/client-task-logs.component.ts
@@ -35,6 +35,7 @@ export class ClientTaskLogsComponent implements OnInit {
mode: ProgressBarMode = 'buffer';
progress = 0;
bufferValue = 0;
+ today = new Date();
filteredCommands2 = Object.keys(COMMAND_TYPES).map(key => ({
name: key,
@@ -69,9 +70,9 @@ export class ClientTaskLogsComponent implements OnInit {
cell: (trace: any) => this.datePipe.transform(trace.finishedAt, 'dd/MM/yyyy hh:mm:ss'),
},
];
- displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
+ displayedColumns = [...this.columns.map(column => column.columnDef), 'information'];
- filters: { [key: string]: string } = {};
+ filters: { [key: string]: any } = {};
filteredCommands!: Observable;
commandControl = new FormControl();
@@ -134,9 +135,7 @@ export class ClientTaskLogsComponent implements OnInit {
}
onOptionCommandSelected(selectedCommand: any): void {
- this.filters['command'] = selectedCommand.id || selectedCommand.uuid;
- console.log('Comando seleccionado:', selectedCommand);
- console.log('Valor que se usará para el filtro:', selectedCommand.name);
+ this.filters['command'] = selectedCommand.name;
this.loadTraces();
}
@@ -145,6 +144,21 @@ export class ClientTaskLogsComponent implements OnInit {
this.loadTraces();
}
+ onDateFilterChange(): void {
+ const start = this.filters['startDate'];
+ const end = this.filters['endDate'];
+
+ if (!start || !end) {
+ return;
+ }
+
+ if (start && end && start > end) {
+ this.toastService.warning('La fecha de inicio no puede ser mayor que la fecha de fin');
+ return;
+ }
+ this.loadTraces();
+ }
+
openInputModal(inputData: any): void {
this.dialog.open(InputDialogComponent, {
width: '70vw',
@@ -186,18 +200,22 @@ export class ClientTaskLogsComponent implements OnInit {
this.loading = true;
- let params = new HttpParams()
- .set('client.id', clientId)
- .set('page', (this.page + 1).toString())
- .set('itemsPerPage', this.itemsPerPage.toString());
+ const params: any = {
+ 'client.id': clientId,
+ page: this.page + 1,
+ itemsPerPage: this.itemsPerPage,
+ ...this.filters
+ };
- if (this.filters['command']) {
- params = params.set('command.uuid', this.filters['command']);
+ if (params['startDate']) {
+ params['executedAt[after]'] = this.datePipe.transform(params['startDate'], 'yyyy-MM-dd');
+ delete params['startDate'];
}
-
- if (this.filters['status']) {
- params = params.set('status', this.filters['status']);
+ if (params['endDate']) {
+ params['executedAt[before]'] = this.datePipe.transform(params['endDate'], 'yyyy-MM-dd');
+ delete params['endDate'];
}
+ console.log('🌐 GET', `${this.baseUrl}/traces`, params);
const url = `${this.baseUrl}/traces`;
@@ -233,6 +251,7 @@ export class ClientTaskLogsComponent implements OnInit {
clientSearchCommandInput.value = null;
clientSearchStatusInput.value = null;
this.filters = {};
+ this.page = 0;
this.loadTraces();
}
@@ -284,7 +303,8 @@ export class ClientTaskLogsComponent implements OnInit {
'tracesTitleStep',
'resetFiltersStep',
'filtersStep',
- 'tracesTableStep',
+ 'tracesProgressStep',
+ 'tracesInfoStep',
'paginationStep'
],
showPrevButton: true,
diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.html b/ogWebconsole/src/app/components/task-logs/task-logs.component.html
index a69aaae..b010f4a 100644
--- a/ogWebconsole/src/app/components/task-logs/task-logs.component.html
+++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.html
@@ -73,8 +73,7 @@
-
+
{{ column.header }}
@@ -90,7 +89,8 @@
-
+
-
+
{{ 'informationLabel' | translate }}
-
+
diff --git a/ogWebconsole/src/app/components/task-logs/task-logs.component.ts b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts
index 8c426b4..46061fe 100644
--- a/ogWebconsole/src/app/components/task-logs/task-logs.component.ts
+++ b/ogWebconsole/src/app/components/task-logs/task-logs.component.ts
@@ -75,7 +75,7 @@ export class TaskLogsComponent implements OnInit {
cell: (trace: any) => this.datePipe.transform(trace.finishedAt, 'dd/MM/yyyy hh:mm:ss'),
},
];
- displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
+ displayedColumns = [...this.columns.map(column => column.columnDef), 'information'];
filters: { [key: string]: string } = {};
filteredClients!: Observable;
@@ -326,7 +326,8 @@ export class TaskLogsComponent implements OnInit {
'tracesTitleStep',
'resetFiltersStep',
'filtersStep',
- 'tracesTableStep',
+ 'tracesProgressStep',
+ 'tracesInfoStep',
'paginationStep'
],
showPrevButton: true,
diff --git a/ogWebconsole/src/app/guards/role.guard.spec.ts b/ogWebconsole/src/app/guards/role.guard.spec.ts
new file mode 100644
index 0000000..30809a2
--- /dev/null
+++ b/ogWebconsole/src/app/guards/role.guard.spec.ts
@@ -0,0 +1,17 @@
+import { TestBed } from '@angular/core/testing';
+import { CanActivateFn } from '@angular/router';
+
+import { roleGuard } from './role.guard';
+
+describe('roleGuard', () => {
+ const executeGuard: CanActivateFn = (...guardParameters) =>
+ TestBed.runInInjectionContext(() => roleGuard(...guardParameters));
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ });
+
+ it('should be created', () => {
+ expect(executeGuard).toBeTruthy();
+ });
+});
diff --git a/ogWebconsole/src/app/guards/role.guard.ts b/ogWebconsole/src/app/guards/role.guard.ts
new file mode 100644
index 0000000..313f4f9
--- /dev/null
+++ b/ogWebconsole/src/app/guards/role.guard.ts
@@ -0,0 +1,19 @@
+import { inject } from '@angular/core';
+import { CanActivateFn, Router } from '@angular/router';
+import { AuthService } from '@services/auth.service';
+
+export const roleGuard: CanActivateFn = (route, state) => {
+ const auth = inject(AuthService);
+ const router = inject(Router);
+
+ const currentRole = auth.getCurrentRole();
+
+ const allowedRoles = route.data?.['allowedRoles'] as string[];
+
+ if (allowedRoles?.includes(currentRole)) {
+ return true;
+ } else {
+ router.navigate(['/groups']);
+ return false;
+ }
+};
diff --git a/ogWebconsole/src/app/layout/header/header.component.html b/ogWebconsole/src/app/layout/header/header.component.html
index e9c1a50..1ad8f7d 100644
--- a/ogWebconsole/src/app/layout/header/header.component.html
+++ b/ogWebconsole/src/app/layout/header/header.component.html
@@ -18,17 +18,17 @@
{{ 'GlobalStatus' | translate }}
-
{{ 'Administration' | translate }}
-
{{ 'changePassword' | translate }}
-
{{ 'logout' | translate }}
@@ -47,10 +47,10 @@
{{ 'GlobalStatus' | translate }}
-
+
{{ 'Administration' | translate }}
-
+
{{ 'changePassword' | translate }}
@@ -65,7 +65,7 @@
matTooltipShowDelay="1000">
{{ 'labelUsers' | translate }}
-
+
{{ 'labelRoles' | translate }}
diff --git a/ogWebconsole/src/app/layout/header/header.component.ts b/ogWebconsole/src/app/layout/header/header.component.ts
index 212ef50..c009d8a 100644
--- a/ogWebconsole/src/app/layout/header/header.component.ts
+++ b/ogWebconsole/src/app/layout/header/header.component.ts
@@ -1,9 +1,9 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
-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';
+import { BreakpointObserver } from '@angular/cdk/layout';
+import { AuthService } from '@services/auth.service';
@Component({
selector: 'app-header',
@@ -16,39 +16,26 @@ export class HeaderComponent implements OnInit {
isSmallScreen: boolean = false;
@Output() toggleSidebar = new EventEmitter();
- private decodedToken: any;
- private username: any;
onToggleSidebar() {
this.toggleSidebar.emit();
}
- constructor(public dialog: MatDialog, private breakpointObserver: BreakpointObserver) { }
+ constructor(
+ public dialog: MatDialog,
+ public auth: AuthService,
+ private breakpointObserver: BreakpointObserver
+ ) { }
ngOnInit(): void {
- const token = localStorage.getItem('loginToken');
- if (token) {
- try {
- this.decodedToken = jwtDecode(token);
- this.isSuperAdmin = this.decodedToken.roles.includes('ROLE_SUPER_ADMIN');
- localStorage.setItem('isSuperAdmin', String(this.isSuperAdmin));
- this.username = this.decodedToken.username;
- } catch (error) {
- console.error('Error decoding JWT:', error);
- }
- }
this.breakpointObserver.observe(['(max-width: 576px)']).subscribe((result) => {
this.isSmallScreen = result.matches;
})
}
- ngDoCheck(): void {
- this.isSuperAdmin = localStorage.getItem('isSuperAdmin') === 'true';
- }
-
editUser() {
const dialogRef = this.dialog.open(ChangePasswordModalComponent, {
- data: { user: this.decodedToken.username, uuid: this.decodedToken.uuid },
+ data: { user: this.auth.username, uuid: this.auth.uuid },
width: '400px',
});
}
@@ -56,7 +43,11 @@ export class HeaderComponent implements OnInit {
showGlobalStatus() {
this.dialog.open(GlobalStatusComponent, {
width: '45vw',
- height: '80vh',
+ height: '80vh',
})
}
+
+ logOut() {
+ this.auth.logout();
+ }
}
diff --git a/ogWebconsole/src/app/layout/sidebar/sidebar.component.html b/ogWebconsole/src/app/layout/sidebar/sidebar.component.html
index 1aab78b..3272552 100644
--- a/ogWebconsole/src/app/layout/sidebar/sidebar.component.html
+++ b/ogWebconsole/src/app/layout/sidebar/sidebar.component.html
@@ -1,9 +1,9 @@
+ matTooltipShowDelay="1000" *ngIf="auth.userCategory !== 'ou-operator' && auth.userCategory !== 'ou-minimal'">
calendar_month
{{ 'calendars' | translate }}
@@ -102,7 +102,7 @@
+ matTooltipShowDelay="1000" *ngIf="auth.userCategory !== 'ou-operator' && auth.userCategory !== 'ou-minimal'">
terminal
{{ 'software' | translate }}
@@ -135,7 +135,8 @@
+ matTooltip="{{ 'TOOLTIP_REPOSITORIES' | translate }}" matTooltipShowDelay="1000"
+ *ngIf="auth.userCategory !== 'ou-operator' && auth.userCategory !== 'ou-minimal'">
warehouse
{{ 'repositories' | translate }}
@@ -143,10 +144,10 @@
+ matTooltipShowDelay="1000" *ngIf="auth.userCategory !== 'ou-operator' && auth.userCategory !== 'ou-minimal'">
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 83d250f..7abcc59 100644
--- a/ogWebconsole/src/app/layout/sidebar/sidebar.component.ts
+++ b/ogWebconsole/src/app/layout/sidebar/sidebar.component.ts
@@ -1,6 +1,6 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
-import { jwtDecode } from 'jwt-decode';
import { MatDialog } from '@angular/material/dialog';
+import { AuthService } from '@services/auth.service';
@Component({
selector: 'app-sidebar',
@@ -12,9 +12,7 @@ export class SidebarComponent {
@Input() sidebarMode: 'side' | 'over' = 'side';
@Output() closeSidebar: EventEmitter = new EventEmitter();
- isSuperAdmin: boolean = false;
- username: string = "";
- decodedToken: any = "";
+ username: string | null = "";
showOgBootSub: boolean = false;
showOgDhcpSub: boolean = false;
showCommandSub: boolean = false;
@@ -39,19 +37,9 @@ export class SidebarComponent {
}
}
- constructor(public dialog: MatDialog) {}
+ constructor(public dialog: MatDialog, public auth: AuthService,) {}
ngOnInit(): void {
- const token = localStorage.getItem('loginToken');
- if (token) {
- try {
- this.decodedToken = jwtDecode(token);
- this.isSuperAdmin = this.decodedToken.roles.includes('ROLE_SUPER_ADMIN');
- localStorage.setItem('isSuperAdmin', String(this.isSuperAdmin));
- this.username = this.decodedToken.username;
- } catch (error) {
- console.error('Error decoding JWT:', error);
- }
- }
+ this.username = this.auth.username
}
}
\ No newline at end of file
diff --git a/ogWebconsole/src/app/services/auth.service.ts b/ogWebconsole/src/app/services/auth.service.ts
new file mode 100644
index 0000000..48340ae
--- /dev/null
+++ b/ogWebconsole/src/app/services/auth.service.ts
@@ -0,0 +1,119 @@
+import { Injectable } from '@angular/core';
+import { Router } from '@angular/router';
+import { jwtDecode } from 'jwt-decode';
+
+export type UserCategory =
+ | 'super-admin'
+ | 'ou-admin'
+ | 'ou-operator'
+ | 'ou-minimal'
+ | 'user';
+
+interface JwtPayload {
+ roles: string[];
+ username: string;
+ id: number;
+ uuid: string;
+ groupsView: string;
+}
+
+@Injectable({ providedIn: 'root' })
+export class AuthService {
+ private tokenPayload: JwtPayload | null = null;
+
+ constructor(private router: Router) {
+ this.loadToken();
+ }
+
+ /** Intenta leer y decodificar el token de localStorage */
+ private loadToken() {
+ const token = localStorage.getItem('loginToken');
+ if (!token) return;
+ try {
+ this.tokenPayload = jwtDecode(token);
+ } catch (e) {
+ console.error('Error decoding JWT', e);
+ this.tokenPayload = null;
+ }
+ }
+
+ /** Fuerza recarga del token (por si cambió en localStorage) */
+ public refresh() {
+ this.loadToken();
+ }
+
+ /** Roles básicos que aparecen en el token */
+ private get roles(): string[] {
+ return this.tokenPayload?.roles || [];
+ }
+
+ get isSuperAdmin(): boolean {
+ return this.roles.includes('ROLE_SUPER_ADMIN');
+ }
+
+ get isOUAdmin(): boolean {
+ return this.roles.includes('ROLE_ORGANIZATIONAL_UNIT_ADMIN');
+ }
+
+ get isOUOperator(): boolean {
+ return this.roles.includes('ROLE_ORGANIZATIONAL_UNIT_OPERATOR');
+ }
+
+ get isOUMinimal(): boolean {
+ return this.roles.includes('ROLE_ORGANIZATIONAL_UNIT_MINIMAL');
+ }
+
+ /**
+ * Categoría única de usuario, según prioridades:
+ * - super-admin
+ * - ou-admin
+ * - ou-operator
+ * - ou-minimal
+ * - user (fallback)
+ */
+ get userCategory(): UserCategory {
+ if (this.isSuperAdmin) return 'super-admin';
+ if (this.isOUAdmin) return 'ou-admin';
+ if (this.isOUOperator) return 'ou-operator';
+ if (this.isOUMinimal) return 'ou-minimal';
+ return 'user';
+ }
+
+ /** Nombre de usuario */
+ get username(): string | null {
+ return this.tokenPayload?.username || null;
+ }
+
+ /** Uuid del usuario*/
+ get uuid(): any | null {
+ return this.tokenPayload?.uuid || null;
+ }
+
+ /** groupsView del usuario */
+ get groupsView(): string | null {
+ return this.tokenPayload?.groupsView || null;
+ }
+
+ /**
+ * Devuelve el rol principal del usuario según prioridad.
+ */
+ getCurrentRole(): string {
+ if (this.isSuperAdmin) return 'super-admin';
+ if (this.isOUAdmin) return 'ou-admin';
+ if (this.isOUOperator) return 'ou-operator';
+ if (this.isOUMinimal) return 'ou-minimal';
+ return 'user';
+ }
+
+ /** Logout: limpia tokens y redirige al login */
+ logout(): void {
+ localStorage.removeItem('loginToken');
+ localStorage.removeItem('refreshToken');
+ localStorage.removeItem('isSuperAdmin');
+ localStorage.removeItem('username');
+ localStorage.removeItem('groupsView');
+ localStorage.removeItem('language');
+ this.tokenPayload = null;
+ this.router.navigate(['/auth/login']);
+ }
+}
diff --git a/ogWebconsole/src/app/shared/page-not-found/page-not-found.component.css b/ogWebconsole/src/app/shared/page-not-found/page-not-found.component.css
index e69de29..b9b0ebb 100644
--- a/ogWebconsole/src/app/shared/page-not-found/page-not-found.component.css
+++ b/ogWebconsole/src/app/shared/page-not-found/page-not-found.component.css
@@ -0,0 +1,16 @@
+.main-container {
+ height: 100dvh;
+ display: grid;
+ place-content: center;
+}
+
+.content-container {
+ padding: 4em;
+ border: 1px solid #3f51b5;
+ border-radius: 30px;
+}
+
+.message {
+ font-size: 25px;
+ font-weight: 500;
+}
\ No newline at end of file
diff --git a/ogWebconsole/src/app/shared/page-not-found/page-not-found.component.html b/ogWebconsole/src/app/shared/page-not-found/page-not-found.component.html
index cc75e49..f611eb0 100644
--- a/ogWebconsole/src/app/shared/page-not-found/page-not-found.component.html
+++ b/ogWebconsole/src/app/shared/page-not-found/page-not-found.component.html
@@ -1 +1,7 @@
-page-not-found works!
+
+
+
+ Page not found
+
+
+
\ No newline at end of file
diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json
index 95239e0..fcfacdf 100644
--- a/ogWebconsole/src/locale/en.json
+++ b/ogWebconsole/src/locale/en.json
@@ -517,6 +517,8 @@
"hardwareInventory": "Hardware Inventory",
"runScript": "Run Script"
},
+ "changeOU": "Change Organizational Unit",
+ "moveClientsTooltip": "Move clients to another organizational unit",
"remoteAccess": "Remote access available",
"noRemoteAccess": "Remote access not available",
"capacityWarning": "The capacity cannot be negative",
@@ -530,5 +532,6 @@
"isDefaultLabel": "Default",
"tracesTitleStepText": "In this screen, you can see the execution traces of each client, with its id, command, real-time status, date and actions to be performed.",
"filtersStepText": "Here you can see the different filters to apply to the table information.",
- "tracesTableStepText": "This is the table with the execution traces updated in real time."
-}
\ No newline at end of file
+ "tracesProgressStepText": "Here you can see the execution status updated in real time.",
+ "tracesInfoStepText": "Here you can consult detailed information about the specific trace."
+}
diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json
index ca67024..d130767 100644
--- a/ogWebconsole/src/locale/es.json
+++ b/ogWebconsole/src/locale/es.json
@@ -518,6 +518,8 @@
"hardwareInventory": "Inventario Hardware",
"runScript": "Ejecutar script"
},
+ "changeOU": "Mover clientes",
+ "moveClientsTooltip": "Mover clientes a otra unidad organizativa",
"remoteAccess": "Disponible acceso remoto",
"noRemoteAccess": "No disponible acceso remoto",
"capacityWarning": "El aforo no puede ser",
@@ -533,5 +535,6 @@
"isDefaultLabel": "Por defecto",
"tracesTitleStepText": "En esta pantalla, puedes ver las trazas de ejecución de cada cliente, con su id, comando, estado en tiempo real, fecha y acciones a realizar.",
"filtersStepText": "Aquí puedes ver los diferentes filtros que aplicar a la información de la tabla.",
- "tracesTableStepText": "Esta es la tabla con las trazas de ejecución actualizadas en tiempo real."
+ "tracesProgressStepText": "Aquí puedes ver el estado de ejecución actualizado en tiempo real.",
+ "tracesInfoStepText": "Aquí puedes consultar información detallada de la traza en concreto."
}