diff --git a/cypress/e2e/integration/device/device.spec.ts b/cypress/e2e/integration/device/device.spec.ts
index 1ee01f3cf..8b8a4498e 100644
--- a/cypress/e2e/integration/device/device.spec.ts
+++ b/cypress/e2e/integration/device/device.spec.ts
@@ -41,7 +41,7 @@ describe('Test Device Page', () => {
if (Cypress.env('ISOLATE').charAt(0).toLowerCase() !== 'n') {
cy.myIntercept('GET', '**/devices?tags=Windows&$top=25&$skip=0&$count=true', {
statusCode: httpCodes.SUCCESS,
- body: devices.getAll.windows.response.data
+ body: devices.getAll.windows.response
}).as('get-windows')
cy.goToPage('Devices')
diff --git a/src/app/devices/devices.component.html b/src/app/devices/devices.component.html
index 4b3923ada..008a7e854 100644
--- a/src/app/devices/devices.component.html
+++ b/src/app/devices/devices.component.html
@@ -46,6 +46,11 @@
@@ -120,6 +125,15 @@
}
+
+
+ {{
+ 'devices.table.productType.value' | translate
+ }}
+
+ {{ getProductType(element) }}
+
+
@@ -203,7 +217,7 @@
diff --git a/src/app/devices/devices.component.spec.ts b/src/app/devices/devices.component.spec.ts
index 2e0b472ae..0b53e5144 100644
--- a/src/app/devices/devices.component.spec.ts
+++ b/src/app/devices/devices.component.spec.ts
@@ -250,4 +250,100 @@ describe('DevicesComponent', () => {
component.tagFilterChange(matSelectChange)
expect(component.filteredTags()).toBe(mockValue)
})
+
+ describe('getProductType', () => {
+ it('should return ISM when bit 4 (0x10) is set', () => {
+ const device = { ...device01, deviceInfo: { fwSku: '16' } } as Device // 0x10 = 16
+ expect(component.getProductType(device)).toBe('ISM')
+ })
+
+ it('should return vPro when bit 3 (0x08) is set and bit 4 is not', () => {
+ const device = { ...device01, deviceInfo: { fwSku: '8' } } as Device // 0x08 = 8
+ expect(component.getProductType(device)).toBe('vPro')
+ })
+
+ it('should return ISM when both bit 4 and bit 3 are set (ISM takes priority)', () => {
+ const device = { ...device01, deviceInfo: { fwSku: '24' } } as Device // 0x18 = 24
+ expect(component.getProductType(device)).toBe('ISM')
+ })
+
+ it('should return empty string when neither bit is set', () => {
+ const device = { ...device01, deviceInfo: { fwSku: '4' } } as Device // 0x04 = 4
+ expect(component.getProductType(device)).toBe('')
+ })
+
+ it('should return empty string when fwSku is undefined', () => {
+ const device = { ...device01, deviceInfo: undefined } as Device
+ expect(component.getProductType(device)).toBe('')
+ })
+
+ it('should return empty string when fwSku is not a number', () => {
+ const device = { ...device01, deviceInfo: { fwSku: 'notanumber' } } as Device
+ expect(component.getProductType(device)).toBe('')
+ })
+ })
+
+ describe('onTabChange / applyTabFilter', () => {
+ beforeEach(() => {
+ const baseInfo = { fwVersion: '', fwBuild: '', fwSku: '0', features: '', ipAddress: '' }
+ component.allDevicesData = [
+ { ...device01, deviceInfo: { ...baseInfo, currentMode: 'acm', discovered: false } },
+ { ...device02, deviceInfo: { ...baseInfo, currentMode: 'not activated', discovered: true } }
+ ]
+ })
+
+ it('should show all devices on tab 0', () => {
+ component.onTabChange(0)
+ expect(component.devices.data.length).toBe(2)
+ })
+
+ it('should filter to activated devices on tab 1', () => {
+ component.onTabChange(1)
+ expect(component.devices.data.length).toBe(1)
+ expect(component.devices.data[0].guid).toBe(device01.guid)
+ })
+
+ it('should filter to discovered devices on tab 2', () => {
+ component.onTabChange(2)
+ expect(component.devices.data.length).toBe(1)
+ expect(component.devices.data[0].guid).toBe(device02.guid)
+ })
+
+ it('should set totalCount to serverTotalCount on tab 0', () => {
+ ;(component as any).serverTotalCount = 42
+ component.onTabChange(0)
+ expect(component.totalCount()).toBe(42)
+ })
+
+ it('should set totalCount to filtered length on tab 1', () => {
+ component.onTabChange(1)
+ expect(component.totalCount()).toBe(1)
+ })
+
+ it('should set totalCount to filtered length on tab 2', () => {
+ component.onTabChange(2)
+ expect(component.totalCount()).toBe(1)
+ })
+ })
+
+ describe('isNoData', () => {
+ it('should return false when allDevicesData has entries regardless of totalCount', () => {
+ component.allDevicesData = [device01]
+ component.isLoading.set(false)
+ component.totalCount.set(0) // filtered tab has 0 — should not trigger no-data
+ expect(component.isNoData()).toBeFalse()
+ })
+
+ it('should return true only when allDevicesData is empty and not loading', () => {
+ component.allDevicesData = []
+ component.isLoading.set(false)
+ expect(component.isNoData()).toBeTrue()
+ })
+
+ it('should return false when loading even if allDevicesData is empty', () => {
+ component.allDevicesData = []
+ component.isLoading.set(true)
+ expect(component.isNoData()).toBeFalse()
+ })
+ })
})
diff --git a/src/app/devices/devices.component.ts b/src/app/devices/devices.component.ts
index 27d9df77f..5907ba1df 100644
--- a/src/app/devices/devices.component.ts
+++ b/src/app/devices/devices.component.ts
@@ -47,6 +47,7 @@ import { MatButton, MatIconButton } from '@angular/material/button'
import { MatToolbar } from '@angular/material/toolbar'
import { MatSort } from '@angular/material/sort'
import { MatInput } from '@angular/material/input'
+import { MatTabGroup, MatTab } from '@angular/material/tabs'
import { TranslatePipe, TranslateService } from '@ngx-translate/core'
@Component({
@@ -86,6 +87,8 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core'
MatPaginator,
MatHint,
RouterModule,
+ MatTabGroup,
+ MatTab,
TranslatePipe
]
})
@@ -108,6 +111,62 @@ export class DevicesComponent implements OnInit, AfterViewInit {
public powerStates: any
public isCloudMode: boolean = environment.cloud
+ public activeTab = signal(0)
+ public allDevicesData: Device[] = []
+ private serverTotalCount = 0
+
+ get allCount(): number {
+ return this.serverTotalCount
+ }
+
+ get allTabLabel(): string {
+ return `${this.translate.instant('devices.tabs.all.value')} (${this.allCount})`
+ }
+
+ get activatedTabLabel(): string {
+ return `${this.translate.instant('devices.tabs.activated.value')} (${this.activatedCount})`
+ }
+
+ get discoveredTabLabel(): string {
+ return `${this.translate.instant('devices.tabs.discovered.value')} (${this.discoveredCount})`
+ }
+
+ get activatedCount(): number {
+ return this.allDevicesData.filter(
+ (d) => d.deviceInfo?.currentMode != null && d.deviceInfo.currentMode !== 'not activated'
+ ).length
+ }
+
+ get discoveredCount(): number {
+ return this.allDevicesData.filter((d) => d.deviceInfo?.discovered === true).length
+ }
+
+ onTabChange(index: number): void {
+ this.activeTab.set(index)
+ this.applyTabFilter()
+ }
+
+ private applyTabFilter(): void {
+ let filtered: Device[]
+ switch (this.activeTab()) {
+ case 1:
+ filtered = this.allDevicesData.filter(
+ (d) => d.deviceInfo?.currentMode != null && d.deviceInfo.currentMode !== 'not activated'
+ )
+ this.totalCount.set(filtered.length)
+ break
+ case 2:
+ filtered = this.allDevicesData.filter((d) => d.deviceInfo?.discovered === true)
+ this.totalCount.set(filtered.length)
+ break
+ default:
+ filtered = this.allDevicesData
+ this.totalCount.set(this.serverTotalCount)
+ break
+ }
+ this.devices.data = filtered
+ }
+
get deleteDeviceLabel(): string {
return this.isCloudMode
? this.translate.instant('devices.actions.deactivateCloud.value')
@@ -119,6 +178,7 @@ export class DevicesComponent implements OnInit, AfterViewInit {
'hostname',
'guid',
'status',
+ 'productType',
'tags',
'actions',
'notification'
@@ -140,6 +200,7 @@ export class DevicesComponent implements OnInit, AfterViewInit {
this.displayedColumns = [
'select',
'hostname',
+ 'productType',
'tags',
'actions',
'notification'
@@ -211,6 +272,7 @@ export class DevicesComponent implements OnInit, AfterViewInit {
.pipe(
switchMap((res) => {
this.totalCount.set(res.totalCount)
+ this.serverTotalCount = res.totalCount
if (!environment.cloud) {
return of(res.data) // Return as-is for non-cloud
@@ -251,7 +313,8 @@ export class DevicesComponent implements OnInit, AfterViewInit {
})
)
.subscribe((devices) => {
- this.devices.data = devices
+ this.allDevicesData = devices
+ this.applyTabFilter()
// Restore selection state on data retrieval
this.selectedDevices.clear()
@@ -335,13 +398,23 @@ export class DevicesComponent implements OnInit, AfterViewInit {
}
isNoData(): boolean {
- return !this.isLoading() && this.totalCount() === 0
+ return !this.isLoading() && this.allDevicesData.length === 0
}
async navigateTo(path: string): Promise {
await this.router.navigate([`/devices/${path}`])
}
+ getProductType(device: Device): string {
+ const skuNum = parseInt(device.deviceInfo?.fwSku ?? '', 10)
+ if (isNaN(skuNum)) return ''
+ const isISM = (skuNum & 0x10) > 0
+ const isVPro = (skuNum & 0x08) > 0
+ if (isISM) return 'ISM'
+ if (isVPro) return 'vPro'
+ return ''
+ }
+
translateConnectionStatus(status?: boolean): string {
switch (status) {
case false:
diff --git a/src/assets/i18n/ar.json b/src/assets/i18n/ar.json
index 57c85e7f4..e91b1eff5 100644
--- a/src/assets/i18n/ar.json
+++ b/src/assets/i18n/ar.json
@@ -1065,10 +1065,26 @@
"description": "عنوان عمود الجدول لحالة الجهاز",
"value": "الحالة"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "نوع المنتج"
+ },
"devices.table.tags": {
"description": "عنوان عمود الجدول للعلامات",
"value": "العلامات"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "الكل"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "مُفعَّل"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "مكتشَف"
+ },
"deviceToolbar.power.on": {
"description": "تلميح الطاقة للحالة تشغيل",
"value": "الطاقة: تشغيل"
diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json
index bc84346e9..b1aeecf32 100644
--- a/src/assets/i18n/de.json
+++ b/src/assets/i18n/de.json
@@ -1065,10 +1065,26 @@
"description": "Tabellen-Spaltenüberschrift für Gerätestatus",
"value": "Status"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "Produkttyp"
+ },
"devices.table.tags": {
"description": "Tabellen-Spaltenüberschrift für Tags",
"value": "Tags"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "Alle"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "Aktiviert"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "Entdeckt"
+ },
"deviceToolbar.power.on": {
"description": "Energie-Tooltip für Eingeschaltet",
"value": "Strom: Ein"
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 5830a8654..d0d4819bd 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -1237,10 +1237,26 @@
"description": "Table column header for device status",
"value": "Status"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "Product Type"
+ },
"devices.table.tags": {
"description": "Table column header for tags",
"value": "Tags"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "All"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "Activated"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "Discovered"
+ },
"deviceToolbar.power.on": {
"description": "Power tooltip for On",
"value": "Power: On"
@@ -1253,10 +1269,6 @@
"description": "Power tooltip for Off",
"value": "Power: Off"
},
- "deviceToolbar.power.refreshAriaLabel": {
- "description": "Aria label for the refresh power status button",
- "value": "Refresh power status"
- },
"deviceUserConsent.description": {
"description": "Description for user consent for devices",
"value": "A user consent code generated by Intel AMT is required to access the system."
diff --git a/src/assets/i18n/es.json b/src/assets/i18n/es.json
index 4a5583b57..3d7c5afcc 100644
--- a/src/assets/i18n/es.json
+++ b/src/assets/i18n/es.json
@@ -1065,10 +1065,26 @@
"description": "Encabezado de columna de la tabla para el estado del dispositivo",
"value": "Estado"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "Tipo de producto"
+ },
"devices.table.tags": {
"description": "Encabezado de columna de tabla para etiquetas",
"value": "Etiquetas"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "Todos"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "Activado"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "Descubierto"
+ },
"deviceToolbar.power.on": {
"description": "Información de energía para Encendido",
"value": "Energía: Encendido"
diff --git a/src/assets/i18n/fi.json b/src/assets/i18n/fi.json
index 2aeabbdbc..a9a70a9db 100644
--- a/src/assets/i18n/fi.json
+++ b/src/assets/i18n/fi.json
@@ -1065,10 +1065,26 @@
"description": "Laitteen tilan taulukon sarakkeen otsikko",
"value": "Tila"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "Tuotetyyppi"
+ },
"devices.table.tags": {
"description": "Taulukon sarakkeen otsikko tunnisteille",
"value": "Tunnisteet"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "Kaikki"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "Aktivoitu"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "Löydetty"
+ },
"deviceToolbar.power.on": {
"description": "Virtatilan vihjeteksti tilalle päällä",
"value": "Virta: Päällä"
diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json
index fbe4a1230..3652c56d3 100644
--- a/src/assets/i18n/fr.json
+++ b/src/assets/i18n/fr.json
@@ -1065,10 +1065,26 @@
"description": "En-tête de colonne du tableau pour l'état de l'appareil",
"value": "Statut"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "Type de produit"
+ },
"devices.table.tags": {
"description": "En-tête de colonne du tableau pour les balises",
"value": "Balises"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "Tous"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "Activé"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "Découvert"
+ },
"deviceToolbar.power.on": {
"description": "Info-bulle d'alimentation pour l'état allumé",
"value": "Alimentation : Allumé"
diff --git a/src/assets/i18n/he.json b/src/assets/i18n/he.json
index 6315ca2cb..d38a34010 100644
--- a/src/assets/i18n/he.json
+++ b/src/assets/i18n/he.json
@@ -1065,10 +1065,26 @@
"description": "כותרת עמודות טבלה למצב המכשיר",
"value": "סטָטוּס"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "סוג מוצר"
+ },
"devices.table.tags": {
"description": "כותרת עמודות טבלה לתגיות",
"value": "תגיות"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "הכל"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "מופעל"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "התגלה"
+ },
"deviceToolbar.power.on": {
"description": "רמז מצב צריכת חשמל עבור פועל",
"value": "חשמל: פועל"
diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json
index 398e4c77d..de20e95b7 100644
--- a/src/assets/i18n/it.json
+++ b/src/assets/i18n/it.json
@@ -1065,10 +1065,26 @@
"description": "Intestazione della colonna della tabella per lo stato del dispositivo",
"value": "Stato"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "Tipo di prodotto"
+ },
"devices.table.tags": {
"description": "Intestazione della colonna della tabella per i tag",
"value": "Tag"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "Tutti"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "Attivato"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "Scoperto"
+ },
"deviceToolbar.power.on": {
"description": "Tooltip di alimentazione per acceso",
"value": "Alimentazione: Acceso"
diff --git a/src/assets/i18n/ja.json b/src/assets/i18n/ja.json
index c85d85cd0..63fe8f091 100644
--- a/src/assets/i18n/ja.json
+++ b/src/assets/i18n/ja.json
@@ -1065,10 +1065,26 @@
"description": "デバイス状態のテーブル列見出し",
"value": "ステータス"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "製品タイプ"
+ },
"devices.table.tags": {
"description": "タグ用テーブル列ヘッダー",
"value": "タグ"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "すべて"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "アクティベート済み"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "検出済み"
+ },
"deviceToolbar.power.on": {
"description": "電源オンのツールチップ",
"value": "電源: オン"
diff --git a/src/assets/i18n/nl.json b/src/assets/i18n/nl.json
index f5a255335..60ad0e8c7 100644
--- a/src/assets/i18n/nl.json
+++ b/src/assets/i18n/nl.json
@@ -1065,10 +1065,26 @@
"description": "Tabelkolomkop voor apparaatstatus",
"value": "Status"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "Producttype"
+ },
"devices.table.tags": {
"description": "Tabelkolomkop voor tags",
"value": "Tags"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "Alle"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "Geactiveerd"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "Ontdekt"
+ },
"deviceToolbar.power.on": {
"description": "Energie-tooltip voor ingeschakeld",
"value": "Stroom: Aan"
diff --git a/src/assets/i18n/ru.json b/src/assets/i18n/ru.json
index 15c59a596..2f87bc9f5 100644
--- a/src/assets/i18n/ru.json
+++ b/src/assets/i18n/ru.json
@@ -1065,10 +1065,26 @@
"description": "Заголовок столбца таблицы для состояния устройства",
"value": "Статус"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "Тип продукта"
+ },
"devices.table.tags": {
"description": "Заголовок столбца таблицы для тегов",
"value": "Теги"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "Все"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "Активирован"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "Обнаружен"
+ },
"deviceToolbar.power.on": {
"description": "Подсказка питания для состояния включено",
"value": "Питание: Включено"
diff --git a/src/assets/i18n/sv.json b/src/assets/i18n/sv.json
index b76e46eee..ef06304c5 100644
--- a/src/assets/i18n/sv.json
+++ b/src/assets/i18n/sv.json
@@ -1065,10 +1065,26 @@
"description": "Tabellkolumnrubrik för enhetsstatus",
"value": "Status"
},
+ "devices.table.productType": {
+ "description": "Table column header for product type",
+ "value": "Produkttyp"
+ },
"devices.table.tags": {
"description": "Tabellkolumnrubrik för taggar",
"value": "Taggar"
},
+ "devices.tabs.all": {
+ "description": "Tab label for all devices",
+ "value": "Alla"
+ },
+ "devices.tabs.activated": {
+ "description": "Tab label for activated devices",
+ "value": "Aktiverad"
+ },
+ "devices.tabs.discovered": {
+ "description": "Tab label for discovered devices",
+ "value": "Upptäckt"
+ },
"deviceToolbar.power.on": {
"description": "Strömverktygstips för på",
"value": "Ström: På"
diff --git a/src/models/models.ts b/src/models/models.ts
index ef1e1db38..e0200c13b 100644
--- a/src/models/models.ts
+++ b/src/models/models.ts
@@ -31,6 +31,7 @@ export interface DeviceInfo {
currentMode: string
features: string
ipAddress: string
+ discovered?: boolean
firstDiscovered?: Date
lastSynced?: Date
}