Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions src/app/core/navbar/navbar.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@
<mat-icon matListItemIcon>wifi</mat-icon
><span i18n="navlink">{{ 'wireless.header.wirelessTitle.value' | translate }}</span>
</a>
<a
mat-list-item
routerLink="/ciraconfigs"
routerLinkActive="active"
[matTooltipPosition]="'right'"
[matTooltip]="ciraTitle">
<mat-icon matListItemIcon>settings_ethernet</mat-icon
><span i18n="navlink">{{ 'configs.header.ciraTitle.value' | translate }}</span>
</a>
@if (ciraEnabled()) {
<a
mat-list-item
routerLink="/ciraconfigs"
routerLinkActive="active"
[matTooltipPosition]="'right'"
[matTooltip]="ciraTitle">
<mat-icon matListItemIcon>settings_ethernet</mat-icon
><span i18n="navlink">{{ 'configs.header.ciraTitle.value' | translate }}</span>
</a>
}
@if (cloudMode === true) {
<a mat-list-item routerLink="/proxy-configs" routerLinkActive="active">
<mat-icon matListItemIcon>router</mat-icon
Expand Down
40 changes: 38 additions & 2 deletions src/app/core/navbar/navbar.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,22 @@ import { MatIconModule } from '@angular/material/icon'
import { MatListModule } from '@angular/material/list'
import { NavbarComponent } from './navbar.component'
import { ActivatedRoute, RouterModule } from '@angular/router'
import { of } from 'rxjs'
import { of, throwError } from 'rxjs'
import { provideHttpClient } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { TranslateModule, TranslateService } from '@ngx-translate/core'
import { TRANSLATE_HTTP_LOADER_CONFIG } from '@ngx-translate/http-loader'
import { ServerFeaturesService } from '../../server-features.service'

describe('NavbarComponent', () => {
let component: NavbarComponent
let fixture: ComponentFixture<NavbarComponent>
let translate: TranslateService
let serverFeaturesSpy: jasmine.SpyObj<ServerFeaturesService>

beforeEach(() => {
serverFeaturesSpy = jasmine.createSpyObj('ServerFeaturesService', ['getFeatures'])
serverFeaturesSpy.getFeatures.and.returnValue(of({ ciraEnabled: true }))
TestBed.configureTestingModule({
imports: [
MatIconModule,
Expand All @@ -33,6 +37,7 @@ describe('NavbarComponent', () => {
providers: [
{ provide: ActivatedRoute, useValue: { params: of({ id: 'guid' }) } },
{ provide: TRANSLATE_HTTP_LOADER_CONFIG, useValue: { prefix: '/assets/i18n/', suffix: '.json' } },
{ provide: ServerFeaturesService, useValue: serverFeaturesSpy },
TranslateService,
provideHttpClient(),
provideHttpClientTesting()
Expand All @@ -42,7 +47,6 @@ describe('NavbarComponent', () => {
component = fixture.componentInstance
translate = TestBed.inject(TranslateService)
translate.setFallbackLang('en')
fixture.detectChanges()
})

afterEach(() => {
Expand All @@ -52,4 +56,36 @@ describe('NavbarComponent', () => {
it('should create', () => {
expect(component).toBeTruthy()
})

it('should not query server features in cloud mode and keep the CIRA tab enabled', () => {
component.cloudMode = true
serverFeaturesSpy.getFeatures.calls.reset()
component.ngOnInit()
expect(serverFeaturesSpy.getFeatures).not.toHaveBeenCalled()
expect(component.ciraEnabled()).toBeTrue()
})

it('should disable the CIRA tab when the server reports CIRA disabled (enterprise mode)', () => {
component.cloudMode = false
serverFeaturesSpy.getFeatures.and.returnValue(of({ ciraEnabled: false }))
fixture.detectChanges()
expect(serverFeaturesSpy.getFeatures).toHaveBeenCalled()
expect(component.ciraEnabled()).toBeFalse()
expect(fixture.nativeElement.querySelector('a[routerlink="/ciraconfigs"]')).toBeNull()
})
Comment thread
madhavilosetty-intel marked this conversation as resolved.

it('should enable the CIRA tab when the server reports CIRA enabled (enterprise mode)', () => {
component.cloudMode = false
serverFeaturesSpy.getFeatures.and.returnValue(of({ ciraEnabled: true }))
fixture.detectChanges()
expect(component.ciraEnabled()).toBeTrue()
expect(fixture.nativeElement.querySelector('a[routerlink="/ciraconfigs"]')).not.toBeNull()
})
Comment thread
madhavilosetty-intel marked this conversation as resolved.

it('should fail open and keep the CIRA tab enabled when the features call errors (enterprise mode)', () => {
component.cloudMode = false
serverFeaturesSpy.getFeatures.and.returnValue(throwError(() => new Error('failed')))
component.ngOnInit()
expect(component.ciraEnabled()).toBeTrue()
})
})
21 changes: 19 additions & 2 deletions src/app/core/navbar/navbar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
* SPDX-License-Identifier: Apache-2.0
**********************************************************************/

import { Component, inject } from '@angular/core'
import { Component, OnInit, inject, signal } from '@angular/core'
import { environment } from '../../../environments/environment'
import { MatIcon } from '@angular/material/icon'
import { RouterLink, RouterLinkActive } from '@angular/router'
import { MatDivider } from '@angular/material/divider'
import { MatNavList, MatListItem, MatListItemIcon } from '@angular/material/list'
import { MatTooltip } from '@angular/material/tooltip'
import { TranslateModule, TranslateService } from '@ngx-translate/core'
import { ServerFeaturesService } from '../../server-features.service'

@Component({
selector: 'app-navbar',
Expand All @@ -28,10 +29,26 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core'
TranslateModule
]
})
export class NavbarComponent {
export class NavbarComponent implements OnInit {
cloudMode = environment.cloud
showSubNav = false
// CIRA tab visibility. Cloud (MPS+RPS) always shows it; in enterprise it is
// driven by the Console server's APP_DISABLE_CIRA setting (fetched on init).
// Start from cloudMode so enterprise hides the tab until the API responds,
// avoiding a flash of the tab when the server reports CIRA disabled.
ciraEnabled = signal(this.cloudMode)
private readonly translate = inject(TranslateService)
private readonly serverFeaturesService = inject(ServerFeaturesService)

ngOnInit(): void {
if (this.cloudMode === false) {
this.serverFeaturesService.getFeatures().subscribe({
next: (features) => this.ciraEnabled.set(features.ciraEnabled),
// Fail open: if the features call fails, keep the CIRA tab visible.
error: () => this.ciraEnabled.set(true)
})
}
}

get ciraTitle(): string {
return this.cloudMode === false
Expand Down
73 changes: 73 additions & 0 deletions src/app/server-features.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*********************************************************************
* Copyright (c) Intel Corporation 2022
* SPDX-License-Identifier: Apache-2.0
**********************************************************************/

import { TestBed } from '@angular/core/testing'
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'
import { provideHttpClient } from '@angular/common/http'
import { ServerFeaturesService } from './server-features.service'
import { AuthService } from './auth.service'
import { environment } from '../environments/environment'
import { ServerFeatures } from '../models/models'

describe('ServerFeaturesService', () => {
let service: ServerFeaturesService
let httpMock: HttpTestingController
let authServiceSpy: jasmine.SpyObj<AuthService>

const mockEnvironment = { rpsServer: 'https://rps-server' }
const mockUrl = `${mockEnvironment.rpsServer}/api/v1/server/features`

beforeEach(() => {
authServiceSpy = jasmine.createSpyObj('AuthService', ['onError'])
environment.rpsServer = mockEnvironment.rpsServer
TestBed.configureTestingModule({
providers: [
ServerFeaturesService,
{ provide: AuthService, useValue: authServiceSpy },
provideHttpClient(),
provideHttpClientTesting()
]
})

service = TestBed.inject(ServerFeaturesService)
httpMock = TestBed.inject(HttpTestingController)
})

afterEach(() => {
httpMock.verify()
})

it('should be created', () => {
expect(service).toBeTruthy()
})

describe('getFeatures', () => {
it('should call the API and return the server features', () => {
const mockResponse: ServerFeatures = { ciraEnabled: true }

service.getFeatures().subscribe((response) => {
expect(response).toEqual(mockResponse)
})

const req = httpMock.expectOne(mockUrl)
expect(req.request.method).toBe('GET')
req.flush(mockResponse)
})

it('should handle errors', () => {
const mockError = { status: 500, statusText: 'Internal Server Error' }
authServiceSpy.onError.and.returnValue(['Error occurred'])

service.getFeatures().subscribe({
error: (error) => {
expect(error).toEqual(['Error occurred'])
}
})

const req = httpMock.expectOne(mockUrl)
req.flush(null, mockError)
})
})
})
31 changes: 31 additions & 0 deletions src/app/server-features.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*********************************************************************
* Copyright (c) Intel Corporation 2022
* SPDX-License-Identifier: Apache-2.0
**********************************************************************/

import { HttpClient } from '@angular/common/http'
import { Injectable, inject } from '@angular/core'
import { Observable, throwError } from 'rxjs'
import { catchError } from 'rxjs/operators'
import { environment } from '../environments/environment'
import { ServerFeatures } from '../models/models'
import { AuthService } from './auth.service'

@Injectable({
providedIn: 'root'
})
export class ServerFeaturesService {
private readonly authService = inject(AuthService)
private readonly http = inject(HttpClient)

private readonly url = `${environment.rpsServer}/api/v1/server/features`

getFeatures(): Observable<ServerFeatures> {
return this.http.get<ServerFeatures>(this.url).pipe(
catchError((err) => {
const errorMessages = this.authService.onError(err)
return throwError(errorMessages)
})
)
}
}
5 changes: 5 additions & 0 deletions src/models/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,3 +538,8 @@ export interface DisplaySelectionResponse {
export interface DisplaySelectionRequest {
displayIndex: number
}

// Server-level capability flags exposed by the Console API (enterprise build).
export interface ServerFeatures {
ciraEnabled: boolean
}
Loading