diff --git a/cypress/e2e/integration/device/paging.spec.ts b/cypress/e2e/integration/device/paging.spec.ts index b6c612cbf..5210e0698 100644 --- a/cypress/e2e/integration/device/paging.spec.ts +++ b/cypress/e2e/integration/device/paging.spec.ts @@ -20,17 +20,6 @@ describe('Test Device Page', () => { body: devices.getAll.forPaging.response }).as('get-devices') - cy.myIntercept('GET', 'api/v1/devices/stats', { - statusCode: httpCodes.SUCCESS, - body: { - totalCount: deviceFixtures.totalCount, - connectedCount: 0, - disconnectedCount: 0, - activatedCount: 0, - discoveredCount: 0 - } - }).as('get-device-stats') - cy.myIntercept('GET', /tags$/, { statusCode: httpCodes.SUCCESS, body: tags.getAll.success.response @@ -42,7 +31,6 @@ describe('Test Device Page', () => { }).as('get-powerstate') cy.goToPage('Devices') - cy.wait('@get-device-stats') }) it('pagination for next page', () => { diff --git a/src/app/devices/devices.component.html b/src/app/devices/devices.component.html index 452e92ec1..72b20d7e1 100644 --- a/src/app/devices/devices.component.html +++ b/src/app/devices/devices.component.html @@ -37,13 +37,15 @@ - - - - - - + @if (!isCloudMode) { + + + + + + + } @if (isNoData()) {

@if (!filteredTags().length) { @@ -101,7 +103,7 @@

{{ element.hostname }} - @if (element.friendlyName !== null) { + @if (element.friendlyName) {  ({{ element.friendlyName }}) } @@ -132,7 +134,17 @@

{{ 'devices.table.productType.value' | translate }} - {{ getProductType(element) }} + {{ getProductType(element) }} + + + + + + {{ 'devices.table.type.value' | translate }} + + + {{ 'devices.tabs.' + getDeviceType(element) + '.value' | translate }} + diff --git a/src/app/devices/devices.component.scss b/src/app/devices/devices.component.scss index f18d32f9b..2aa7330a0 100644 --- a/src/app/devices/devices.component.scss +++ b/src/app/devices/devices.component.scss @@ -7,6 +7,25 @@ flex: 0 0 75px; } +.product-type-chip, +.device-type-chip { + display: inline-flex; + align-items: center; + min-height: 30px; + padding: 0 12px; + border-radius: 16px; + background: #e5e5e5; +} + +.device-type-chip { + background: #b0bec5; +} + +.device-type-chip.activated { + background: #0277bd; + color: #fff; +} + mat-icon.addTag { visibility: hidden; } diff --git a/src/app/devices/devices.component.spec.ts b/src/app/devices/devices.component.spec.ts index f1bf5a6cc..3873bff10 100644 --- a/src/app/devices/devices.component.spec.ts +++ b/src/app/devices/devices.component.spec.ts @@ -285,8 +285,35 @@ describe('DevicesComponent', () => { }) }) + describe('getDeviceType', () => { + it('should return activated for a real AMT control mode', () => { + const device = { ...device01, deviceInfo: { currentMode: 'client control mode' } } as Device + expect(component.getDeviceType(device)).toBe('activated') + }) + + it('should return discovered when the device is not activated', () => { + const device = { ...device01, deviceInfo: { currentMode: 'not activated' } } as Device + expect(component.getDeviceType(device)).toBe('discovered') + }) + + it('should return discovered when currentMode is missing', () => { + const device = { ...device01, deviceInfo: undefined } as Device + expect(component.getDeviceType(device)).toBe('discovered') + }) + + it('should keep the computed device type logic stable for rendering', () => { + const activatedDevice = { ...device01, deviceInfo: { currentMode: 'client control mode' } } as Device + const discoveredDevice = { ...device02, deviceInfo: { currentMode: 'not activated' } } as Device + + expect(component.getDeviceType(activatedDevice)).toBe('activated') + expect(component.getDeviceType(discoveredDevice)).toBe('discovered') + }) + }) + describe('onTabChange / server-side counts', () => { beforeEach(() => { + component.isCloudMode = false + component.getDevices() getDevicesSpy.mockClear() }) diff --git a/src/app/devices/devices.component.ts b/src/app/devices/devices.component.ts index 4d1155066..89ba4ed5a 100644 --- a/src/app/devices/devices.component.ts +++ b/src/app/devices/devices.component.ts @@ -199,6 +199,7 @@ export class DevicesComponent implements OnInit, AfterViewInit { 'guid', 'status', 'productType', + 'deviceType', 'tags', 'actions', 'notification' @@ -221,6 +222,7 @@ export class DevicesComponent implements OnInit, AfterViewInit { 'select', 'hostname', 'productType', + 'deviceType', 'tags', 'actions', 'notification' @@ -283,10 +285,12 @@ export class DevicesComponent implements OnInit, AfterViewInit { getDevices(): void { this.isLoading.set(true) + let responseTotalCount: number | undefined - // Counts (all/activated/discovered) are computed server-side and shared with - // headless/API consumers, so refresh them alongside the current page. - this.loadStats() + if (!this.isCloudMode) { + // Console exposes server-side counts for the activated/discovered tabs. + this.loadStats() + } // Store previous selection before making the request const prevSelected = this.selectedDevices.selected.map((d) => d.guid) @@ -295,6 +299,7 @@ export class DevicesComponent implements OnInit, AfterViewInit { .getDevices({ ...this.pageEvent, tags: this.filteredTags(), status: this.currentTabStatus() }) .pipe( switchMap((res) => { + responseTotalCount = res.totalCount if (!environment.cloud) { return of(res.data) // Return as-is for non-cloud } @@ -335,6 +340,10 @@ export class DevicesComponent implements OnInit, AfterViewInit { ) .subscribe((devices) => { this.devices.data = devices + if (this.isCloudMode) { + this.serverTotalCount = responseTotalCount ?? devices.length + this.totalCount.set(this.serverTotalCount) + } // Restore selection state on data retrieval this.selectedDevices.clear() @@ -435,6 +444,11 @@ export class DevicesComponent implements OnInit, AfterViewInit { return '' } + getDeviceType(device: Device): DeviceFilterStatus { + const currentMode = device.deviceInfo?.currentMode?.trim().toLowerCase() + return currentMode && currentMode !== 'not activated' ? 'activated' : 'discovered' + } + translateConnectionStatus(status?: boolean): string { switch (status) { case false: diff --git a/src/assets/i18n/ar.json b/src/assets/i18n/ar.json index 1368112d0..46a8550e2 100644 --- a/src/assets/i18n/ar.json +++ b/src/assets/i18n/ar.json @@ -1077,6 +1077,10 @@ "description": "Table column header for product type", "value": "نوع المنتج" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "النوع" + }, "devices.table.tags": { "description": "عنوان عمود الجدول للعلامات", "value": "العلامات" diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 0b83319d6..31ef0a06f 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -1073,6 +1073,10 @@ "description": "Table column header for product type", "value": "Produkttyp" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "Typ" + }, "devices.table.tags": { "description": "Tabellen-Spaltenüberschrift für Tags", "value": "Tags" diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 581cd2cdb..1cdb5d61f 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1395,6 +1395,10 @@ "description": "Table column header for product type", "value": "Product Type" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "Type" + }, "devices.table.tags": { "description": "Table column header for tags", "value": "Tags" diff --git a/src/assets/i18n/es.json b/src/assets/i18n/es.json index dd10f6ef8..92fbb6f23 100644 --- a/src/assets/i18n/es.json +++ b/src/assets/i18n/es.json @@ -1073,6 +1073,10 @@ "description": "Table column header for product type", "value": "Tipo de producto" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "Tipo" + }, "devices.table.tags": { "description": "Encabezado de columna de tabla para etiquetas", "value": "Etiquetas" diff --git a/src/assets/i18n/fi.json b/src/assets/i18n/fi.json index 36acaf083..6f27406ca 100644 --- a/src/assets/i18n/fi.json +++ b/src/assets/i18n/fi.json @@ -1073,6 +1073,10 @@ "description": "Table column header for product type", "value": "Tuotetyyppi" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "Tyyppi" + }, "devices.table.tags": { "description": "Taulukon sarakkeen otsikko tunnisteille", "value": "Tunnisteet" diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index f1c3799b5..076ad2917 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -1077,6 +1077,10 @@ "description": "Table column header for product type", "value": "Type de produit" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "Type" + }, "devices.table.tags": { "description": "En-tête de colonne du tableau pour les balises", "value": "Balises" diff --git a/src/assets/i18n/he.json b/src/assets/i18n/he.json index 950d01b23..8653aeff0 100644 --- a/src/assets/i18n/he.json +++ b/src/assets/i18n/he.json @@ -1069,6 +1069,10 @@ "description": "Table column header for product type", "value": "סוג מוצר" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "סוג" + }, "devices.table.tags": { "description": "כותרת עמודות טבלה לתגיות", "value": "תגיות" diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index 89af0797a..4627f397f 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -1069,6 +1069,10 @@ "description": "Table column header for product type", "value": "Tipo di prodotto" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "Tipo" + }, "devices.table.tags": { "description": "Intestazione della colonna della tabella per i tag", "value": "Tag" diff --git a/src/assets/i18n/ja.json b/src/assets/i18n/ja.json index a57473d7d..48f69a573 100644 --- a/src/assets/i18n/ja.json +++ b/src/assets/i18n/ja.json @@ -1069,6 +1069,10 @@ "description": "Table column header for product type", "value": "製品タイプ" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "タイプ" + }, "devices.table.tags": { "description": "タグ用テーブル列ヘッダー", "value": "タグ" diff --git a/src/assets/i18n/nl.json b/src/assets/i18n/nl.json index 0a48a08a4..54dd0de45 100644 --- a/src/assets/i18n/nl.json +++ b/src/assets/i18n/nl.json @@ -1077,6 +1077,10 @@ "description": "Table column header for product type", "value": "Producttype" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "Type" + }, "devices.table.tags": { "description": "Tabelkolomkop voor tags", "value": "Tags" diff --git a/src/assets/i18n/ru.json b/src/assets/i18n/ru.json index d25e66b8f..b480b5e22 100644 --- a/src/assets/i18n/ru.json +++ b/src/assets/i18n/ru.json @@ -1077,6 +1077,10 @@ "description": "Table column header for product type", "value": "Тип продукта" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "Тип" + }, "devices.table.tags": { "description": "Заголовок столбца таблицы для тегов", "value": "Теги" diff --git a/src/assets/i18n/sv.json b/src/assets/i18n/sv.json index 68d55108e..e379dd734 100644 --- a/src/assets/i18n/sv.json +++ b/src/assets/i18n/sv.json @@ -981,6 +981,10 @@ "description": "Table column header for product type", "value": "Produkttyp" }, + "devices.table.type": { + "description": "Table column header for device type", + "value": "Typ" + }, "devices.table.tags": { "description": "Tabellkolumnrubrik för taggar", "value": "Taggar"