Corrected ou-operator bug
parent
348df41425
commit
2b77eb8afa
|
|
@ -54,6 +54,11 @@ test.describe('OU Operator — solo grupos y operaciones de equipos', () => {
|
|||
const finalUrl = await navigateTo(page, '/clients/run-script');
|
||||
expect(finalUrl).toContain('/clients/run-script');
|
||||
});
|
||||
|
||||
test('accede a /commands-logs (trazas de sus acciones)', async ({ page }) => {
|
||||
const finalUrl = await navigateTo(page, '/commands-logs');
|
||||
expect(finalUrl).toContain('/commands-logs');
|
||||
});
|
||||
});
|
||||
|
||||
// ── GUI en /groups: botones de crear OU/cliente ocultos ─────────────────────
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ const routes: Routes = [
|
|||
{ 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: 'commands-logs', component: TaskLogsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin', 'ou-operator'] } },
|
||||
{ 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', 'ou-operator'] } },
|
||||
{ path: 'clients/partition-assistant', component: PartitionAssistantComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin', 'ou-operator'] } },
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { HttpClient } from '@angular/common/http';
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
import { AuthService } from '@services/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-global-status',
|
||||
|
|
@ -67,7 +68,8 @@ export class GlobalStatusComponent implements OnInit {
|
|||
constructor(
|
||||
private configService: ConfigService,
|
||||
private toastService: ToastrService,
|
||||
private http: HttpClient
|
||||
private http: HttpClient,
|
||||
private auth: AuthService
|
||||
) {
|
||||
this.baseUrl = this.configService.apiUrl;
|
||||
this.dhcpsApiUrl = `${this.baseUrl}/dhcp-servers`;
|
||||
|
|
@ -115,7 +117,18 @@ export class GlobalStatusComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Las sincronizaciones con la infraestructura (subredes DHCP, plantillas PXE y
|
||||
* ogLives) están restringidas a ROLE_SUPER_ADMIN en ogCore; para el resto de
|
||||
* perfiles devuelven "Access Denied", así que ni siquiera se lanzan.
|
||||
*/
|
||||
private get canSyncInfrastructure(): boolean {
|
||||
return this.auth.isSuperAdmin;
|
||||
}
|
||||
|
||||
syncSubnets() {
|
||||
if (!this.canSyncInfrastructure) return;
|
||||
|
||||
this.http.get<any>(`${this.dhcpsApiUrl}?page=1&itemsPerPage=10`).subscribe(data => {
|
||||
const dhcps = data['hydra:member'] || [];
|
||||
if (dhcps.length === 0) return;
|
||||
|
|
@ -155,6 +168,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
}
|
||||
|
||||
syncTemplates() {
|
||||
if (!this.canSyncInfrastructure) return;
|
||||
if (this.bootServers.length === 0) return;
|
||||
|
||||
let pending = this.bootServers.length;
|
||||
|
|
@ -189,6 +203,7 @@ export class GlobalStatusComponent implements OnInit {
|
|||
}
|
||||
|
||||
syncOgLives(): void {
|
||||
if (!this.canSyncInfrastructure) return;
|
||||
if (this.bootServers.length === 0) return;
|
||||
|
||||
let pending = this.bootServers.length;
|
||||
|
|
|
|||
|
|
@ -64,10 +64,14 @@ export class LoginComponent {
|
|||
this.auth.refresh();
|
||||
this.openSnackBar(false, 'Bienvenido ' + this.auth.username);
|
||||
this.router.navigateByUrl('/groups');
|
||||
this.dialog.open(GlobalStatusComponent, {
|
||||
width: '65vw',
|
||||
height: '80vh',
|
||||
});
|
||||
// El estado global consulta y sincroniza infraestructura (ogBoot, DHCP,
|
||||
// repositorios), a la que solo tienen acceso los perfiles de administración.
|
||||
if (this.auth.isSuperAdmin || this.auth.isOUAdmin) {
|
||||
this.dialog.open(GlobalStatusComponent, {
|
||||
width: '65vw',
|
||||
height: '80vh',
|
||||
});
|
||||
}
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -105,19 +105,19 @@ export class ClientPendingTasksComponent implements OnInit {
|
|||
eventSource.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data && data['@id']) {
|
||||
this.updateTracesStatus(data['@id'], data.status);
|
||||
this.updateTraceFromEvent(data['@id'], data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateTracesStatus(clientUuid: string, newStatus: string): void {
|
||||
const traceIndex = this.traces.findIndex(trace => trace['@id'] === clientUuid);
|
||||
private updateTraceFromEvent(traceId: string, data: any): void {
|
||||
const traceIndex = this.traces.findIndex(trace => trace['@id'] === traceId);
|
||||
if (traceIndex !== -1) {
|
||||
const updatedTraces = [...this.traces];
|
||||
|
||||
updatedTraces[traceIndex] = {
|
||||
...updatedTraces[traceIndex],
|
||||
status: newStatus
|
||||
...data
|
||||
};
|
||||
|
||||
this.traces = updatedTraces;
|
||||
|
|
|
|||
|
|
@ -107,28 +107,23 @@ export class ClientTaskLogsComponent implements OnInit {
|
|||
eventSource.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data && data['@id']) {
|
||||
this.updateTracesStatus(data['@id'], data.status, data.progress);
|
||||
this.updateTraceFromEvent(data['@id'], data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateTracesStatus(clientUuid: string, newStatus: string, progress: Number): void {
|
||||
const traceIndex = this.traces.findIndex(trace => trace['@id'] === clientUuid);
|
||||
private updateTraceFromEvent(traceId: string, data: any): void {
|
||||
const traceIndex = this.traces.findIndex(trace => trace['@id'] === traceId);
|
||||
if (traceIndex !== -1) {
|
||||
const updatedTraces = [...this.traces];
|
||||
|
||||
updatedTraces[traceIndex] = {
|
||||
...updatedTraces[traceIndex],
|
||||
status: newStatus,
|
||||
progress: progress
|
||||
...data
|
||||
};
|
||||
|
||||
this.traces = updatedTraces;
|
||||
this.cdr.detectChanges();
|
||||
|
||||
console.log(`Estado actualizado para la traza ${clientUuid}: ${newStatus}`);
|
||||
} else {
|
||||
console.warn(`Traza con UUID ${clientUuid} no encontrado en la lista.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ export class TaskLogsComponent implements OnInit, OnDestroy {
|
|||
this.eventSource.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data && data['@id']) {
|
||||
this.updateTracesStatus(data['@id'], data.status, data.progress);
|
||||
this.updateTraceFromEvent(data['@id'], data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -439,15 +439,14 @@ export class TaskLogsComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
private updateTracesStatus(clientUuid: string, newStatus: string, progress: Number): void {
|
||||
const traceIndex = this.traces.findIndex(trace => trace['@id'] === clientUuid);
|
||||
private updateTraceFromEvent(traceId: string, data: any): void {
|
||||
const traceIndex = this.traces.findIndex(trace => trace['@id'] === traceId);
|
||||
if (traceIndex !== -1) {
|
||||
const updatedTraces = [...this.traces];
|
||||
|
||||
updatedTraces[traceIndex] = {
|
||||
...updatedTraces[traceIndex],
|
||||
status: newStatus,
|
||||
progress: progress
|
||||
...data
|
||||
};
|
||||
|
||||
this.traces = updatedTraces;
|
||||
|
|
|
|||
|
|
@ -139,7 +139,6 @@ describe('roleGuard — control de acceso por rol', () => {
|
|||
// super-admin + ou-admin
|
||||
{ path: 'commands', allowedRoles: ['super-admin', 'ou-admin'] },
|
||||
{ path: 'commands-task', allowedRoles: ['super-admin', 'ou-admin'] },
|
||||
{ path: 'commands-logs', allowedRoles: ['super-admin', 'ou-admin'] },
|
||||
{ path: 'pxe-images', allowedRoles: ['super-admin', 'ou-admin'] },
|
||||
{ path: 'pxe', allowedRoles: ['super-admin', 'ou-admin'] },
|
||||
{ path: 'pxe-boot-file', allowedRoles: ['super-admin', 'ou-admin'] },
|
||||
|
|
@ -156,6 +155,7 @@ describe('roleGuard — control de acceso por rol', () => {
|
|||
{ path: 'hardware-profiles', allowedRoles: ['super-admin', 'ou-admin'] },
|
||||
{ path: 'hardware-types', allowedRoles: ['super-admin', 'ou-admin'] },
|
||||
// super-admin + ou-admin + ou-operator
|
||||
{ path: 'commands-logs', allowedRoles: ['super-admin', 'ou-admin', 'ou-operator'] },
|
||||
{ path: 'clients/deploy-image', allowedRoles: ['super-admin', 'ou-admin', 'ou-operator'] },
|
||||
{ path: 'clients/partition-assistant',allowedRoles: ['super-admin', 'ou-admin', 'ou-operator'] },
|
||||
{ path: 'clients/run-script', allowedRoles: ['super-admin', 'ou-admin', 'ou-operator'] },
|
||||
|
|
|
|||
|
|
@ -21,9 +21,11 @@
|
|||
{{ 'downloadAgent' | translate }}
|
||||
</a>
|
||||
|
||||
<button class="ordinary-button" (click)="showGlobalStatus()">
|
||||
{{ 'GlobalStatus' | translate }}
|
||||
</button>
|
||||
@if (auth.userCategory === 'super-admin' || auth.userCategory === 'ou-admin') {
|
||||
<button class="ordinary-button" (click)="showGlobalStatus()">
|
||||
{{ 'GlobalStatus' | translate }}
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (auth.userCategory === 'super-admin') {
|
||||
<button class="ordinary-button" [matMenuTriggerFor]="menu"
|
||||
|
|
@ -75,9 +77,11 @@
|
|||
<mat-icon>download</mat-icon>
|
||||
{{ 'downloadAgent' | translate }}
|
||||
</a>
|
||||
<button mat-menu-item (click)="showGlobalStatus()">
|
||||
{{ 'GlobalStatus' | translate }}
|
||||
</button>
|
||||
@if (auth.userCategory === 'super-admin' || auth.userCategory === 'ou-admin') {
|
||||
<button mat-menu-item (click)="showGlobalStatus()">
|
||||
{{ 'GlobalStatus' | translate }}
|
||||
</button>
|
||||
}
|
||||
@if (auth.userCategory === 'super-admin') {
|
||||
<button mat-menu-item [matMenuTriggerFor]="menu">
|
||||
{{ 'Administration' | translate }}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
</mat-list-item>
|
||||
|
||||
<mat-list-item (click)="toggleCommandSub()" matTooltip="{{ 'TOOLTIP_ACTIONS' | translate }}"
|
||||
matTooltipShowDelay="1000" *ngIf="auth.userCategory !== 'ou-operator' && auth.userCategory !== 'ou-minimal'">
|
||||
matTooltipShowDelay="1000" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">playlist_play</mat-icon>
|
||||
<span>{{ 'actions' | translate }}</span>
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item routerLink="/commands-logs" (click)="onItemClick()" matTooltip="{{ 'TOOLTIP_TRACE_LOGS' | translate }}"
|
||||
matTooltipShowDelay="1000" *ngIf="auth.userCategory !== 'ou-operator' && auth.userCategory !== 'ou-minimal'">
|
||||
matTooltipShowDelay="1000" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">chevron_right</mat-icon>
|
||||
<span>{{ 'traceLogs' | translate }}</span>
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ describe('SidebarComponent — visibilidad de elementos por rol', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// ── Ou-operator: solo /groups ──────────────────────────────────────────────
|
||||
describe('ou-operator — solo ve /groups', () => {
|
||||
// ── Ou-operator: /groups y las trazas de sus acciones ──────────────────────
|
||||
describe('ou-operator — ve /groups y /commands-logs', () => {
|
||||
beforeEach(async () => createComponent('ou-operator'));
|
||||
|
||||
it('ve /groups', () => {
|
||||
|
|
@ -130,9 +130,22 @@ describe('SidebarComponent — visibilidad de elementos por rol', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('NO ve el menú de acciones (commands)', () => {
|
||||
// Solo debe haber 1 mat-list-item visible: /groups
|
||||
expect(countVisibleListItems(fixture)).toBe(1);
|
||||
it('ve el menú de acciones (commands) para acceder a las trazas', () => {
|
||||
// /groups + el desplegable de acciones
|
||||
expect(countVisibleListItems(fixture)).toBe(2);
|
||||
});
|
||||
|
||||
it('ve el enlace a /commands-logs al desplegar el menú de acciones', () => {
|
||||
component.toggleCommandSub();
|
||||
fixture.detectChanges();
|
||||
expect(hasRouteLink(fixture, '/commands-logs')).toBeTrue();
|
||||
});
|
||||
|
||||
it('NO ve los enlaces de gestión de comandos ni tareas', () => {
|
||||
component.toggleCommandSub();
|
||||
fixture.detectChanges();
|
||||
expect(hasRouteLink(fixture, '/commands')).toBeFalse();
|
||||
expect(hasRouteLink(fixture, '/commands-task')).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue