Skip to content

Commit e9e9989

Browse files
committed
feat: hide KVM tab on ISM systems and use IDER tab
1 parent 5eec359 commit e9e9989

25 files changed

Lines changed: 5450 additions & 3602 deletions

src/app/devices/device-detail/device-detail.component.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
matTooltipPosition="left">
1111
<mat-icon matListItemIcon>{{ !isCollapsed ? 'chevron_right' : 'chevron_left' }}</mat-icon>
1212
</a>
13-
@for (item of categories; track item) {
13+
@for (item of categories(); track item) {
1414
<a
1515
mat-list-item
1616
(click)="setCurrentView(item)"
@@ -41,6 +41,9 @@ <h3 matListItemTitle>{{ item.name | translate }}</h3>
4141
@case ('kvm') {
4242
<app-kvm [deviceId]="deviceId"></app-kvm>
4343
}
44+
@case ('ider') {
45+
<app-ider [deviceId]="deviceId"></app-ider>
46+
}
4447
@case ('sol') {
4548
<app-sol [deviceId]="deviceId"></app-sol>
4649
}

src/app/devices/device-detail/device-detail.component.spec.ts

Lines changed: 87 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,35 @@
55

66
import { ComponentFixture, TestBed } from '@angular/core/testing'
77
import { NoopAnimationsModule } from '@angular/platform-browser/animations'
8-
import { ActivatedRoute, RouterModule, provideRouter } from '@angular/router'
9-
import { of } from 'rxjs'
8+
import { ActivatedRoute, provideRouter } from '@angular/router'
9+
import { ReplaySubject, of } from 'rxjs'
1010
import { DevicesService } from '../devices.service'
1111
import { DeviceDetailComponent } from './device-detail.component'
1212
import { provideNativeDateAdapter } from '@angular/material/core'
1313
import { Component, signal, input } from '@angular/core'
1414
import { DeviceToolbarComponent } from '../device-toolbar/device-toolbar.component'
1515
import { GeneralComponent } from '../general/general.component'
16+
import { IderComponent } from '../ider/ider.component'
1617
import { provideTranslateService } from '@ngx-translate/core'
1718

1819
describe('DeviceDetailComponent', () => {
1920
let component: DeviceDetailComponent
2021
let fixture: ComponentFixture<DeviceDetailComponent>
22+
let routeParams$: ReplaySubject<any>
23+
let devicesServiceSpy: jasmine.SpyObj<DevicesService>
24+
25+
const makeAmtVersion = (sku: string) => ({
26+
CIM_SoftwareIdentity: {
27+
responses: [
28+
{},
29+
{},
30+
{},
31+
{},
32+
{ VersionString: sku }
33+
]
34+
}
35+
})
36+
2137
@Component({
2238
selector: 'app-device-toolbar',
2339
imports: []
@@ -37,37 +53,100 @@ describe('DeviceDetailComponent', () => {
3753
public readonly deviceId = input('')
3854
}
3955

56+
@Component({
57+
selector: 'app-ider',
58+
imports: []
59+
})
60+
class TestIderComponent {
61+
public readonly deviceId = input('')
62+
}
63+
4064
beforeEach(() => {
65+
routeParams$ = new ReplaySubject<any>(1)
66+
devicesServiceSpy = jasmine.createSpyObj('DevicesService', ['getAMTVersion'])
67+
devicesServiceSpy.getAMTVersion.and.returnValue(of(makeAmtVersion('16400') as any))
68+
4169
TestBed.configureTestingModule({
4270
imports: [
4371
NoopAnimationsModule,
44-
RouterModule,
4572
DeviceDetailComponent,
4673
TestDeviceToolbarComponent
4774
],
4875
providers: [
4976
provideTranslateService(),
5077
provideRouter([]), // Provide an empty router configuration
5178
provideNativeDateAdapter(),
52-
{ provide: DevicesService, useValue: {} },
79+
{ provide: DevicesService, useValue: devicesServiceSpy },
5380
{
5481
provide: ActivatedRoute,
5582
useValue: {
56-
params: of({ id: 'guid' })
83+
params: routeParams$.asObservable()
5784
}
5885
}
5986
]
6087
}).overrideComponent(DeviceDetailComponent, {
61-
remove: { imports: [DeviceToolbarComponent, GeneralComponent] },
62-
add: { imports: [TestDeviceToolbarComponent, TestGeneralComponent] }
88+
remove: { imports: [
89+
DeviceToolbarComponent,
90+
GeneralComponent,
91+
IderComponent
92+
] },
93+
add: { imports: [
94+
TestDeviceToolbarComponent,
95+
TestGeneralComponent,
96+
TestIderComponent
97+
] }
6398
})
6499

65100
fixture = TestBed.createComponent(DeviceDetailComponent)
66101
component = fixture.componentInstance
67-
component.ngOnInit()
68102
})
69103

70104
it('should create', () => {
105+
routeParams$.next({ id: 'guid' })
106+
fixture.detectChanges()
71107
expect(component).toBeTruthy()
108+
expect(devicesServiceSpy.getAMTVersion).toHaveBeenCalledWith('guid')
109+
})
110+
111+
it('shows IDER and hides KVM for ISM systems', () => {
112+
routeParams$.next({ id: 'guid' })
113+
fixture.detectChanges()
114+
115+
const components = component.categories().map((c) => c.component)
116+
expect(components).toContain('ider')
117+
expect(components).not.toContain('kvm')
118+
})
119+
120+
it('shows KVM and hides IDER for non-ISM systems', () => {
121+
devicesServiceSpy.getAMTVersion.and.returnValue(of(makeAmtVersion('99999') as any))
122+
routeParams$.next({ id: 'guid' })
123+
fixture.detectChanges()
124+
125+
const components = component.categories().map((c) => c.component)
126+
expect(components).toContain('kvm')
127+
expect(components).not.toContain('ider')
128+
})
129+
130+
it('sets currentView from route component param', () => {
131+
routeParams$.next({ id: 'guid', component: 'ider' })
132+
fixture.detectChanges()
133+
134+
expect(component.currentView).toBe('ider')
135+
})
136+
137+
it('includes enterprise-only categories only when not cloud mode', () => {
138+
routeParams$.next({ id: 'guid' })
139+
fixture.detectChanges()
140+
141+
const components = component.categories().map((c) => c.component)
142+
if (component.isCloudMode) {
143+
expect(components).not.toContain('explorer')
144+
expect(components).not.toContain('network-settings')
145+
expect(components).not.toContain('tls')
146+
} else {
147+
expect(components).toContain('explorer')
148+
expect(components).toContain('network-settings')
149+
expect(components).toContain('tls')
150+
}
72151
})
73152
})

src/app/devices/device-detail/device-detail.component.ts

Lines changed: 114 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
**********************************************************************/
55

6-
import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'
6+
import { Component, OnDestroy, OnInit, computed, inject, signal } from '@angular/core'
77
import { ReactiveFormsModule } from '@angular/forms'
88
import { MatList, MatListItem, MatListItemTitle, MatListItemLine, MatListModule } from '@angular/material/list'
99
import { provideNativeDateAdapter } from '@angular/material/core'
@@ -12,7 +12,7 @@ import { MatTooltip } from '@angular/material/tooltip'
1212
import { MatSidenavContainer, MatSidenav, MatSidenavContent } from '@angular/material/sidenav'
1313
import { DeviceToolbarComponent } from '../device-toolbar/device-toolbar.component'
1414
import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router'
15-
import { Subject, takeUntil } from 'rxjs'
15+
import { catchError, of, Subject, switchMap, takeUntil } from 'rxjs'
1616
import { ExplorerComponent } from '../explorer/explorer.component'
1717
import { AlarmsComponent } from '../alarms/alarms.component'
1818
import { CertificatesComponent } from '../certificates/certificates.component'
@@ -21,11 +21,23 @@ import { AuditLogComponent } from '../audit-log/audit-log.component'
2121
import { HardwareInformationComponent } from '../hardware-information/hardware-information.component'
2222
import { SolComponent } from '../sol/sol.component'
2323
import { KvmComponent } from '../kvm/kvm.component'
24+
import { IderComponent } from '../ider/ider.component'
2425
import { GeneralComponent } from '../general/general.component'
2526
import { NetworkSettingsComponent } from '../network-settings/network-settings.component'
2627
import { environment } from '../../../environments/environment'
2728
import { TLSComponent } from '../tls/tls.component'
28-
import { TranslatePipe } from '@ngx-translate/core'
29+
import { TranslatePipe, TranslateService } from '@ngx-translate/core'
30+
import { DevicesService } from '../devices.service'
31+
import SnackbarDefaults from '../../shared/config/snackBarDefault'
32+
import { MatSnackBar } from '@angular/material/snack-bar'
33+
34+
interface DeviceDetailCategory {
35+
name: string
36+
description: string
37+
description2?: string
38+
component: string
39+
icon: string
40+
}
2941

3042
@Component({
3143
selector: 'app-device-detail',
@@ -40,6 +52,7 @@ import { TranslatePipe } from '@ngx-translate/core'
4052
HardwareInformationComponent,
4153
SolComponent,
4254
KvmComponent,
55+
IderComponent,
4356
GeneralComponent,
4457
ExplorerComponent,
4558
DeviceToolbarComponent,
@@ -62,67 +75,78 @@ import { TranslatePipe } from '@ngx-translate/core'
6275
]
6376
})
6477
export class DeviceDetailComponent implements OnInit, OnDestroy {
65-
// Dependency Injection
6678
private readonly activatedRoute = inject(ActivatedRoute)
79+
private readonly devicesService = inject(DevicesService)
80+
private readonly translate = inject(TranslateService)
81+
private readonly snackBar = inject(MatSnackBar)
6782
private readonly destroy$ = new Subject<void>()
83+
private readonly ismSku = '16400'
6884
public deviceId = ''
6985
public readonly isCloudMode: boolean = environment.cloud
70-
71-
categories = [
72-
{
73-
name: 'deviceDetail.general.value',
74-
description: 'deviceDetail.generalDescription.value',
75-
description2: '',
76-
component: 'general',
77-
icon: 'info'
78-
},
79-
{
80-
name: 'deviceDetail.kvm.value',
81-
description: 'deviceDetail.kvmDescription.value',
82-
component: 'kvm',
83-
icon: 'tv'
84-
},
85-
{
86-
name: 'deviceDetail.sol.value',
87-
description: 'deviceDetail.solDescription.value',
88-
component: 'sol',
89-
icon: 'keyboard'
90-
},
91-
{
92-
name: 'deviceDetail.hardwareInfo.value',
93-
description: 'deviceDetail.hardwareInfoDescription.value',
94-
component: 'hardware-info',
95-
icon: 'memory'
96-
},
97-
{
98-
name: 'deviceDetail.auditLog.value',
99-
description: 'deviceDetail.auditLogDescription.value',
100-
component: 'audit-log',
101-
icon: 'history'
102-
},
103-
{
104-
name: 'deviceDetail.eventLog.value',
105-
description: 'deviceDetail.eventLogDescription.value',
106-
component: 'event-log',
107-
icon: 'event_list'
108-
},
109-
{
110-
name: 'deviceDetail.alarms.value',
111-
description: 'deviceDetail.alarmsDescription.value',
112-
component: 'alarms',
113-
icon: 'alarm'
114-
},
115-
{
116-
name: 'deviceDetail.certificates.value',
117-
description: 'deviceDetail.certificatesDescription.value',
118-
component: 'certificates',
119-
icon: 'verified'
120-
}
121-
]
122-
123-
constructor() {
86+
public isISMSystem = signal(false)
87+
public categories = computed(() => {
88+
const base: DeviceDetailCategory[] = [
89+
{
90+
name: 'deviceDetail.general.value',
91+
description: 'deviceDetail.generalDescription.value',
92+
description2: '',
93+
component: 'general',
94+
icon: 'info'
95+
},
96+
{
97+
name: 'deviceDetail.kvm.value',
98+
description: 'deviceDetail.kvmDescription.value',
99+
component: 'kvm',
100+
icon: 'tv'
101+
},
102+
{
103+
name: 'deviceDetail.sol.value',
104+
description: 'deviceDetail.solDescription.value',
105+
component: 'sol',
106+
icon: 'keyboard'
107+
},
108+
{
109+
name: 'deviceDetail.hardwareInfo.value',
110+
description: 'deviceDetail.hardwareInfoDescription.value',
111+
component: 'hardware-info',
112+
icon: 'memory'
113+
},
114+
{
115+
name: 'deviceDetail.auditLog.value',
116+
description: 'deviceDetail.auditLogDescription.value',
117+
component: 'audit-log',
118+
icon: 'history'
119+
},
120+
{
121+
name: 'deviceDetail.eventLog.value',
122+
description: 'deviceDetail.eventLogDescription.value',
123+
component: 'event-log',
124+
icon: 'event_list'
125+
},
126+
{
127+
name: 'deviceDetail.alarms.value',
128+
description: 'deviceDetail.alarmsDescription.value',
129+
component: 'alarms',
130+
icon: 'alarm'
131+
},
132+
{
133+
name: 'deviceDetail.certificates.value',
134+
description: 'deviceDetail.certificatesDescription.value',
135+
component: 'certificates',
136+
icon: 'verified'
137+
},
138+
{
139+
name: 'deviceDetail.ider.value',
140+
description: 'deviceDetail.iderDescription.value',
141+
component: 'ider',
142+
icon: 'storage'
143+
}
144+
]
145+
const filtered = base
146+
.filter((c) => !(this.isISMSystem() && c.component === 'kvm'))
147+
.filter((c) => !(!this.isISMSystem() && c.component === 'ider'))
124148
if (!this.isCloudMode) {
125-
this.categories.push(
149+
filtered.push(
126150
{
127151
name: 'deviceDetail.explorer.value',
128152
description: 'deviceDetail.explorerDescription.value',
@@ -143,18 +167,43 @@ export class DeviceDetailComponent implements OnInit, OnDestroy {
143167
}
144168
)
145169
}
146-
}
170+
return filtered
171+
})
147172

148173
public currentView = 'general'
149174
public isLoading = signal(false)
150175
isCollapsed = false
151176

152177
ngOnInit(): void {
153-
this.activatedRoute.params.pipe(takeUntil(this.destroy$)).subscribe((params) => {
154-
this.isLoading.set(true)
155-
this.deviceId = params.id
156-
this.currentView = params.component || 'general'
157-
})
178+
this.activatedRoute.params
179+
.pipe(
180+
takeUntil(this.destroy$),
181+
switchMap((params) => {
182+
this.isLoading.set(true)
183+
this.deviceId = params.id
184+
this.currentView = params.component || 'general'
185+
return this.devicesService.getAMTVersion(this.deviceId).pipe(
186+
catchError(() => {
187+
const msg: string = this.translate.instant('general.errorAMTVersion.value')
188+
this.snackBar.open(msg, undefined, SnackbarDefaults.defaultError)
189+
return of(null)
190+
})
191+
)
192+
})
193+
)
194+
.subscribe({
195+
next: (amtVersion) => {
196+
const sku: string = amtVersion?.CIM_SoftwareIdentity?.responses[4]?.VersionString ?? ''
197+
const isIsm = sku === this.ismSku
198+
this.isISMSystem.set(isIsm)
199+
if (isIsm && this.currentView === 'kvm') this.currentView = 'ider'
200+
if (!isIsm && this.currentView === 'ider') this.currentView = 'kvm'
201+
this.isLoading.set(false)
202+
},
203+
error: () => {
204+
this.isLoading.set(false)
205+
}
206+
})
158207
}
159208

160209
ngOnDestroy(): void {

0 commit comments

Comments
 (0)